Coraza v3.8.1, which sww uses, has a data race when two requests are inspected at once by the same Core Rule Set. sww's HTTP server does this on every pair of concurrent requests.
What goes wrong. A ctl:ruleRemoveTargetById exclusion adds the removed field to the rule's list of exceptions for one request (internal/corazawaf/rule.go line 249 in v3.8.1). That list is a copy, but it shares its memory with the rule's own list. Two concurrent requests can write to the same slot, so one request can be checked with the other request's exception. Example: a WordPress request whose exclusion removes one field from rule 942100 can run with a Drupal exclusion's field removed instead. Its own field is then checked (a false refusal), and the other field is skipped (an attack let through). gitea.conf, wordpress.conf and drupal.conf all use these exclusions.
How it showed up.#253 makes internal/coreruleset's tests share one Core Rule Set across concurrent tests. go test -race in make check then reports the race in (*Rule).doEvaluate, called from (*CoreRuleSet).Inspect. Ordinary requests are also refused there: for example, Drupal's GET /admin/people?user=docker-compose.yml&status=All&role=All&permission=All matches 932260, and WordPress's GET /wp-admin/edit-tags.php?taxonomy=post_tag&post_type=post&s=python3 does too. Both pass when inspected alone.
sww uses a Coraza with the fix. github.com/corazawaf/coraza/v3 v3.8.2-0.20261022102553-d028be399c68 (commit d028be399c68 on Coraza's main) passes every test with the shared Core Rule Sets of the pull request above, under -race. A tagged release is the alternative, if one ships first.
go.mod and go.sum change through make tidy.
make check passes.
Whether to depend on an untagged Coraza commit until a release ships is a judgement call for the reviewer to confirm.
Model: opus-5-5
Coraza v3.8.1, which sww uses, has a data race when two requests are inspected at once by the same Core Rule Set. sww's HTTP server does this on every pair of concurrent requests.
**What goes wrong.** A `ctl:ruleRemoveTargetById` exclusion adds the removed field to the rule's list of exceptions for one request (`internal/corazawaf/rule.go` line 249 in v3.8.1). That list is a copy, but it shares its memory with the rule's own list. Two concurrent requests can write to the same slot, so one request can be checked with the other request's exception. Example: a WordPress request whose exclusion removes one field from rule 942100 can run with a Drupal exclusion's field removed instead. Its own field is then checked (a false refusal), and the other field is skipped (an attack let through). `gitea.conf`, `wordpress.conf` and `drupal.conf` all use these exclusions.
**How it showed up.** https://git.eeqj.de/sneak/smallwebwaf/pulls/253 makes `internal/coreruleset`'s tests share one Core Rule Set across concurrent tests. `go test -race` in `make check` then reports the race in `(*Rule).doEvaluate`, called from `(*CoreRuleSet).Inspect`. Ordinary requests are also refused there: for example, Drupal's `GET /admin/people?user=docker-compose.yml&status=All&role=All&permission=All` matches 932260, and WordPress's `GET /wp-admin/edit-tags.php?taxonomy=post_tag&post_type=post&s=python3` does too. Both pass when inspected alone.
**Upstream fix.** Coraza fixed it on `main` in https://github.com/corazawaf/coraza/pull/1745 ("keep ctl:ruleRemoveTargetById removals per transaction"), with https://github.com/corazawaf/coraza/pull/1778 adding a test. No release contains it yet: the latest tag is `v3.8.1`.
**Definition of done**, one PR into `next`:
- sww uses a Coraza with the fix. `github.com/corazawaf/coraza/v3 v3.8.2-0.20261022102553-d028be399c68` (commit `d028be399c68` on Coraza's `main`) passes every test with the shared Core Rule Sets of the pull request above, under `-race`. A tagged release is the alternative, if one ships first.
- `go.mod` and `go.sum` change through `make tidy`.
- `make check` passes.
Whether to depend on an untagged Coraza commit until a release ships is a judgement call for the reviewer to confirm.
Model: opus-5-5
Decision and plan. This is a bug in production: concurrent requests share one Core Rule Set, so one request's exclusion can apply to another, refusing an ordinary request or letting an attack through. It goes before every other unit.
Coraza version: use the commit on Coraza's main that carries the fix, through its pseudo-version, pinned by go.sum like any module. No tagged release has the fix, and waiting leaves the bug in place. Switching to the first tagged release that contains it is a one-line make tidy change; when one ships, it gets its own issue. The reviewer confirms the commit carries https://github.com/corazawaf/coraza/pull/1745 and nothing that changes how sww's rules behave.
Two commits, rebase-merged:
First, a test in internal/coreruleset that inspects the two requests named above concurrently, many times over, on one Core Rule Set, and expects both to pass. It fails under -race with v3.8.1. Put it in coreruleset_test.go, beside newCoreRuleSet.
Second, the Coraza change through make tidy, with no change to the test.
Nothing else: no change to .conf files, other tests or documents. SPEC.md names Coraza's version only if it already does; then update it.
make fmt, then flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check on the second commit; a flock timeout also exits 1, so read the output. Push each commit as it is made. Label the PR needs-review.
Model: opus-5-5
**Decision and plan.** This is a bug in production: concurrent requests share one Core Rule Set, so one request's exclusion can apply to another, refusing an ordinary request or letting an attack through. It goes before every other unit.
- **Coraza version:** use the commit on Coraza's `main` that carries the fix, through its pseudo-version, pinned by `go.sum` like any module. No tagged release has the fix, and waiting leaves the bug in place. Switching to the first tagged release that contains it is a one-line `make tidy` change; when one ships, it gets its own issue. The reviewer confirms the commit carries https://github.com/corazawaf/coraza/pull/1745 and nothing that changes how sww's rules behave.
- **Two commits, rebase-merged:**
- First, a test in `internal/coreruleset` that inspects the two requests named above concurrently, many times over, on one Core Rule Set, and expects both to pass. It fails under `-race` with v3.8.1. Put it in `coreruleset_test.go`, beside `newCoreRuleSet`.
- Second, the Coraza change through `make tidy`, with no change to the test.
- **Nothing else:** no change to `.conf` files, other tests or documents. `SPEC.md` names Coraza's version only if it already does; then update it.
`make fmt`, then `flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check` on the second commit; a `flock` timeout also exits 1, so read the output. Push each commit as it is made. Label the PR `needs-review`.
Model: opus-5-5
clawbot
self-assigned this 2026-11-09 21:58:41 +01:00
Built in #255: a concurrency test that fails on v3.8.1, then Coraza at commit d028be399c68 through make tidy. SPEC.md names no Coraza version, so it is unchanged.
Model: opus-5-5
Built in https://git.eeqj.de/sneak/smallwebwaf/pulls/255: a concurrency test that fails on v3.8.1, then Coraza at commit `d028be399c68` through `make tidy`. `SPEC.md` names no Coraza version, so it is unchanged.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Coraza v3.8.1, which sww uses, has a data race when two requests are inspected at once by the same Core Rule Set. sww's HTTP server does this on every pair of concurrent requests.
What goes wrong. A
ctl:ruleRemoveTargetByIdexclusion adds the removed field to the rule's list of exceptions for one request (internal/corazawaf/rule.goline 249 in v3.8.1). That list is a copy, but it shares its memory with the rule's own list. Two concurrent requests can write to the same slot, so one request can be checked with the other request's exception. Example: a WordPress request whose exclusion removes one field from rule 942100 can run with a Drupal exclusion's field removed instead. Its own field is then checked (a false refusal), and the other field is skipped (an attack let through).gitea.conf,wordpress.confanddrupal.confall use these exclusions.How it showed up. #253 makes
internal/coreruleset's tests share one Core Rule Set across concurrent tests.go test -raceinmake checkthen reports the race in(*Rule).doEvaluate, called from(*CoreRuleSet).Inspect. Ordinary requests are also refused there: for example, Drupal'sGET /admin/people?user=docker-compose.yml&status=All&role=All&permission=Allmatches 932260, and WordPress'sGET /wp-admin/edit-tags.php?taxonomy=post_tag&post_type=post&s=python3does too. Both pass when inspected alone.Upstream fix. Coraza fixed it on
mainin https://github.com/corazawaf/coraza/pull/1745 ("keep ctl:ruleRemoveTargetById removals per transaction"), with https://github.com/corazawaf/coraza/pull/1778 adding a test. No release contains it yet: the latest tag isv3.8.1.Definition of done, one PR into
next:github.com/corazawaf/coraza/v3 v3.8.2-0.20261022102553-d028be399c68(commitd028be399c68on Coraza'smain) passes every test with the shared Core Rule Sets of the pull request above, under-race. A tagged release is the alternative, if one ships first.go.modandgo.sumchange throughmake tidy.make checkpasses.Whether to depend on an untagged Coraza commit until a release ships is a judgement call for the reviewer to confirm.
Model: opus-5-5
clawbot referenced this issue2026-11-09 21:53:39 +01:00
Decision and plan. This is a bug in production: concurrent requests share one Core Rule Set, so one request's exclusion can apply to another, refusing an ordinary request or letting an attack through. It goes before every other unit.
mainthat carries the fix, through its pseudo-version, pinned bygo.sumlike any module. No tagged release has the fix, and waiting leaves the bug in place. Switching to the first tagged release that contains it is a one-linemake tidychange; when one ships, it gets its own issue. The reviewer confirms the commit carries https://github.com/corazawaf/coraza/pull/1745 and nothing that changes how sww's rules behave.internal/corerulesetthat inspects the two requests named above concurrently, many times over, on one Core Rule Set, and expects both to pass. It fails under-racewith v3.8.1. Put it incoreruleset_test.go, besidenewCoreRuleSet.make tidy, with no change to the test..conffiles, other tests or documents.SPEC.mdnames Coraza's version only if it already does; then update it.make fmt, thenflock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make checkon the second commit; aflocktimeout also exits 1, so read the output. Push each commit as it is made. Label the PRneeds-review.Model: opus-5-5
Built in #255: a concurrency test that fails on v3.8.1, then Coraza at commit
d028be399c68throughmake tidy.SPEC.mdnames no Coraza version, so it is unchanged.Model: opus-5-5