Files
Charon/docs/plans/current_spec.md
GitHub Actions 67f2f27cf8 feat: Add Import Success Modal and Certificate Status Card features
- Implemented ImportSuccessModal to replace alert with a modal displaying import results and guidance.
- Updated ImportCaddy to show the new modal with import summary and navigation options.
- Created CertificateStatusCard to display certificate provisioning status on the dashboard.
- Enhanced API types and hooks to support new features.
- Added unit tests for ImportSuccessModal and CertificateStatusCard components.
- Updated QA report to reflect the status of the new features and tests.
2025-12-12 00:42:27 +00:00

9.5 KiB

Cerberus/Security Feature Issues - Diagnostic Plan

Date: December 12, 2025 Status: Analysis Complete


Issue Summary

# Issue Severity
1 Cerberus shows ON by default on first load (should be OFF) High
2 Cerberus dashboard header shows "disabled" even when enabled Medium
3 CrowdSec toggle auto-enables when Cerberus is enabled Medium
4 CrowdSec toggle unresponsive + Config button grayed out High

Root Cause Analysis

Issue 1: Cerberus Shows ON by Default

Root Cause: The feature_flags_handler.go has a default value of true for all feature flags including feature.cerberus.enabled.

File: backend/internal/api/handlers/feature_flags_handler.go#L39-L42

// Line 39-42
for _, key := range defaultFlags {
    defaultVal := true  // <-- THIS IS THE BUG
    if v, ok := defaultFlagValues[key]; ok {
        defaultVal = v
    }

Problem: The code sets defaultVal := true for all flags, then only overrides it if the key exists in defaultFlagValues. However, feature.cerberus.enabled is NOT in defaultFlagValues:

// Line 29-31
var defaultFlagValues = map[string]bool{
    "feature.crowdsec.console_enrollment": false,
}

Result: On first load with an empty database, feature.cerberus.enabled defaults to true instead of false.

Additional Context:

  • The backend/internal/config/config.go#L60 correctly defaults CerberusEnabled to false:
    CerberusEnabled: getEnvAny("false", "CERBERUS_SECURITY_CERBERUS_ENABLED", ...) == "true"
    
  • However, the feature flags handler ignores this config and uses its own default.

Issue 2: Dashboard Header Shows "Disabled" Even When Enabled

Root Cause: The header banner logic in Security.tsx checks status.cerberus?.enabled which comes from the security status API, but there's a data source mismatch.

Files:

Problem Flow:

  1. Security.tsx checks status.cerberus?.enabled from /api/v1/security/status
  2. security_handler.go reads from config AND settings table:
    // Line 36-48
    enabled := h.cfg.CerberusEnabled
    var settingKey = "security.cerberus.enabled"  // <-- WRONG KEY!
    if h.db != nil {
        var setting struct{ Value string }
        if err := h.db.Raw("SELECT value FROM settings WHERE key = ? LIMIT 1", settingKey).Scan(&setting).Error; ...
    
  3. SystemSettings.tsx toggles feature.cerberus.enabled (via feature flags API)

The Mismatch:

Component Key Used
SystemSettings toggle feature.cerberus.enabled
Security status API security.cerberus.enabled

The toggle writes to feature.cerberus.enabled but the security status reads from security.cerberus.enabled - two different keys!


Issue 3: CrowdSec Auto-Enables When Cerberus is Enabled

Root Cause: The docker-compose.override.yml and docker-compose.local.yml both set CHARON_SECURITY_CROWDSEC_MODE=local:

File: docker-compose.override.yml#L21

- CHARON_SECURITY_CROWDSEC_MODE=local

Problem: When the container starts:

  1. Config loads with CrowdSecMode: "local" from env var
  2. Security status API returns crowdsec.enabled: true because mode is "local"
  3. Frontend shows CrowdSec as enabled

File: backend/internal/api/handlers/security_handler.go#L59-L62

// Allow runtime override for CrowdSec enabled flag via settings table
crowdsecEnabled := mode == "local"  // <-- Auto-true if mode is "local"

Issue 4: CrowdSec Toggle Unresponsive + Config Button Grayed Out

Root Cause: Multiple issues combine to break the toggle:

A. Toggle Disabled Logic:

File: frontend/src/pages/Security.tsx#L127

const crowdsecToggleDisabled = cerberusDisabled || crowdsecPowerMutation.isPending

File: frontend/src/pages/Security.tsx#L126

const cerberusDisabled = !status.cerberus?.enabled

Since status.cerberus?.enabled is false due to Issue 2 (wrong settings key), cerberusDisabled is true, making the toggle disabled.

B. Config Button Disabled:

File: frontend/src/pages/Security.tsx#L128

const crowdsecControlsDisabled = cerberusDisabled || crowdsecPowerMutation.isPending

Same logic - the controls are disabled because Cerberus appears disabled.

C. Switch Component Event Handling:

File: frontend/src/components/ui/Switch.tsx#L17-L20

The Switch component passes disabled to the native checkbox input, which prevents click events. This is correct behavior - the issue is the disabled prop is incorrectly true.


Fix 1: Update Feature Flag Defaults

File: backend/internal/api/handlers/feature_flags_handler.go

// Change defaultFlagValues to include cerberus.enabled as false
var defaultFlagValues = map[string]bool{
    "feature.cerberus.enabled":            false, // ADD THIS
    "feature.crowdsec.console_enrollment": false,
    "feature.uptime.enabled":              true,  // Uptime can default ON
}

Fix 2: Align Settings Keys

Option A (Recommended): Update security_handler.go to read from feature flags key

File: backend/internal/api/handlers/security_handler.go

// Line 37: Change from
var settingKey = "security.cerberus.enabled"
// To
var settingKey = "feature.cerberus.enabled"

Option B: Create a sync mechanism between feature flags and security settings

Fix 3: Remove CrowdSec Mode Override from Docker Compose

Files:

  • docker-compose.override.yml
  • docker-compose.local.yml
# Remove or comment out:
# - CHARON_SECURITY_CROWDSEC_MODE=local
# Or change to:
- CHARON_SECURITY_CROWDSEC_MODE=disabled

Fix 4: No Additional Fix Needed

Issue 4 is a symptom of Issues 1-2. Once those are fixed:

  • cerberusDisabled will be false when Cerberus is enabled
  • crowdsecToggleDisabled will be false
  • crowdsecControlsDisabled will be false
  • Toggle and Config button will be interactive

Test Scenarios

Test 1: Fresh Install Default State

Given: Clean database, no env vars set
When: User loads the Settings > System page
Then: Cerberus toggle should be OFF
And: /api/v1/feature-flags returns { "feature.cerberus.enabled": false }

Test 2: Cerberus Toggle Sync

Given: User is on Settings > System page
When: User enables Cerberus toggle
Then: /api/v1/security/status returns { "cerberus": { "enabled": true } }
And: Security dashboard header banner is NOT displayed

Test 3: CrowdSec Toggle Interaction

Given: Cerberus is enabled
And: User is on Security dashboard
When: User clicks CrowdSec toggle
Then: Toggle should respond to click
And: CrowdSec enabled state should change
And: Toast notification should appear

Test 4: CrowdSec Config Button

Given: Cerberus is enabled
And: User is on Security dashboard
When: User clicks CrowdSec "Config" button
Then: User should navigate to /security/crowdsec
And: Button should NOT be grayed out

Test 5: Environment Variable Override

Given: CERBERUS_SECURITY_CERBERUS_ENABLED=true set
When: User loads Settings > System (fresh DB)
Then: Cerberus toggle should be ON (env override)

Implementation Priority

Priority Fix Effort Impact
P0 Fix 2 (Key alignment) Low High - Fixes Issues 2, 4
P1 Fix 1 (Default values) Low High - Fixes Issue 1
P2 Fix 3 (Docker compose) Low Medium - Fixes Issue 3

Files to Modify

  1. backend/internal/api/handlers/feature_flags_handler.go - Add default value for cerberus
  2. backend/internal/api/handlers/security_handler.go - Change settings key to feature.cerberus.enabled
  3. docker-compose.override.yml - Remove or change CrowdSec mode
  4. docker-compose.local.yml - Remove or change CrowdSec mode

Additional Observations

  1. Dual Control Systems: There are two overlapping control systems:

    • Feature flags (feature.cerberus.enabled) - toggled in SystemSettings.tsx
    • Security config (SecurityConfig.Enabled in DB) - used by Enable/Disable endpoints

    Consider consolidating to one source of truth.

  2. Config vs Settings: The config.SecurityConfig struct loaded from env vars is separate from DB-backed SecurityConfig model. This creates confusion about which takes precedence.

  3. No Migration: When updating default values, existing users may need a migration or reset to see the new defaults.


Code Reference Summary

File Line Purpose
feature_flags_handler.go L29-31 Missing cerberus default
feature_flags_handler.go L39 defaultVal := true bug
security_handler.go L37 Wrong settings key
Security.tsx L126-128 Disabled state logic
SystemSettings.tsx L99-105 Feature toggle UI
docker-compose.override.yml L21 CrowdSec mode env var
config.go L60 Correct cerberus default