Episode 14: an agent reads the WAF blocks, a script beats it, a human merges
ops-readonly gets two read-only questions, waf_blocked and waf_rule_info. The rule ids per blocked request are in CrowdSec's alerts, not in its log, and alerts need machine credentials that can also write, so waf-export.py runs on the host and the container gets one more read-only mount. Workflow G lets a local 3B model draft CRS route exclusions as a Forgejo issue. The model never writes rule syntax, a code node checks every field against the tool output, the only write permission is an issue in one repo, trusted sources live in the workflow and the trigger needs a token. waf-propose.py does the same without a model: 5 of 5 false positives, no exclusion for an attack, 0.03 s. The model proposed an exclusion for nearly every attack it saw. Numbers and the traps we hit are in HARDENING.md.
This commit is contained in:
parent
696f3e311d
commit
3edef38ba3
13 changed files with 987 additions and 1 deletions
|
|
@ -193,6 +193,34 @@ Traps found while tuning:
|
|||
- Every new app or new public path needs its own exclusion. A missed one blocks
|
||||
a real workflow, and it fails only after login, so it is easy to miss.
|
||||
|
||||
### Episode 14: narrower exclusions, and who drafts them
|
||||
|
||||
`appsec-rules/crs-route-exclusions-by-rule.yaml` is the finer variant: per
|
||||
route it removes only the rule ids that fired on legitimate requests there,
|
||||
instead of the blocking rule 949110. Measured on the same box: all false
|
||||
positives gone (including `git push`, which hides behind the failing clone and
|
||||
never shows up in the log on its own), 18 of 18 attacks still 403, virtual
|
||||
patching alive, and SQL injection on the exempted git API still 403, because
|
||||
no SQLi rule had fired on that route. To use it, list
|
||||
`selfhostedops/crs-route-exclusions-by-rule` in `appsec-tuned.yaml` instead of
|
||||
`selfhostedops/crs-route-exclusions`.
|
||||
|
||||
The rule ids per blocked request are not in `crowdsec.log`. They are in the
|
||||
alerts (`cscli alerts list -o json`, kind `waf`). `ops-readonly/waf-export.py`
|
||||
dumps them for the read-only tool, `ops-readonly/waf-propose.py` turns them
|
||||
into a proposal, and workflow G (`automation/README.md`) lets a local model
|
||||
try the same. Read `HARDENING.md` (episode 14) before you let the model do it.
|
||||
|
||||
Two things to know before you test this on your own box:
|
||||
|
||||
- With the CRS on and no exclusions, **four false positives within two seconds
|
||||
banned the legitimate user for four hours** (`crowdsecurity/appsec-native`
|
||||
plus `LePresidente/http-generic-403-bf`). Whitelist your own test source
|
||||
first.
|
||||
- Do not run `cscli alerts delete` right after `cscli decisions delete`. The
|
||||
decision is gone from the database before the bouncers have seen it go, and
|
||||
the IP stays blocked until you restart them.
|
||||
|
||||
**What this repo runs by default: vpatch + generic** (the `appsec-default`
|
||||
config, zero false positives, no per-app tuning). The CRS files above are the
|
||||
measured opt-in for anyone who wants generic coverage and will budget the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue