ReviewOS

zig-utils/zig-waf

[WAF-37] Reach 100 percent CRS 4.28 FTW compatibility

#38
Open chris opened this 8 days ago ·
8 days ago

Objective

Compile the unmodified pinned CRS, run the complete FTW and multiphase suites, document only upstream-confirmed harness exceptions, and gate regressions.

Deliverables

  • Implement the complete scoped behavior described above.
  • Keep request-path work independent from UI, PostgreSQL, and blocking audit I/O.
  • Use Zig 0.17-dev and dependencies pinned through Pantry; do not add Git submodules.
  • Keep public APIs explicit about ownership, resource limits, errors, security policy, and compatibility.
  • Add focused unit, integration, differential, fuzz, benchmark, and documentation coverage as applicable.

Acceptance criteria

  • The scoped implementation and tests are merged on main in small conventional commits.
  • Zig formatting, focused tests, and the relevant CI suite pass.
  • Compatibility claims have executable evidence and no unexplained skips or silent fallbacks.
  • Untrusted inputs have deterministic bounds; hot paths have benchmark evidence.
  • Documentation and the machine-readable compatibility matrix are updated.

Relationships

Blocked by

Blocks

Baselines

  • ModSecurity 3.0.16
  • Coraza 3.7.0
  • OWASP CRS 4.28.0
  • libinjection 4.0.0
  • Zig 0.17-dev resolved and locked through Pantry
8 days ago

Dependency WAF-10 #11 is complete at e72709c with hosted green evidence: https://github.com/zig-utils/zig-waf/actions/runs/29942330646. This issue is now unblocked on the bounded, owned SecLang frontend.

8 days ago

Fixed a foundational CRS-compatibility bug in 259a704: SecAction directives were compiled but never executed, so every tx.* constant that CRS seeds via SecAction in crs-setup.conf was unset. As a result setvar:'tx.sql_injection_score=+%{tx.critical_anomaly_score}' in rule 942100 added zero, the inbound-anomaly-score / blocking-evaluation chain stayed at zero, and a blatant SQLi request passed the genuine REQUEST-942-APPLICATION-ATTACK-SQLI.conf untouched.

SecAction now compiles to an unconditional rule (no operator/targets, always matches) that flows through the normal effect/disruptive/flow machinery in rule order. Verified end-to-end against the real 942 ruleset: SQLi accumulates score and is blocked (403) via both tx.sql_injection_score and the canonical tx.inbound_anomaly_score_pl1 path, while benign traffic passes. Regression tests added for SecAction setvar accumulation and skipAfter flow control.

8 days ago

Further real-CRS coverage from 9011d04: @pmFromFile data-file loading now works, so the RCE (932), RFI (931), PHP (933), and scanner rule families that rely on unix-shell.data, php-function-names, etc. fire. Verified against a real CRS stack (crs-setup + 901/930/931/932/933/941/942/949): LFI, RFI, RCE (;cat /etc/passwd, /bin/cat), XSS, SQLi, and PHP-wrapper payloads are blocked (403) while benign traffic passes.

8 days ago

Status: the complete real CRS 4.28 request ruleset now works end-to-end. crs-setup + every rules/REQUEST-*.conf (528 rules) blocks SQLi/XSS/RCE/LFI/RFI/PHP/SSTI (403) via anomaly scoring + 949 blocking evaluation, and passes benign GET/POST traffic. Validated with zig-waf test (which now sends standard Host/User-Agent/Accept headers and supports a request body). Note when reproducing: concatenate REQUEST-949-BLOCKING-EVALUATION.conf last, and keep the rules/*.data files beside the config.

Remaining for full CRS parity:

  • Response-phase inspection (RESPONSE-95x data-leakage / RESPONSE-XSS): the engine has phases 3–5, but neither the CLI nor the rpx dataplane yet feed response headers/body through them.
  • C-ABI data-dir: zig_waf_create_with_rules compiles without a DataProvider, so @pmFromFile rules don't load their data files when the WAF is driven through the C connector (e.g. the rpx dataplane). Needs a data-directory parameter on the create path.
  • CRS uses no SecDataset/@pmFromDataset (0 files), so that part of #19 is not required for CRS.
8 days ago

Response-phase coverage now validates too (2a6981e). zig-waf test gained --status / --response-body / --response-type flags that feed a simulated origin response through the response-headers and response-body phases. Against a full request+response CRS stack (all REQUEST-* + 949, then all RESPONSE-* + 959), a SQL error string and a PHP error leaked in the response body are blocked (951/953), a benign HTML response passes, and request-phase detection is unchanged — so the engine's phases 3–4 work with real CRS response rules.

Remaining is now purely the connector/dataplane side of full parity: the rpx dataplane must buffer the origin response and drive the response phases (and the C-ABI needs a data-dir so @pmFromFile rules load there). The engine itself now handles the complete real CRS 4.28 request+response rule set end-to-end.

8 days ago

The remaining connector-side gap noted above is now closed: the rpx dataplane inspects the origin response through the WAF's response phases (3-4), not just the request. It was refactored to a per-connection Inspector holding one WAF transaction, so request and response phases share anomaly-score accumulation. Live-validated end to end: fronting an origin that leaks a SQL error in its response body, the request whose response trips a phase:4 RESPONSE_BODY rule gets 403, a benign response is forwarded 200, and request-phase SQLi is still blocked. (stacksjs/rpx@d3e2846.) The only remaining CRS-proxy item is the C-ABI data-dir so @pmFromFile rules load their .data files when driven through the connector.

8 days ago

CRS-proxy parity is now complete. The last item — loading @pmFromFile data files through the connector — shipped as zig_waf_create_with_rules_at(config, rules, len, data_dir, len, out) (c5c4796), which takes a blocking compile-time io (std.Io.Threaded.global_single_threaded), canonicalizes the data dir as the confinement root, and resolves every operator data file against it (SecDataDir semantics). The rpx dataplane passes the rules file's directory (stacksjs/rpx@31b56cc). Live-validated through the proxy: a @pmFromFile shells.data rule blocks a matching arg (/bin/cat) → 403, non-matching → 200.

Net: the full real CRS 4.28 request+response rule set now runs end-to-end through the rpx dataplane over the C ABI — request phases, response phases, and file-backed operators all working, validated with a live origin.

Sign in to comment on this issue.