Coraza reference adapter
Runs the conformance data in ../../tests against
Coraza through its public API, as go test.
cd adapters/coraza
go test ./... # everything
go test ./... -run TestEngine -v # engine-tier profiles, one subtest per file
go test ./... -run 'TestUnit$' -v # unit-tier filesThe Coraza version is pinned in go.mod (v3.8.1). Change it there to grade a different
release; every other input is the repository’s test data.
How results are gated
compat/known-gaps.md is the expected-failure list. For each test file:
| Row for Coraza in known-gaps | Result | Outcome |
|---|---|---|
| none | all pass | pass |
| none | any failure | fail (a real defect in Coraza or in the specification) |
| present | any failure | pass, failures logged as expected (known gap: …) |
| present | all pass | fail with known-gaps row is obsolete (remove the row) |
So the build is green exactly when the table is accurate. A new divergence is handled by adding a row with a source pointer; a fixed one by deleting its row.
Extended features Coraza does not implement are skipped, not failed, as
spec/00-conventions.md requires: engine profiles through requires:, unit files for
the operators and transformations listed in conformance_test.go, and the
rx-pcre-extensions cases.
How the tiers are driven
- Engine tier (
engine.go): one WAF per profile with the profile’sfiles:written under a temporary root; one transaction per stage throughProcessConnection,ProcessURI, headers,WriteRequestBody,ProcessRequestBody, the synthetic response when the stage has one, andProcessLogging.triggered_rulescome fromMatchedRules(),interruptionfromInterruption(),log_containsfrom the error callback. An engine panic is reported as one failure instead of aborting the run. - Unit tier (
unit.go): each operator or transformation case is wrapped in a rule overREQUEST_BODY, the input is sent as raw bytes (unit strings are Latin-1 byte strings,tests/README.md), and the result is read from the matched rule: whether it matched for operators, the transformed value for transformations,TX:0..9forre_groups. Coraza treats\"as the only escape inside a quoted argument, so parameters are inserted with quotes escaped and nothing else (an Apache-based adapter would also have to double backslashes; seespec/01-lexical.md#quoting-and-escapes). Engine-tierdatais UTF-8 text and is sent as such; unit-tier strings are Latin-1 byte strings and are sent byte for byte.
Differential run against the Lean model
cmd/differential drives Coraza with seeded random requests against every Core profile’s
ruleset and writes what the engine did, in the JSON shape formal/EvalMain.lean reads, so
the Lean model can be checked on inputs no hand-written profile anticipated:
cd adapters/coraza && go run ./cmd/differential -n 30 -seed 1 > ../../formal/.lake/differential.json
cd ../../formal && lake exe seclang-eval .lake/differential.json # exit 1 on any mismatchProfiles with requires:, with a Coraza row in compat/known-gaps.md, or with
expect_error or log assertions are skipped, and the generator never produces a value the
specification leaves unspecified for the profile’s operators (digits followed by text for
the numeric operators, a body of exactly SecRequestBodyLimit bytes). A mismatch is
classified like any adapter disagreement: a spec or model defect is fixed, an engine
divergence gets a Core profile and a known-gaps row (which then excludes the profile from
the run). CI runs seed 1 with 30 requests per profile; the first run found ADR-0030, the
REQBODY_PROCESSOR clarification and the body-limit boundary gap. With -profile FILE the
generator runs one external profile instead: CI feeds it OWASP CRS as assembled by
tools/crs_profile.py (20 requests, seed 1), so the model is checked against Coraza on
some seven hundred real rules.
Adding another engine
Copy the shape: load the data (profile.go is engine-neutral), drive the engine, map
observations to the same Observed/UnitResult structs, reuse CheckStage/CheckUnit
and LoadGaps with your engine’s name. ADR-0023 records that reference adapters live
here; an engine may equally keep its adapter in its own repository.
adapters/libmodsecurity is the second reference adapter (Python, ctypes over the C API);
it shares no code with this module but follows the same gating.