- 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.
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
CerberusEnabledtofalse: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:
- frontend/src/pages/Security.tsx#L141-L153 - Header banner logic
- backend/internal/api/handlers/security_handler.go#L35-L49 - Security status API
Problem Flow:
- Security.tsx checks
status.cerberus?.enabledfrom/api/v1/security/status - 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; ... - 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:
- Config loads with
CrowdSecMode: "local"from env var - Security status API returns
crowdsec.enabled: truebecause mode is "local" - 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.
Recommended Fixes
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.ymldocker-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:
cerberusDisabledwill befalsewhen Cerberus is enabledcrowdsecToggleDisabledwill befalsecrowdsecControlsDisabledwill befalse- 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
- backend/internal/api/handlers/feature_flags_handler.go - Add default value for cerberus
- backend/internal/api/handlers/security_handler.go - Change settings key to
feature.cerberus.enabled - docker-compose.override.yml - Remove or change CrowdSec mode
- docker-compose.local.yml - Remove or change CrowdSec mode
Additional Observations
-
Dual Control Systems: There are two overlapping control systems:
- Feature flags (
feature.cerberus.enabled) - toggled in SystemSettings.tsx - Security config (
SecurityConfig.Enabledin DB) - used by Enable/Disable endpoints
Consider consolidating to one source of truth.
- Feature flags (
-
Config vs Settings: The
config.SecurityConfigstruct loaded from env vars is separate from DB-backedSecurityConfigmodel. This creates confusion about which takes precedence. -
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 |