- DetectionOnly mode: add SecAction to set anomaly score thresholds to
9999999 so rule 949110/980130 never fires; works around coraza-caddy
bug where is_interrupted=true still causes a 403 in detection mode
- Switch SecAuditEngine back to On (from RelevantOnly) so DetectionOnly
hits are captured, now safe because body parts are excluded
- SecAuditLogParts: ABIJDEFHZ → ABFHZ, dropping request body (I),
multipart files (J), intermediate response headers (D), and response
body (E) — prevents multi-MB payloads being written to audit log
- Parser: store both blocked and detected events; filter on rule matched
OR is_interrupted instead of is_interrupted only
- Add blocked column to waf_events (migration 0014); existing rows
default to blocked=true
- WAF Events UI: Blocked/Detected chip in table and drawer header
- Fix misleading help text that said to use Detection Only to observe
traffic before blocking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Coraza does not write matched rules to the audit log (known upstream
bug). Rule details are logged by Caddy's http.handlers.waf logger.
Two changes:
1. caddy.ts: Always configure a dedicated Caddy log sink that writes
http.handlers.waf logger output to /logs/waf-rules.log as JSON.
2. waf-log-parser.ts: Before parsing the audit log, read the new
waf-rules.log to build a Map<unique_id, RuleInfo>. Each audit log
entry joins against this map via transaction.id to populate
ruleId, ruleMessage, and severity fields. Skips anomaly evaluation
rules (949110/980130) to show the actual detection rule instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Coraza does not populate the messages array in the audit log JSON
(known bug — matched rules appear in Caddy's error log but not in
the audit log's messages field). The transaction.is_interrupted flag
IS correctly set to true for blocked/detected requests.
Changes:
- Filter on tx.is_interrupted instead of entry.messages.length
- Check both top-level and tx.messages for rule info (future compat)
- Fall back to tx.highest_severity when messages missing
- Document the Coraza messages bug in interface comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change SecAuditEngine from On to RelevantOnly so Coraza only writes
audit log entries for transactions that triggered at least one rule.
Previously all requests were logged regardless of matches.
- Add parser-side guard to skip entries with empty messages array as
belt-and-suspenders against any pre-existing clean entries in log.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- buildWafHandler: directives must be string[] not a joined string (coraza-caddy
JSON API requirement); load_owasp_crs is Caddyfile-only and silently ignored in
JSON config — replaced with Include @owasp_crs/... directives
- waf-log-parser: use unix_timestamp (nanoseconds) for precise ts; host header is
headers.host[] (lowercase array); messages[].data.{id,msg,severity} not rule.*
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>