Files
Charon/docs/reports/crowdsec_final_validation_20251215.md

11 KiB

CrowdSec Traffic Blocking - Final Validation Report

Date: December 15, 2025 Agent: QA_Security Environment: Docker container charon:local


VERDICT: FAIL

Traffic blocking is NOT functional end-to-end.


Test Results Summary

Component Status Details
CrowdSec Process RUNNING PID 324, started manually
LAPI Health HEALTHY Accessible at http://127.0.0.1:8085
Bouncer Registration REGISTERED caddy-bouncer active, last pull at 20:06:01Z
Bouncer API Connectivity CONNECTED Bouncer successfully querying LAPI
CrowdSec App Config CONFIGURED API key set, ticker_interval: 10s
Decision Creation SUCCESS Test IP 203.0.113.99 banned for 15m
BLOCKING TEST FAIL Banned IP returned HTTP 200 instead of 403
Normal Traffic PASS Non-banned traffic returns 200 OK
Pre-commit PASS All checks passed, 85.1% coverage

Critical Issue: HTTP Handler Middleware Not Applied

Problem

While the CrowdSec bouncer is successfully:

  • Running and connected to LAPI
  • Fetching decisions from LAPI
  • Registered with valid API key

The Caddy HTTP handler middleware is not applied to routes, so blocking decisions are not enforced on incoming traffic.

Evidence

1. CrowdSec LAPI Running and Healthy

$ docker exec charon ps aux | grep crowdsec
324 root      0:01 /usr/local/bin/crowdsec -c /app/data/crowdsec/config/config.yaml

$ docker exec charon sh -c 'cd /app/data/crowdsec && /usr/local/bin/cscli lapi status'
Trying to authenticate with username "844aa6ea34104e829b80a8b9f459b4d9QqsifNBhWtcwmq1s" on http://127.0.0.1:8085/
You can successfully interact with Local API (LAPI)

2. Bouncer Registered and Active

$ docker exec charon sh -c 'cd /app/data/crowdsec && /usr/local/bin/cscli bouncers list'
---------------------------------------------------------------------------------------------
 Name           IP Address  Valid  Last API pull         Type              Version  Auth Type
---------------------------------------------------------------------------------------------
 caddy-bouncer  127.0.0.1   ✔️     2025-12-15T20:06:01Z  caddy-cs-bouncer  v0.9.2   api-key
---------------------------------------------------------------------------------------------

3. Decision Created Successfully

$ docker exec charon sh -c 'cd /app/data/crowdsec && /usr/local/bin/cscli decisions add --ip 203.0.113.99 --duration 15m --reason "FINAL QA VALIDATION TEST"'
level=info msg="Decision successfully added"

$ docker exec charon sh -c 'cd /app/data/crowdsec && /usr/local/bin/cscli decisions list' | grep 203.0.113.99
| 1  | cscli  | Ip:203.0.113.99 | FINAL QA VALIDATION TEST | ban    |         |    | 1      | 14m54s     | 1        |

4. BLOCKING TEST FAILED - Traffic NOT Blocked

$ curl -H "X-Forwarded-For: 203.0.113.99" http://localhost:8080/ -v
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.5.0
> Accept: */*
> X-Forwarded-For: 203.0.113.99
>
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Content-Length: 687
< Content-Type: text/html; charset=utf-8
< Last-Modified: Mon, 15 Dec 2025 17:46:43 GMT
< Date: Mon, 15 Dec 2025 20:05:59 GMT

Expected: HTTP 403 Forbidden Actual: HTTP 200 OK Result: FAIL

5. Caddy HTTP Routes Missing CrowdSec Handler

$ docker exec charon curl -s http://localhost:2019/config/apps/http/servers | jq '.[].routes[0].handle'
[
  {
    "handler": "rewrite",
    "uri": "/unknown.html"
  },
  {
    "handler": "file_server",
    "root": "/app/frontend/dist"
  }
]

No crowdsec handler present in the middleware chain.

6. CrowdSec Headers

No X-Crowdsec-* headers were present in the response, confirming the middleware is not processing requests.


Root Cause Analysis

Configuration Gap

  1. CrowdSec App Level: Configured with API key and URL
  2. HTTP Handler Level: NOT configured - Missing from route middleware chain

The Caddy server has the CrowdSec bouncer module loaded:

$ docker exec charon caddy list-modules | grep crowdsec
admin.api.crowdsec
crowdsec
http.handlers.crowdsec
layer4.matchers.crowdsec

But the http.handlers.crowdsec is not applied to any routes in the current configuration.

Why This Happened

Looking at the application logs:

{"bin_path":"/usr/local/bin/crowdsec","data_dir":"/app/data/crowdsec","level":"info","msg":"CrowdSec reconciliation: starting startup check","time":"2025-12-15T19:59:33Z"}
{"db_mode":"disabled","level":"info","msg":"CrowdSec reconciliation skipped: both SecurityConfig and Settings indicate disabled","setting_enabled":false,"time":"2025-12-15T19:59:33Z"}

And later:

Initializing CrowdSec configuration...
CrowdSec configuration initialized. Agent lifecycle is GUI-controlled.

The system initialized CrowdSec configuration but did NOT auto-start it or configure Caddy routes because:

  • The reconciliation logic checked both SecurityConfig and Settings tables
  • Even though I manually set crowd_sec_mode='local' and enabled=1 in the database, the startup check at 19:59:33 found them disabled
  • The system then initialized configs but left "Agent lifecycle GUI-controlled"
  • Manual start of CrowdSec LAPI succeeded, but Caddy route configuration was never updated

What Works

CrowdSec Core Components:

  • LAPI running and healthy
  • Bouncer registered and polling decisions
  • Decision management (add/delete/list) working
  • cscli commands functional
  • Database integration working
  • Configuration files properly structured

Infrastructure:

  • Backend tests: 100% pass
  • Code coverage: 85.1% (meets 85% requirement)
  • Pre-commit hooks: All passed
  • Container build: Successful
  • Caddy admin API: Accessible and responsive

What Doesn't Work

Traffic Enforcement:

  • HTTP requests from banned IPs are not blocked
  • CrowdSec middleware not in Caddy route handler chain
  • No automatic configuration of Caddy routes when CrowdSec is enabled

Auto-Start Logic:

  • CrowdSec does not auto-start when database is configured to mode=local, enabled=true
  • Reconciliation logic may have race condition or query timing issue
  • Manual intervention required to start LAPI process

Production Readiness: NO

Blockers

  1. Critical: Traffic blocking does not work - primary security feature non-functional
  2. High: Auto-start logic unreliable - requires manual intervention
  3. High: Caddy route configuration not synchronized with CrowdSec state

Required Fixes

1. Fix Caddy Route Configuration (CRITICAL)

File: backend/internal/caddy/manager.go or similar Caddy config generator

Action Required: When CrowdSec is enabled, the Caddy configuration builder must inject the crowdsec HTTP handler into the route middleware chain BEFORE other handlers.

Expected Structure:

{
  "handle": [
    {
      "handler": "crowdsec",
      "trusted_proxies_raw": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.1/32", "::1/128"]
    },
    {
      "handler": "rewrite",
      "uri": "/unknown.html"
    },
    {
      "handler": "file_server",
      "root": "/app/frontend/dist"
    }
  ]
}

The trusted_proxies_raw field must be set at the HTTP handler level (not app level).

2. Fix Auto-Start Logic (HIGH)

File: backend/internal/services/crowdsec_startup.go

Issues:

  • Line 110-117: The check if cfg.CrowdSecMode != "local" && !crowdSecEnabled is skipping startup even when database shows enabled
  • Possible issue: db.First(&cfg) not finding the manually-created record
  • Consider: The Name field mismatch (code expects "Default Security Config", DB has "default")

Recommended Fix:

// At line 43, ensure proper fallback:
if err := db.First(&cfg).Error; err != nil {
    if err == gorm.ErrRecordNotFound {
        // Try finding by uuid='default' as fallback
        if err := db.Where("uuid = ?", "default").First(&cfg).Error; err != nil {
            // Then proceed with auto-initialization logic
            // ...
        }
    }
}

3. Add Integration Test for End-to-End Blocking

File: scripts/crowdsec_blocking_integration.sh (new)

Test Steps:

  1. Enable CrowdSec in DB
  2. Restart container
  3. Verify LAPI running
  4. Verify bouncer registered
  5. Add ban decision
  6. Test traffic with banned IP → Assert 403
  7. Test normal traffic → Assert 200
  8. Cleanup

This test must be added to CI/CD and must FAIL if traffic is not blocked.


Recommendation

DO NOT DEPLOY

The CrowdSec feature is non-functional for its primary purpose: blocking traffic. While all the supporting infrastructure works correctly (LAPI, bouncer registration, decision management), the absence of HTTP middleware enforcement makes this a critical security feature gap.

Next Steps (Priority Order)

  1. IMMEDIATE (P0): Fix Caddy route handler injection in caddy/manager.go

    • Add crowdsec handler to route middleware chain
    • Include trusted_proxies_raw configuration
    • Reload Caddy config when CrowdSec is enabled/disabled
  2. HIGH (P1): Fix CrowdSec auto-start reconciliation logic

    • Debug why db.First(&cfg) returns 0 rows despite data existing
    • Fix query or add fallback to uuid lookup
    • Ensure consistent startup behavior
  3. HIGH (P1): Add blocking integration test

    • Create crowdsec_blocking_integration.sh
    • Add to CI pipeline
    • Must verify actual 403 responses
  4. MEDIUM (P2): Add automatic bouncer registration

    • When CrowdSec starts, auto-register bouncer if not exists
    • Update Caddy config with generated API key
    • Eliminate manual registration step
  5. LOW (P3): Add admin UI controls

    • Start/Stop CrowdSec buttons
    • Bouncer status display
    • Decision management interface

Test Environment Details

Container Image: charon:local Build Date: December 15, 2025 Caddy Version: (with crowdsec module v0.9.2) CrowdSec Version: LAPI running, cscli available Database: SQLite at /app/data/charon.db Host OS: Linux


Files Modified During Testing

  • data/charon.db - Added security_configs and settings entries
  • Caddy live config - Added apps.crowdsec configuration via admin API

Note: These changes are ephemeral in the container and not persisted in the repository.


Conclusion

CrowdSec infrastructure is 80% complete but missing the critical 20% - actual traffic enforcement. The foundation is solid:

  • LAPI works
  • Bouncer communicates
  • Decisions are managed correctly
  • Database integration works
  • Code quality is high (85% coverage)

However, without the HTTP handler middleware properly configured, zero traffic is being blocked, making the feature unusable in production.

Estimated effort to fix: 4-8 hours

  1. Add HTTP handler injection logic (2-4h)
  2. Fix auto-start logic (1-2h)
  3. Add integration test (1-2h)
  4. Verify end-to-end (1h)

Report Author: QA_Security Agent Report Status: FINAL Next Action: Development team to implement fixes per recommendations above