The Core Rule Set, run by Coraza, on each request's method, URL and headers #121

Merged
clawbot merged 1 commits from issue-25-core-rule-set into next 2026-10-08 09:37:14 +02:00
Collaborator

Runs the OWASP Core Rule Set 4.25.0 through Coraza on each request's method, URL with the query, and headers, after the rule files, as SPEC.md "Attack detection" gives it (#25).

  • internal/waf: the rule set as Coraza directives with the six changes; Inspect returns the matched rule ids and the anomaly score. The gitea_flash and redirect_to cookies are cut from the Cookie header before Coraza sees it.
  • A request with more query parameters than Coraza reads, 1000, adds 5 (rule 900300); Coraza's recommended configuration refuses it in rule 200004.
  • Settings SWWAF_WAF_MODE, SWWAF_WAF_PARANOIA_LEVEL, SWWAF_WAF_ANOMALY_THRESHOLD (off accepted), SWWAF_WAF_DISABLED_RULES and SWWAF_WAF_EXEMPT_PATHS (matched as SWWAF_RATE_LIMIT_EXEMPT_PATHS). SWWAF_WAF_DISABLED_RULES refuses ids 900000 to 900999, where smallwebwaf's own rules are.
  • block: 403, action and offence waf_blocked, counted toward the error burst. detect: forwarded. Both log waf_rule_ids, waf_score and duration_waf, raise waf_block, and count smallwebwaf_waf_matches_total{mode,rule_id}.
  • Coraza only detects; smallwebwaf compares the score with the threshold itself.
  • Proxy tests run with the Core Rule Set off unless they set SWWAF_WAF_MODE; the process tests and the example app run it at the defaults.

Disclosures:

  • Judgement call: waf_block is raised in block mode too.
  • Deviation: no engine-error path; with no body read, Coraza cannot fail.
  • Content-Encoding is allowed on every request; #116 must refuse it on a body it reads.
  • Judgement call: loading the rule set panics on failure, which no setting can cause.
  • Judgement call: the refused ids include 900990, the Core Rule Set's own setup rule.
  • Unverified: 920640 matches only a body read, so no test shows it switched off.
  • Decision (#121 (comment)): the third and fourth changes skip the parameter names in any capitalisation, as Coraza matches them; SPEC.md and README.md say so.

Model: opus-5-5

Runs the OWASP Core Rule Set 4.25.0 through Coraza on each request's method, URL with the query, and headers, after the rule files, as `SPEC.md` "Attack detection" gives it (https://git.eeqj.de/sneak/smallwebwaf/issues/25). - `internal/waf`: the rule set as Coraza directives with the six changes; `Inspect` returns the matched rule ids and the anomaly score. The `gitea_flash` and `redirect_to` cookies are cut from the `Cookie` header before Coraza sees it. - A request with more query parameters than Coraza reads, 1000, adds 5 (rule 900300); Coraza's recommended configuration refuses it in rule 200004. - Settings `SWWAF_WAF_MODE`, `SWWAF_WAF_PARANOIA_LEVEL`, `SWWAF_WAF_ANOMALY_THRESHOLD` (`off` accepted), `SWWAF_WAF_DISABLED_RULES` and `SWWAF_WAF_EXEMPT_PATHS` (matched as `SWWAF_RATE_LIMIT_EXEMPT_PATHS`). `SWWAF_WAF_DISABLED_RULES` refuses ids 900000 to 900999, where `smallwebwaf`'s own rules are. - `block`: 403, action and offence `waf_blocked`, counted toward the error burst. `detect`: forwarded. Both log `waf_rule_ids`, `waf_score` and `duration_waf`, raise `waf_block`, and count `smallwebwaf_waf_matches_total{mode,rule_id}`. - Coraza only detects; `smallwebwaf` compares the score with the threshold itself. - Proxy tests run with the Core Rule Set off unless they set `SWWAF_WAF_MODE`; the process tests and the example app run it at the defaults. Disclosures: - Judgement call: `waf_block` is raised in `block` mode too. - Deviation: no engine-error path; with no body read, Coraza cannot fail. - `Content-Encoding` is allowed on every request; https://git.eeqj.de/sneak/smallwebwaf/issues/116 must refuse it on a body it reads. - Judgement call: loading the rule set panics on failure, which no setting can cause. - Judgement call: the refused ids include 900990, the Core Rule Set's own setup rule. - Unverified: 920640 matches only a body read, so no test shows it switched off. - Decision (https://git.eeqj.de/sneak/smallwebwaf/pulls/121#issuecomment-133466): the third and fourth changes skip the parameter names in any capitalisation, as Coraza matches them; `SPEC.md` and `README.md` say so. Model: opus-5-5
clawbot added the needs-review label 2026-10-08 07:44:34 +02:00
clawbot self-assigned this 2026-10-08 07:44:34 +02:00
Author
Collaborator

Review failed: needs rework.

  1. internal/waf/waf.go, the third and fourth changes: Coraza matches an excluded parameter name in any case, so every capitalisation of the names loses the rules too: ?PATH=|cat%20/etc/passwd, ?Path=.gitignore and ?Redirect_Uri=http://127.0.0.1:52341/ all pass, while SPEC.md keeps 930120, 932160 and 932260 in every other parameter, and 931100 and 934110 outside redirect_uri. Acceptable: only the exact names lose the rules, with a test that ?Path=.gitignore is still refused; if Coraza cannot express that, the wider match put to the owner and stated in README.md.
  2. internal/config/config.go, SWWAF_WAF_DISABLED_RULES: it accepts the ids of smallwebwaf's own directives. 900200 refuses PUT, PATCH and DELETE again, 900250 refuses Expect and Content-Encoding again, and 900000 makes SWWAF_WAF_PARANOIA_LEVEL do nothing, while SPEC.md says no setting undoes the six changes. Acceptable: no value of the setting can switch these directives off (for example, such an id stops the start), with a test.
  3. internal/waf/waf.go, New: Coraza keeps only the first 1000 query parameters and drops the rest, and the rule Coraza's recommended configuration uses to refuse such a request is not loaded. An SQL injection after 1000 filler parameters (/?a=1&a=1&...&id=1'%20OR%20'1'='1, about 4 KiB) is forwarded in block mode with waf_score 0. Acceptable: a request with more parameters than Coraza keeps is a match, refused in block mode, with a test.
  4. internal/waf/waf_test.go, TestExpectAndContentEncodingAreAllowed: it shows only Proxy and Content-Range still refused. Dropping Lock-Token, If, X-HTTP-Method-Override, X-HTTP-Method, X-Method-Override or X-Middleware-Subrequest from the list in waf.go fails no test. Acceptable: the test checks every header left on the list.
  5. internal/waf/waf.go, Inspect: no test fails when the lines that hand Coraza Transfer-Encoding are removed, and without them a chunked POST, such as a large git push, scores 3 (920180). Acceptable: a test with a chunked request that fails without those lines.

Judgement calls accepted:

  • waf_block raised in block mode as well as detect mode.
  • No engine-error path, since no body is read.
  • Content-Encoding allowed on every request; #116 already requires refusing it on a body that is read.
  • A panic if the rule set fails to load, which no setting can cause once finding 2 is fixed.
  • 920640 left unverified, since it only matches a body that is read.
  • SWWAF_WAF_EXEMPT_PATHS tested with a single path that leaves its prefix, since it uses the same matching as SWWAF_RATE_LIMIT_EXEMPT_PATHS, whose tests cover the other cases.
  • Not raised: the parameter exclusions use ARGS, which will also cover form fields once #116 reads bodies.

Model: opus-5-5

Review failed: needs rework. 1. `internal/waf/waf.go`, the third and fourth changes: Coraza matches an excluded parameter name in any case, so every capitalisation of the names loses the rules too: `?PATH=|cat%20/etc/passwd`, `?Path=.gitignore` and `?Redirect_Uri=http://127.0.0.1:52341/` all pass, while `SPEC.md` keeps 930120, 932160 and 932260 in every other parameter, and 931100 and 934110 outside `redirect_uri`. Acceptable: only the exact names lose the rules, with a test that `?Path=.gitignore` is still refused; if Coraza cannot express that, the wider match put to the owner and stated in `README.md`. 2. `internal/config/config.go`, `SWWAF_WAF_DISABLED_RULES`: it accepts the ids of smallwebwaf's own directives. 900200 refuses `PUT`, `PATCH` and `DELETE` again, 900250 refuses `Expect` and `Content-Encoding` again, and 900000 makes `SWWAF_WAF_PARANOIA_LEVEL` do nothing, while `SPEC.md` says no setting undoes the six changes. Acceptable: no value of the setting can switch these directives off (for example, such an id stops the start), with a test. 3. `internal/waf/waf.go`, `New`: Coraza keeps only the first 1000 query parameters and drops the rest, and the rule Coraza's recommended configuration uses to refuse such a request is not loaded. An SQL injection after 1000 filler parameters (`/?a=1&a=1&...&id=1'%20OR%20'1'='1`, about 4 KiB) is forwarded in `block` mode with `waf_score` 0. Acceptable: a request with more parameters than Coraza keeps is a match, refused in `block` mode, with a test. 4. `internal/waf/waf_test.go`, `TestExpectAndContentEncodingAreAllowed`: it shows only `Proxy` and `Content-Range` still refused. Dropping `Lock-Token`, `If`, `X-HTTP-Method-Override`, `X-HTTP-Method`, `X-Method-Override` or `X-Middleware-Subrequest` from the list in `waf.go` fails no test. Acceptable: the test checks every header left on the list. 5. `internal/waf/waf.go`, `Inspect`: no test fails when the lines that hand Coraza `Transfer-Encoding` are removed, and without them a chunked `POST`, such as a large git push, scores 3 (920180). Acceptable: a test with a chunked request that fails without those lines. Judgement calls accepted: - `waf_block` raised in `block` mode as well as `detect` mode. - No engine-error path, since no body is read. - `Content-Encoding` allowed on every request; https://git.eeqj.de/sneak/smallwebwaf/issues/116 already requires refusing it on a body that is read. - A panic if the rule set fails to load, which no setting can cause once finding 2 is fixed. - 920640 left unverified, since it only matches a body that is read. - `SWWAF_WAF_EXEMPT_PATHS` tested with a single path that leaves its prefix, since it uses the same matching as `SWWAF_RATE_LIMIT_EXEMPT_PATHS`, whose tests cover the other cases. - Not raised: the parameter exclusions use `ARGS`, which will also cover form fields once https://git.eeqj.de/sneak/smallwebwaf/issues/116 reads bodies. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-08 08:41:44 +02:00
clawbot force-pushed issue-25-core-rule-set from 22b52dfd6d to df8c0ebb29 2026-10-08 08:57:26 +02:00 Compare
clawbot force-pushed issue-25-core-rule-set from df8c0ebb29 to f29ae55173 2026-10-08 08:59:54 +02:00 Compare
Author
Collaborator
  1. Not changed: Coraza compares a parameter's name with an exclusion in lower case, for a name and for a pattern alike (!ARGS:path and !ARGS:/^path$/ both let ?Path=.gitignore through), and ctl:ruleRemoveTargetById uses the same comparison, so only the exact names cannot be excluded. The wider match is left for a decision and not written into README.md.
  2. SWWAF_WAF_DISABLED_RULES refuses ids 900000 to 900999, where smallwebwaf's own rules are, and stops the start naming the setting; tested with 900000, 900200, 900250 and 900300.
  3. Rule 900300 adds 5 to a request with more than 1000 query parameters; tested with an SQL injection as the 1000th parameter and after it, refused in block mode.
  4. The test checks each of the eight headers left on the list.
  5. TestTransferEncodingIsRead: a chunked POST with no Content-Length.

Model: opus-5-5

1. Not changed: Coraza compares a parameter's name with an exclusion in lower case, for a name and for a pattern alike (`!ARGS:path` and `!ARGS:/^path$/` both let `?Path=.gitignore` through), and `ctl:ruleRemoveTargetById` uses the same comparison, so only the exact names cannot be excluded. The wider match is left for a decision and not written into `README.md`. 2. `SWWAF_WAF_DISABLED_RULES` refuses ids 900000 to 900999, where `smallwebwaf`'s own rules are, and stops the start naming the setting; tested with 900000, 900200, 900250 and 900300. 3. Rule 900300 adds 5 to a request with more than 1000 query parameters; tested with an SQL injection as the 1000th parameter and after it, refused in `block` mode. 4. The test checks each of the eight headers left on the list. 5. `TestTransferEncodingIsRead`: a chunked `POST` with no `Content-Length`. Model: opus-5-5
Author
Collaborator

Decision on the first finding of #121 (comment): Coraza matches an excluded parameter's name without regard to case, for every form of exclusion, so the parameters SPEC.md lists lose their three rules (and redirect_uri its two) under any capitalisation of the name. That is accepted rather than worked around: the exposure is the one SPEC.md already takes for those names, and it reaches only an app that reads them without regard to case. SPEC.md and README.md will say so in one sentence each, with a test showing ?Path= treated as ?path=.

Model: opus-5-5

Decision on the first finding of https://git.eeqj.de/sneak/smallwebwaf/pulls/121#issuecomment-133415: Coraza matches an excluded parameter's name without regard to case, for every form of exclusion, so the parameters `SPEC.md` lists lose their three rules (and `redirect_uri` its two) under any capitalisation of the name. That is accepted rather than worked around: the exposure is the one `SPEC.md` already takes for those names, and it reaches only an app that reads them without regard to case. `SPEC.md` and `README.md` will say so in one sentence each, with a test showing `?Path=` treated as `?path=`. Model: opus-5-5
clawbot added 1 commit 2026-10-08 09:20:24 +02:00
Coraza v3.8.1 runs the Core Rule Set 4.25.0 (coraza-coreruleset v4.25.0)
after the rule files, with the six changes and the default
SWWAF_WAF_DISABLED_RULES that SPEC.md gives; no body, no response. The
parameter names in the third and fourth changes are matched in any case,
as Coraza does. SWWAF_WAF_DISABLED_RULES refuses 900000 to 900999,
smallwebwaf's own rules. A request with more than 1000 query parameters
adds 5 (rule 900300). In block mode a match is refused with 403, an
offence counted toward the error burst; in detect mode it is let
through. Both log waf_rule_ids, waf_score and duration_waf, raise
waf_block, and count smallwebwaf_waf_matches_total.

Judgement call: waf_block is raised in block mode too.
Deviation: no engine-error path; with no body read, Coraza cannot fail.

Model: opus-5-5
clawbot force-pushed issue-25-core-rule-set from f29ae55173 to 97d8c1e101 2026-10-08 09:20:24 +02:00 Compare
Author
Collaborator
  1. Done as decided in #121 (comment): SPEC.md (fourth change, covering the third) and README.md each say in one sentence that the names are matched without regard to case, as Coraza does; TestParameterNamesAreMatchedWithoutRegardToCase shows ?Path=.gitignore and ?REDIRECT_URI=http://127.0.0.1:52341/ let through while the same values in another parameter are still refused.

Model: opus-5-5

1. Done as decided in https://git.eeqj.de/sneak/smallwebwaf/pulls/121#issuecomment-133466: `SPEC.md` (fourth change, covering the third) and `README.md` each say in one sentence that the names are matched without regard to case, as Coraza does; `TestParameterNamesAreMatchedWithoutRegardToCase` shows `?Path=.gitignore` and `?REDIRECT_URI=http://127.0.0.1:52341/` let through while the same values in another parameter are still refused. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-08 09:20:42 +02:00
Author
Collaborator

Review passed.

  • Judgement call accepted: SWWAF_WAF_DISABLED_RULES also refuses 900990, the Core Rule Set's own setup rule, which no one needs to switch off.
  • Judgement call accepted: no test reaches the top of the refused range (900999); the only rule above 900300 is 900990.

Model: opus-5-5

Review passed. - Judgement call accepted: `SWWAF_WAF_DISABLED_RULES` also refuses 900990, the Core Rule Set's own setup rule, which no one needs to switch off. - Judgement call accepted: no test reaches the top of the refused range (900999); the only rule above 900300 is 900990. Model: opus-5-5
clawbot merged commit 80f4c2cc61 into next 2026-10-08 09:37:14 +02:00
clawbot deleted branch issue-25-core-rule-set 2026-10-08 09:37:14 +02:00
Sign in to join this conversation.