zig-waf

Collection variables, targets, and macros

On this page 4

Issue WAF-08 defines the transaction-facing collection model used by SecLang targets and runtime macro expansion. collections.Name is the executable registry for 37 canonical namespaces, including transient request/response data, TX, and the persistent namespace interfaces consumed by WAF-09.

Storage and ownership

Collection keys and values are copied into a transaction-local arena. Repeated values retain insertion order and source origin/offset/length. Entry count, individual key/value size, and physical allocated bytes are independently bounded. Replacement and deletion use tombstones: superseded arena bytes remain charged to the physical limit, so hostile update churn cannot hide memory growth behind a smaller live-byte count.

Headers, ENV, GEO, RULE, TX, and persistent map keys compare ASCII case-insensitively. Argument, cookie, file, multipart, JSON, and XML keys retain case. Map-style set, repeated add, selector-based removal, and paired atomic insertion cover both SecLang actions and parser producers.

Targets and selectors

A target combines a collection, an all/exact/compiled-regex key selector, and the SecLang count marker. Exclusion targets are applied while iterating without allocating a result list. Exact header and map selectors honor the namespace key policy. Regex targets use a ruleset-owned Pantry-pinned zig-regex plus one reusable matcher per worker, so DFA state is amortized without sharing mutable matching state. Runtime exhaustion or an input limit is returned as MatcherLimitExceeded, never converted into a false non-match.

The transaction API phase-gates collections before returning iterators or counts. Request-body, response-header, and response-body namespaces therefore cannot leak future-phase values even if a producer publishes data early.

Runtime macros

Runtime strings compile once into immutable literal, scalar, and collection tokens. Compilation rejects empty, unknown, malformed, unterminated, oversized, and over-tokenized input. Expansion has an independent output bound and reads the current transaction state. It is single-pass, matching the upstream model; expanded bytes are not reparsed as nested macros.

ModSecurity expands a missing value to empty while Coraza preserves the expression text without delimiters. Builder.setMacroMissingPolicy makes this baseline difference explicit; the stable default is ModSecurity-compatible empty expansion.

Downstream producers

This issue owns namespaces, storage, mutation, origins, targets, selectors, counts, exclusions, and macro resolution. Parsing remains deliberately located with the subsystem that can provide correct offsets and diagnostics:

  • WAF-23: URL arguments, headers, and cookies.
  • WAF-25: files and multipart collections.
  • WAF-26: URL-encoded and JSON body collections.
  • WAF-27: XML/XPath collections.
  • WAF-09: persistence, expiry, and cross-request synchronization for IP, SESSION, USER, GLOBAL, and RESOURCE.

Those producers use the same bounded publication APIs; unavailable data is absent rather than fabricated. Run zig build bench-collections -Doptimize=ReleaseFast for the reusable regex-target plus macro-expansion hot path.