- Add getTopWafRulesWithHosts() and getWafEventCountries() model queries
- WAF stats API now returns topRules with per-host breakdown and byCountry
- Analytics: replace WAF rules table with bar chart + host chip details
- Analytics: add WAF column (amber) to Top Countries table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WafEvent model: expose rawData field from DB
- DataTable: add optional onRowClick prop with hover cursor
- WafEventsClient: clicking a row opens a right-side drawer showing
all event fields plus the raw Coraza audit JSON (pretty-printed)
Safety: rawData is rendered via JSON.stringify into a <pre> element,
never via dangerouslySetInnerHTML, so attack payloads are displayed
as inert text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DataTable: add overflowX auto to TableContainer + minWidth 600
- WAF events: tighten column widths (Time 150, Host 150, IP 140,
Method 60), add ellipsis+tooltip on Host column, let Rule Message
expand to fill remaining space
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>
Coraza's RelevantOnly mode does not write audit log entries for requests
blocked by the WAF itself (403 responses), so the waf-log-parser had
nothing to parse. Reverting to On so all transactions are logged, and
relying on the parser-side messages[] filter to skip clean requests.
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>
load_owasp_crs: true only merges the embedded coraza-coreruleset
filesystem - it does NOT auto-include rule files. The correct way to
load CRS rules is to explicitly Include them using the @ prefix which
references the embedded FS:
Include @coraza.conf-recommended
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
Without these includes, SecRuleEngine On had no rules to apply and
all requests passed through unblocked (rulesets: null in audit log).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Caddyfile adapter test confirms: load_owasp_crs loads all CRS rules
internally without any Include directives. Include @owasp_crs/... was
wrong — that path is not accessible from SecLang directives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
load_owasp_crs:true mounts the embedded CRS filesystem (@owasp_crs prefix),
but Include @owasp_crs/... directives are still needed to actually load the
rules. Previously we had one or the other — now both are set together.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The coraza-caddy Go struct defines directives as type string, not []string.
Revert to joined string but keep the Include @owasp_crs/... fix for CRS loading.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The enabled switch state was never submitted to the form, so the host
WAF config was always saved as enabled: false regardless of the toggle.
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>
Previously only counted rows in the certificates table (0 for most setups).
Now counts: proxy hosts with certificate_id=NULL (each gets one Caddy ACME cert
via HTTP-01/DNS-01) + imported certs with actual PEM data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SwapHorizIcon→Proxy Hosts, VpnKeyIcon→Access Lists, SecurityIcon→Certificates,
HistoryIcon→Audit Log. Nav now uses the exact same icons as the overview stat
cards so identical sections share identical icons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DnsIcon→RouterIcon, SecurityIcon→LockIcon, ShieldIcon→WorkspacePremiumIcon,
HistoryIcon→AssignmentIcon — all now match the visual weight of Dashboard,
BarChart, and Settings icons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace DeleteOutline→Delete, CheckCircleOutline→CheckCircle, ErrorOutline→Error,
RemoveCircleOutline→RemoveCircle, InfoOutlined→Info across all dashboard components.
Replace custom SVG bar chart in OverviewClient with BarChartIcon.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revert 2-column squircle grid back to original list layout —
same space usage as before, just with squircle-style border radius
on each ListItemButton instead of sharp rectangle edges.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>