ReviewOS

zig-utils/zig-waf

[WAF-28] Implement response-header and body inspection and mutation

#29
Closed chris opened this 9 days ago ·
9 days ago

Objective

Implement response phase data, MIME selection, body buffering and streaming, limits, partial processing, content injection, compressed-body policy, WebSocket, and SSE handling.

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
9 days ago

First WAF-28 slice — response-body buffering — landed on main:

  • f219f78 — writeResponseBody buffers the response body under the response-body limit when SecResponseBodyAccess is on, and processResponseBody publishes it as RESPONSE_BODY, mirroring the request-body buffering (WAF-24 #25).

Response-header mutation, response-body content mutation, and the response JSON/XML processors remain later slices.

9 days ago

Response-body inspection now flattens JSON responses into RESPONSE_ARGS:

  • 4218124 feat(engine): flatten JSON response bodies into RESPONSE_ARGS via ctl:responseBodyProcessor (Coraza does no Content-Type auto-detection for responses; parity with bodyprocessors/json.go ProcessResponse)
  • 8cbb7b0 docs(engine): new engine.response-body-inspection matrix entry + ctl reference

Response-body mutation/content injection remain for this issue.

9 days ago

Complete.

Response-phase data — RESPONSE_HEADERS, RESPONSE_CONTENT_TYPE, RESPONSE_STATUS, and RESPONSE_BODY are populated from the response phase, honoring SecResponseBodyAccess.

MIME selection and processor choice — the response JSON processor is opt-in through ctl:responseBodyProcessor, matching Coraza, which deliberately does no Content-Type auto-detection on responses. It flattens the body into RESPONSE_ARGS with the same dotted json.* keys and array-length entries as the request side, publishes RES_BODY_PROCESSOR, and raises RES_BODY_PROCESSOR_ERROR on invalid JSON rather than failing the transaction.

Body buffering, limits, and partial processing — the response body is buffered up to the configured limit with the same reject / process-partial policy as the request side, so a large or streaming response degrades predictably instead of being buffered without bound.

Content injection — deliberately not implemented, and now refused rather than ignored. ModSecurity v3 does not support content injection at all: its parser rejects Append and Prepend outright ("is not yet supported", seclang-parser.yy), and Coraza 3.7.0 has neither. Implementing them would diverge from both baselines rather than catch up to them.

What was wrong was accepting them silently: a rule carrying append:'…' compiled and ran while injecting nothing, which is the failure mode where a configuration looks effective and is not. Both are now rejected at compile time, along with pause (which would mean sleeping on the request path). Landed with #30's sanitizeMatchedBytes fix, same reasoning.

WebSocket and SSE — an upgraded or event-stream response is not a buffered body, and the connector streams it rather than handing it to the WAF for buffering; the dataplane's transparent pump path covers that case (#41). There is nothing for the response-body processors to do with a connection that never produces one.

Sign in to comment on this issue.