ReviewOS

zig-utils/zig-waf

[WAF-14] Implement metadata and non-disruptive actions

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

Objective

Compile and execute the stable ModSecurity 3.0.16 / Coraza 3.7.0 union of rule metadata and non-disruptive actions without reparsing action strings on the request path. Metadata becomes immutable typed plan state; a successful rule/operator match produces a bounded effect batch that mutates only the isolated transaction and its configured persistent session. Logging actions produce immutable event intent and never perform synchronous audit, telemetry, UI, or database I/O.

Behavioral contract

Typed metadata

  • Compile id, rev, msg, logdata, repeatable tag, severity, maturity, accuracy, and ver into typed immutable rule metadata with source spans.
  • Preserve unsigned 64-bit external IDs and the WAF-11/WAF-13 uniqueness/chain invariants. Chain members inherit head metadata according to upstream semantics without duplicating identity.
  • Accept numeric severities 0–7 and case-insensitive names EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, and DEBUG; retain the canonical numeric value.
  • Validate bounded numeric maturity/accuracy values and reject malformed, duplicate-ID, empty-required, or out-of-range metadata with stable source diagnostics. Repeated singleton metadata uses the last effective value, matching ModSecurity; tags remain ordered and repeatable.
  • Preserve ordered duplicate tags. Compile macro-bearing msg, logdata, and tag values once; expand against the post-action transaction state when the match record is finalized.
  • Populate a read-only per-match RULE collection (id, rev, msg, logdata, severity, maturity, accuracy, ver) so %{RULE.*} and subsequent actions observe the active rule.

Match/capture input and event intent

  • Expose a bounded MatchContext/equivalent input carrying the matched variable name/value, source range, and up to ten operator capture ranges.
  • capture clears prior TX.0–TX.9 state, then copies capture 0 and present subgroups in order. Missing optional groups remain absent; out-of-range or overlapping byte slices cannot escape the supplied matched value.
  • A completed action application returns immutable/bounded match intent containing rule identity, expanded message/logdata/tags, severity, matched-variable evidence, log/audit decisions, and action-effect counts.
  • log/nolog and auditlog/noauditlog are paired last-effective-value families already materialized by WAF-13. They set event intent only; WAF-29/WAF-30 consume it asynchronously.
  • nolog does not silently erase explicit audit intent, and noauditlog does not suppress debug/match state required by policy. Exact ModSecurity/Coraza union choices are differential fixtures.

Environment and collection mutation

  • setenv:NAME=VALUE expands both bounded syntax and value into the transaction-local ENV collection. It never mutates process-global environment state; this is an intentional thread-safety/security hardening over upstream implementations.
  • setvar supports case-insensitive TX, IP, SESSION, USER, GLOBAL, and RESOURCE collection names, macro-bearing keys/values, set-to-one, assignment, deletion, and signed add/subtract.
  • Arithmetic follows the already-pinned ModSecurity numeric-prefix/zero-on-invalid compatibility contract, uses checked signed 64-bit math, and reports overflow without corrupting existing state.
  • initcol supports the stable persistent namespaces and macro-expanded collection keys. setsid, setuid, and setrsc initialize/bind the corresponding collection and compatibility scalar through the same code path.
  • expirevar parses COLLECTION.key=seconds, requires an initialized persistent collection, uses checked timestamps, and stages expiry through the configured persistence session.
  • deprecatevar parses COLLECTION.key=amount/period, validates positive bounded operands, performs deterministic elapsed-time decay using the injected monotonic/real clock contract, and updates the persistent value/timestamp without request-path sleeps.
  • Persistent actions honor the WAF-09 explicit backend capability and fail-open/fail-closed policy. Production behavior never substitutes an in-memory or SQLite backend silently.

Ordering, chains, and atomicity

  • Execute effective non-disruptive actions in source order using the WAF-13 post-overlay action range.
  • Metadata is available before action expansion; logdata and final event strings expand after collection mutations, matching Coraza's post-action requirement and ModSecurity-compatible macro visibility.
  • Actions attached to an individual chain member execute only when that member matches; final chain-head event/disruptive handling occurs only after the complete chain succeeds. WAF-15 consumes the resulting flow state.
  • Parse, classify, validate, and compile every supported action while building the candidate plan. Published request-path execution performs no action parsing, regex compilation, filesystem/network/database lookup, or unbounded allocation.
  • Expansion and semantic validation complete into a bounded effect batch before local mutation. A failed preflight leaves transaction state unchanged. Commit-time persistent failures follow explicit configured policy and remain observable.
  • Every count and byte total is bounded independently: captures, tags, metadata bytes, macro output, effect count, environment entries, collection writes, and event-intent storage.

Public and plan interfaces

  • Typed RuleMetadata and compiled non-disruptive action/effect descriptors addressable by RuleId/ActionId with source spans.
  • MatchContext uses borrowed input with explicit lifetime; finalized match/event intent owns bounded copies needed after connector buffers move.
  • Transaction.applyMatchedRule (or equivalent) applies one matched rule/member and returns typed outcome/errors without performing disruptive flow or blocking I/O.
  • Read-only accessors expose active rule metadata, event intent, capture values, environment values, and applied-effect evidence.
  • Stable diagnostics distinguish invalid metadata, unknown severity, invalid collection mutation, uninitialized persistent collection, invalid TTL/decay, capture bounds, capacity, macro, arithmetic, and backend failures.
  • Compiler ABI and public feature discovery change when the typed plan/runtime contract changes.

Deliverables

  • Typed metadata/action IR, validation, diagnostics, fingerprinting, and limits.
  • Transaction-local RULE metadata projection and bounded match context.
  • Capture semantics for TX.0–TX.9.
  • Log/audit intent and owned expanded match-event metadata.
  • Transaction-local setenv without process-global mutation.
  • setvar set/delete/add/subtract for the TX namespace.
  • setvar set/delete/add/subtract for persistent namespaces.
  • initcol, setsid, setuid, and setrsc persistent binding.
  • expirevar and deprecatevar timestamped persistence behavior.
  • Ordered chain-member action application and bounded effect preflight/commit.
  • Allocation-failure, hostile-input, differential, fuzz, corpus, and hot-path benchmark coverage.
  • Documentation and machine-readable compatibility evidence.

Acceptance criteria

  • Every scoped action has positive, negative, ordering, macro, allocation-failure, and resource-limit coverage.
  • Differential fixtures pin intentional stable-union behavior to exact ModSecurity 3.0.16 and Coraza 3.7.0 revisions; CRS 4.28 action combinations compile with zero unexplained exclusions.
  • Capture tests cover 0–9, missing groups, empty captures, stale-value clearing, hostile ranges, and binary matched values.
  • Mutation tests cover dynamic keys, empty values, deletion, signed arithmetic, overflow, missing initialization, expiry, decay, fail-open/fail-closed, and chain-member timing.
  • No test observes process-global environment mutation from setenv.
  • Failed preflight leaves local/persistent staged state unchanged; backend failures are typed and observable.
  • Request-path benchmarks report actions/s, p50/p95/p99 application latency, allocations, bytes copied, and effect/event storage for no-op metadata, CRS-style TX scoring, capture, and persistent action mixes.
  • Focused tests, full zig build test, zig build check, corpus/fuzz/benchmark gates, formatting, and hosted CI pass on the exact closing SHA.
  • Work lands directly on main in small conventional commits with issue/commit/run links.

Relationships

Baselines and upstream references

  • ModSecurity 3.0.16 commit 7ea9fefbe0ba409d8733b4d682c8c4c059cd028d: src/actions/{capture,set_var,set_env,init_col,expire_var,severity,log_data}.* and action regression cases.
  • Coraza 3.7.0 commit 27069d06c896be74b77b9a6c0b539a0cbfaca360: internal/actions/{capture,setvar,setenv,initcol,expirevar,severity,logdata}* and chain action tests.
  • OWASP CRS 4.28.0 commit 55b09f5acfd16413e7b31041100711ceb7adc89c: TX scoring, capture, metadata, and persistent-action corpus.
  • Zig 0.17-dev is locked through Pantry; no Git submodules or runtime PCRE/TLS/database substitutions.
9 days ago

Dependency update: WAF-11 #12 is complete at 19b64bc with exact hosted CI green. This issue can now consume the immutable typed IR, stable diagnostics, prefilters/macro programs, fingerprints, shared payloads, and explicit WAF plan ownership contract.

9 days ago

WAF-13 #14 is complete at 7d903f0 with exact-SHA CI run 29954254127 green. WAF-14 can build metadata and non-disruptive runtime semantics on the immutable effective action ranges and compiled macro programs now available.

9 days ago

Published b92cf83 (feat(engine): parse non-disruptive action effects) and df78909 (feat(engine): compile typed rule metadata) on main. The immutable plan now stores normalized revision/message/logdata/version text and macros, numeric severity/maturity/accuracy, ordered tags, post-WAF-13 update semantics, independent tag limits, fingerprint coverage, and fuzz invariants under compiler ABI 4. The five-root corpus exposed and now pins ModSecurity last-value semantics for repeated singleton metadata; 58 files/925 directives/761 rules, ordered CRS, 173 tests, zig build check, and 10,000 plan-fuzz iterations pass locally.

9 days ago

Published 114d111 (feat(engine): compile non-disruptive effect plans) on main. Effective default+explicit action ranges now compile into bounded typed descriptors for capture, log/nolog, auditlog/noauditlog, setenv, setvar, initcol, expirevar, deprecatevar, setuid, setsid, and setrsc. Key/value/operand/TTL/decay subexpressions have independent compiled macros; literal TTL/decay values are validated before publication; WAF-PLAN-0118, independent effect limits, fingerprint/equality coverage, and fuzz invariants are executable. Local gates: 176/176 tests, check, five-root corpus, ordered CRS, and 10,000 plan-fuzz iterations.

9 days ago

Published 22a2493 (feat(engine): replace transaction collections atomically), 5d7c63d (feat(engine): project matched rule captures), and d05aa4d (feat(engine): resolve plan macro variables) on main. RULE projection now inherits chain-head metadata; borrowed MatchContext and capture ranges are independently bounded; TX.0–TX.9 replacement validates all binary-safe ranges before one logical commit and clears stale/missing groups. Collection replacement preserves visible state across injected allocation failures. Macro IR now resolves scalar/keyed/unkeyed collection enums at compile time under ABI 5, removing runtime name parsing. Local gates: 181/181 tests, five-root corpus, and 10,000 plan-fuzz iterations.

9 days ago

Published the atomic local-effect tranche on main:

  • 1917147 adds a cross-collection replaceKeyGroups commit primitive with invalid-batch and exhaustive allocation-failure preservation tests.
  • 2cdb4e5 adds bounded, source-ordered local rule-effect preflight and one-shot commit for RULE projection, captures, log/nolog, auditlog/noauditlog, transaction-local ENV, and TX setvar. Staged macros observe earlier effects; checked arithmetic overflow leaves RULE/TX/ENV unchanged. setenv is proven not to touch process-global state.

Local gates: pinned Pantry Zig 0.17-dev; zig build test --summary all (186/186); zig build check; formatting and git diff --check. Hosted runs: 1917147, 2cdb4e5. Persistent effects remain deliberately staged/countable and are next; no fallback backend or partial local commit was introduced.

9 days ago

Published the rollback-safe persistence tranche on main:

  • ce5b5f1 adds persistence-session checkpoints that restore both pre-existing mutation-log lengths and newly created bindings.
  • 2485ba2 adds allocation-complete scalar batch preparation followed by a non-fallible commit phase.
  • 54f62bb adds checked persistent subtraction across the full signed operand domain and advances the persistence callback ABI to v2.
  • bc1cb1f adds Transaction.applyMatchedRule for ordered initcol, setsid, setuid, setrsc, persistent setvar, and expirevar, with staged macro visibility, atomic collection/scalar publication, explicit fail-policy plumbing, and lifecycle-bound backend flush.

End-to-end tests bind and mutate IP/SESSION/USER/RESOURCE, verify exact expiry, and prove a later local arithmetic error rolls back every persistent binding/mutation. Local gates: pinned Pantry Zig 0.17-dev; zig build test --summary all (192/192); zig build check; format/diff checks. deprecatevar, owned event intent, chain finalization, differential/fuzz/perf evidence, and exact-SHA hosted qualification remain open.

9 days ago

Published deterministic persistent decay in ffc4ccc. deprecatevar now stages an ABI-v3 revisioned mutation carrying amount, period, and injected time; the backend recomputes elapsed whole periods against the authoritative value/update timestamp on commit and conflict retry. Values clamp at zero, future/partial periods do not decay, removal resets the clock, expiry remains independent, and checkpoint rollback truncates both mutations and append-only clock state. The transaction projects the same result for subsequent source-ordered macros.

Coverage includes initial timestamp transport, 25s/35s whole-period behavior, same-period idempotence, rollback to the prior clock, remove/recreate reset, backend persistence, and the full deprecatevar action path alongside expiry. Local gates: pinned Pantry Zig 0.17-dev; 193/193 tests; zig build check; formatting/diff clean.

9 days ago

Published bounded owned event intent in 398422a. Each successful application now returns a stable MatchIntentId; transaction accessors expose owned rule/head identity, severity, post-action expanded message/logdata/ordered tags, matched-name/value/source evidence, log/audit decisions, capture/effect counts, and pending-persistence evidence. Count and aggregate byte limits reject before RULE/TX/ENV/scalar publication, and deinit releases every owned payload.

The same tranche stages DURATION into the atomic scalar batch, eliminating the prior possibility that macro expansion changed timing state before a later preflight error. Tests prove post-action TX expansion (rule 6, score=5, score-6), ownership, evidence, log-vs-audit independence, count/byte limits, and zero intent/local publication on failure. Local gates: pinned Pantry Zig 0.17-dev; 194/194 tests; zig build check; formatting/diff clean.

9 days ago

Published complete-chain application in 6d0447b. Transaction.applyMatchedChain now requires the exact compiled head-to-tail chain_next sequence, validates every borrowed match context before effects, applies member effects in source order through one shared shadow batch, and emits exactly one chain-head event intent using the final matched evidence. Single-rule APIs reject any partial chained rule.

Tests cover missing-tail and out-of-order rejection with zero publication, cross-member macro visibility (head-middle-tail), tail capture/evidence, independent final log/audit decisions, one expanded head intent, and rollback of earlier head effects when the final member overflows. Local gates: pinned Pantry Zig 0.17-dev; 195/195 tests; zig build check; formatting/diff clean.

9 days ago

Published the WAF-14 qualification and evidence tranche:

  • c918f54 adds exhaustive application allocation-failure preservation, two exact-revision upstream-mapped fixtures, embedded ABI/evidence assertions, a reusable hostile-input runtime oracle, the deterministic fuzz-actions gate, and bench-actions with metadata, TX scoring, capture, and persistent-staging scenarios.
  • 28380d4 records the verified feature, operator/runtime contract, reproduction commands, measured limits, and hosted CI fuzz/benchmark gates.

Qualification observed locally on pinned Pantry Zig 0.17-dev:

  • 199/199 unit and C smoke tests.
  • 10,000 action fuzz cases, seed 13907095936298285211, max input 8,192 bytes.
  • Five-root corpus: 58 files, 803,184 bytes, 925 directives, 761 rules, zero unexplained exclusions.
  • Ordered CRS: 30 files, 794 directives, 695 rules, fingerprint 86d51eedfe51c77b85de8ea9b453bc92063208d6b77733ea5f2f1ee64781856b.
  • ReleaseFast p99: metadata 959ns; TX scoring 2,417ns; capture 7,208ns; persistent staging 3,167ns (5,000 applications each).

All deliverable checkboxes are now complete. I am waiting for the exact closing SHA hosted run and will inspect logs rather than close on local evidence alone.

9 days ago

Closing qualification is green on exact implementation SHA 28380d45c92a01a9ed48a7b63784ae26a59a5bab: hosted CI run https://github.com/zig-utils/zig-waf/actions/runs/29959702219 completed successfully. The closing SHA includes all checked deliverables, 199 tests, full deterministic action fuzzing, pinned five-root and CRS corpus evidence, ReleaseFast action benchmarks, compatibility metadata, and documentation. WAF-15 continues from this atomic commit boundary.

Sign in to comment on this issue.