Episode 13: tune the OWASP CRS with per-route exclusions

Measured follow-up to the AppSec section. Virtual patching blocks 7 of 41
probes with near-zero false positives; the OWASP CRS raises that to 27 of 41
but, at default paranoia, blocks six legitimate workflows (git push, Forgejo
issues and file contents, Outline documents, ntfy publish, n8n webhooks).

crowdsec/appsec-rules/crs-route-exclusions.yaml disables the SecLang/Coraza
engine (ctl:ruleEngine=Off) for exactly those application routes, matched by
host + URI, so virtual patching still runs there. crowdsec/appsec-configs/
appsec-tuned.yaml wires it in before the CRS rule. Measured with tuning: zero
false positives, still 27 of 41 blocked on non-excluded routes, about +2 ms
per request, ~15-40 MB more RAM.

Trap: RemoveInBandRuleByName in a pre_eval hook does not disable CRS (it is
SecLang, not a native rule); ctl:ruleEngine in phase 1 is the working lever,
and the exclusion rule must be listed before the CRS rule.

The stack runs vpatch+generic by default (zero false positives, no per-app
tuning). The CRS recipe ships as a documented opt-in in crowdsec/README.md.
This commit is contained in:
Maximilian Grimm 2026-09-16 20:46:16 +02:00
parent 88670f028f
commit 26a7da0495
3 changed files with 108 additions and 0 deletions

View file

@ -0,0 +1,15 @@
name: selfhostedops/appsec-tuned
default_remediation: ban
# ep13: vpatch + generic (nativ) plus OWASP CRS (SecLang). Eine lokale
# SecLang-Regel schaltet die CRS-Engine je legitimer Route ab (ctl:ruleEngine),
# die native vpatch-Abdeckung bleibt dort erhalten. Die Exclusion-Regel muss
# VOR crowdsecurity/crs stehen, damit ctl in Phase 1 greift.
inband_rules:
- crowdsecurity/base-config
- selfhostedops/crs-route-exclusions
- crowdsecurity/crs
- crowdsecurity/vpatch-*
- crowdsecurity/generic-*
outofband_rules:
- crowdsecurity/experimental-*
- crowdsecurity/appsec-generic-test