ADR-0003: Test formats: SecRules Test Set JSON for units, go-ftw-derived YAML for engine profiles
ADR-0003: Test formats: SecRules Test Set JSON for units, go-ftw-derived YAML for engine profiles
- Status: proposed
- Date: 2026-10-06
- Deciders: @fzipi
- Category: Clarification
Context
Three machine-readable test corpora already exist for SecLang engines:
- The SecRules Test Set (
SpiderLabs/secrules-language-tests, vendored as a submodule by libmodsecurity v3): JSON arrays of{type, name, param, input, output, ret}cases for operators and transformations. Both ModSecurity v3 and Coraza have runners for this shape. - libmodsecurity v3 regression tests (
test/test-cases/regression/*.json): a full synthetic transaction plus expected debug-log substrings and HTTP code. Tied to v3 debug-log wording. - go-ftw YAML (
coreruleset/go-ftw, documented inwaf-test-spec/YAMLFormat.md) and Coraza’s in-treetesting/profileGo structs, which mirror it and addrules,triggered_rules,non_triggered_rulesandinterruption.
This repository must stay runner-free (design assumption 4), so the formats must be loadable by Go and C++ test code with off-the-shelf libraries.
Decision
Two tiers, both with a JSON Schema in tests/schema/:
- Unit tier,
tests/unit/**/*.json: the SecRules Test Set field names, plus optionalspec,noteandre_groupsfields. Two deliberate differences from the corpus:input,outputandparamare plain JSON strings with no second layer of backslash escapes (libmodsecurity’s runner decodes\\xHHand\\uHHHHininputandoutputafter parsing, to one byte each, and nothing else;tools/import_sts.pyapplies exactly that model once, on import), and they are byte strings: every code point is at most U+00FF and denotes one byte, which the validator enforces, so adapters encode them with Latin-1. - Engine tier,
tests/engine/**/*.yaml: one profile per file in the Coraza profile / go-ftw shape (meta,rules,tests[].stages[].stage.{input,output}), plusspec,requires, astage.responseblock for synthetic backend responses, andoutput.no_interruption.
Assertions are limited to rule IDs, interruption and log substrings. Audit-log layout, debug-log wording and timing are never asserted.
The validator uses two Python dev dependencies, pyyaml and jsonschema, because
YAML parsing and JSON Schema 2020-12 validation are not worth reimplementing and both
are available in every CI image.
Options considered
- Invent a new unified format: nothing can run it on day one; rejected.
- JSON for the engine tier too: one parser fewer, but go-ftw users write YAML and the
multi-line
rulesblock is unreadable as a JSON string; rejected. - Reuse v3 regression JSON: assertions depend on v3 debug-log wording; rejected.
- stdlib-only validator: would need a hand-written YAML subset parser and schema checker; rejected as more code than the rest of the repo.
Consequences
- For ModSecurity: the unit tier is already runnable; the engine tier needs a small
adapter over the existing regression harness (
test/regression). - For Coraza:
testing/profilealready loads the engine tier minusresponse,requires,specandno_interruption; add those fields and a skip onrequires. - For contributors: write YAML/JSON, run
python3 tools/validate.py, no toolchain.