Objective
Implement the complete stable ModSecurity 3.0.16 / Coraza 3.7.0 transformation union as typed, immutable execution plans with byte-exact behavior, case-insensitive aliases, t:none reset semantics, ordered pipelines, multi-match checkpoints, bounded scratch storage, and executable differential evidence. Transformation execution must remain independent from UI, PostgreSQL, audit delivery, and all blocking I/O.
Stable union
Encoding, decoding, and representation
- base64Decode, base64DecodeExt, base64Encode
- hexDecode, hexEncode, sqlHexDecode
- urlDecode, urlDecodeUni, urlEncode
- htmlEntityDecode, cssDecode, jsDecode, escapeSeqDecode
- utf8toUnicode
- length
Canonicalization and filtering
- lowercase, uppercase
- trim, trimLeft, trimRight
- compressWhitespace, removeWhitespace
- removeNulls, replaceNulls
- removeComments, removeCommentsChar, replaceComments
- cmdLine
- normalisePath, normalisePathWin
- Accept normalizePath and normalizePathWin as case-insensitive aliases that compile to the canonical kinds.
- parityEven7bit, parityOdd7bit, parityZero7bit
Compatibility digests and pipeline control
- md5, sha1; document that these are byte-compatible SecLang transformations, not approved password/signature primitives.
- none as a compile-time pipeline reset only; it must never become a runtime transform step.
Typed plan and registry
- Replace free-form transformation names in published plans with a stable TransformationKind enum and canonical feature-discovery names.
- Validate every configured transformation before ruleset publication. Unknown names and unavailable reduced-build features fail with the stable plan diagnostic; no runtime lookup fallback.
- Preserve declared order after default inheritance, t:none, and SecRuleUpdateActionById overlays.
- Preserve chain-member pipelines independently while retaining WAF-15 chain-head action/intervention semantics.
- Keep aliases case-insensitive at configuration time and canonical in fingerprints, serialization, evidence, and benchmarks.
- Define explicit native/plugin registration boundaries without weakening immutable-plan ownership; plugin execution itself remains linked to WAF-22.
Execution API and ownership
- Add a reusable per-worker/per-transaction transformation executor with bounded scratch buffers and no global mutable state.
- Return explicit bytes, changed/not-changed state, and ownership/lifetime information so unchanged transforms may safely borrow input and changed transforms cannot outlive scratch storage.
- Preserve embedded NULs and arbitrary byte strings; never assume UTF-8 unless a specific transformation requires UTF-8/code-point decoding.
- Use overflow-checked output sizing and deterministic limits for input bytes, output bytes, pipeline steps, cumulative expanded bytes, and cache storage.
- Make allocation, invalid input, limit exhaustion, and plugin failure distinguishable. Compatibility transforms with tolerant malformed-input semantics must return their specified bytes rather than being conflated with engine failure.
- Ensure partial pipeline failure publishes no operator match, capture, multi-match checkpoint, action, or intervention state.
Byte semantics
- Match the pinned upstream handling of malformed/incomplete percent, base64, hex, entity, CSS, JavaScript, C escape, SQL hex, and UTF-8 sequences.
- Match upstream path normalization for repeated separators, dot segments, root traversal, trailing separators, and Windows slash conversion.
- Match exact comment state machines, including unterminated comments and comment-marker edge cases.
- Match ASCII-only case and whitespace definitions where upstream is byte-oriented; do not introduce locale-sensitive behavior.
- Match digest output representation and exact length semantics.
- Define and test expansion bounds for URL encoding and utf8toUnicode.
Pipeline, multiMatch, and cache semantics
- Execute transformations in published plan order and propagate changed-buffer state without redundant copies.
- Implement WAF-15 multiMatch checkpoints at the original value and after each transformation that changed the value, without duplicate evaluation for unchanged steps.
- Retain the correct transformed value and source/origin metadata for operator evaluation, captures, rule evidence, and replay.
- Implement bounded transaction/worker transformation caching where SecCacheTransformations enables it; cache keys include transformation kind/pipeline identity and exact input bytes.
- Keep cache misses correct under collisions and disable caching explicitly when limits are exhausted rather than bypassing transformations.
- Never share mutable scratch, digest state, or cache entries across request workers without synchronization.
Evidence and qualification
- Build a generated inventory from the pinned ModSecurity scanner/parser and Coraza registry; fail CI if the stable union drifts.
- Import or adapt pinned Coraza transformation JSON fixtures and relevant ModSecurity regression fixtures with retained Apache/BSD notices.
- Add differential vectors for every stable kind and alias, including empty, unchanged, malformed, binary, maximum-bound, and expansion cases.
- Add pipeline/default/overlay/chain/multiMatch integration tests and exhaustive allocation-failure rollback tests.
- Add deterministic fuzzing for every decoder/state machine plus random ordered pipelines under output/work limits.
- Add ReleaseFast benchmarks for no-op/borrowed steps, common CRS pipelines, decoder-heavy inputs, path normalization, digest transforms, multiMatch, and cache hit/miss behavior.
- Record throughput, p50/p95/p99, allocations, input/output bytes, and cache behavior in an executable evidence manifest.
- Update feature discovery, the compatibility matrix, architecture documentation, and operator-facing transformation documentation only after executable evidence passes.
Acceptance criteria
- Every stable union name above resolves case-insensitively to a typed implementation or canonical alias and has positive, malformed, boundary, and differential evidence.
- All pinned Coraza transformation fixture cases and relevant ModSecurity/CRS transformation cases pass with no unexplained mismatches.
- t:none, inherited defaults, overlays, chains, and multiMatch match the pinned behavior.
- Unknown or unavailable transformations reject the ruleset before publication; no silent no-op or runtime string dispatch exists.
- Untrusted work and output growth have deterministic limits; allocation/limit failures leave match/action state unchanged.
- Common unchanged transforms avoid output allocation, and measured hot pipelines have recorded ReleaseFast gates suitable for later WAF-39 thresholds.
- Formatting, focused tests, inventory/differential corpora, deterministic fuzzing, benchmarks, and hosted CI pass on the exact closing SHA.
- Work lands directly on main in small conventional commits with issue/commit links, no submodules, and no unrelated checkout changes.
Relationships
- Parent: WAF-00 #1
Blocked by
- WAF-11 #12 — immutable execution plans (complete)
- WAF-14 #15 — atomic match/action state (complete)
- WAF-15 #16 — chain/multiMatch execution metadata (complete)
Blocks / feeds
- WAF-17 #18 — numeric/string/regex operators
- WAF-19 #20 — SQLi/XSS operators
- WAF-22 #23 — native Zig and C plugin registries
- WAF-33 #34 — validate/test/explain/replay tooling
- WAF-34 #35 — SecRules Test Set
- WAF-35 #36 — ModSecurity differential regression
- WAF-36 #37 — Coraza differential and HTTP E2E
- WAF-37 #38 — CRS FTW compatibility
- WAF-39 #40 — request-path performance gates