Core Rule Set: the package registries' searches of gitea and Forgejo pass (closes #354) #378

Merged
clawbot merged 1 commits from issue-354-package-registry-searches into next 2026-11-12 16:43:26 +01:00
Collaborator

Closes #354.

gitea 1.27.3 and Forgejo 17.0.1, images pinned by digest, were sent each package registry search with python3, powershell, ls -la, .htaccess, file names and plain words. Composer, Conan and npm also got /bin/sh and a name holding a slash. The clients were cargo, knife (Cinc), composer, conan 1 and 2, dotnet package search (NuGet v3 and v2) and npm. curl sent what no client sends: the Chef cookbook list with q, NuGet's Packages() with the search in $filter as NuGet's V2FeedQueryBuilder builds it, and each NuGet v2 search to its count route.

Changes in internal/coreruleset/gitea.conf:

  • 900603: q on the Cargo, Chef (search, cookbooks), Composer, Conan v1/v2 searches and NuGet's query skips 930120, 932120, 932250 and 932260.
  • 900605: q on Composer and Conan also skips 932160 (/bin/sh).
  • 900609 (new): npm's text skips those five rules.
  • 900611 (new, phase 2): Conan's q skips 942100, which refused zlib/*, only when the request, body included, has one q, shaped as a Conan reference without +, */ or --, that 942100 lets through once each /* comment is a space.
  • 900612 (new): NuGet's Search(), Packages() and their /$count routes: searchTerm skips the four rules; $filter skips 930120 and 932120; the parameter names $filter, $orderby and $top skip 942290 (MongoDB operators), which refused every v2 search.

An ordinary request can still be refused on a field outside these searches, for example q of Conan's package search within one recipe.

Model: opus-5-5

Closes https://git.eeqj.de/sneak/smallwebwaf/issues/354. gitea 1.27.3 and Forgejo 17.0.1, images pinned by digest, were sent each package registry search with `python3`, `powershell`, `ls -la`, `.htaccess`, file names and plain words. Composer, Conan and npm also got `/bin/sh` and a name holding a slash. The clients were `cargo`, `knife` (Cinc), `composer`, `conan` 1 and 2, `dotnet package search` (NuGet v3 and v2) and `npm`. curl sent what no client sends: the Chef cookbook list with `q`, NuGet's `Packages()` with the search in `$filter` as NuGet's `V2FeedQueryBuilder` builds it, and each NuGet v2 search to its count route. Changes in `internal/coreruleset/gitea.conf`: - 900603: `q` on the Cargo, Chef (`search`, `cookbooks`), Composer, Conan v1/v2 searches and NuGet's `query` skips 930120, 932120, 932250 and 932260. - 900605: `q` on Composer and Conan also skips 932160 (`/bin/sh`). - 900609 (new): npm's `text` skips those five rules. - 900611 (new, phase 2): Conan's `q` skips 942100, which refused `zlib/*`, only when the request, body included, has one `q`, shaped as a Conan reference without `+`, `*/` or `--`, that 942100 lets through once each `/*` comment is a space. - 900612 (new): NuGet's `Search()`, `Packages()` and their `/$count` routes: `searchTerm` skips the four rules; `$filter` skips 930120 and 932120; the parameter names `$filter`, `$orderby` and `$top` skip 942290 (MongoDB operators), which refused every v2 search. An ordinary request can still be refused on a field outside these searches, for example `q` of Conan's package search within one recipe. Model: opus-5-5
clawbot added the needs-review label 2026-11-12 13:13:38 +01:00
clawbot self-assigned this 2026-11-12 13:13:39 +01:00
Author
Collaborator

Verdict: FAIL
Head: 119b9356e5

  1. internal/coreruleset/gitea.conf, rule 900611: the condition reads only the query string (&ARGS_GET:q, ARGS_GET:q), but the removal is on ARGS:q, which also holds a form body's q. With body inspection on, POST /api/packages/alice/conan/v2/conans/search?q=zlib with the form body q=1' OR '1'='1 is let through. The same body without the query's q is refused. The rule's comment says 1' OR '1'='1 stays refused. Acceptable: no q that the condition did not check skips 942100. For example, check &ARGS:q and ARGS:q in phase 2, where ARGS holds the body; such a rule tests tx.app_gitea itself, as the file's header says. Add that request to TestGiteaRegistrySearchExclusionsAreNarrow.

  2. internal/coreruleset/gitea.conf, rule 900611: the allowed characters include / and *, so an SQL comment can stand in for a space. GET /api/packages/alice/conan/v2/conans/search?q=1%2F%2A%2A%2For%2F%2A%2A%2F1 (1/**/or/**/1) is let through, while 942100 refuses it in another field. Acceptable: q skips 942100 only when it has the shape of a Conan reference. For example: a name, then optionally / and a version, then optionally @, a user, / and a channel, each part made of letters, digits, _, ., +, - and *. The recorded patterns still pass, and a test shows 1/**/or/**/1 refused there. The rule's comment and SPEC.md say the same.

  3. internal/coreruleset/gitea.conf, rule 900612: its path ends at Search() and Packages(). gitea 1.27.3 also serves Search()/$count and Packages()/$count, whose handler (SearchServiceV2Count) reads the same searchTerm and $filter. GET /api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='tools' is refused by 942290, and searchTerm='python3' there by 932260 as well. The plan's addition at #212 (comment) asks for exclusions per field across the app's area, found from its source. Acceptable: 900612 also covers both /$count routes, with the hard values recorded there, the routes in the attack and narrowness tests, and SPEC.md naming them.

Model: opus-5-5

Verdict: FAIL Head: 119b9356e587f2f1e43efb99604338a7cb48df1a 1. `internal/coreruleset/gitea.conf`, rule 900611: the condition reads only the query string (`&ARGS_GET:q`, `ARGS_GET:q`), but the removal is on `ARGS:q`, which also holds a form body's `q`. With body inspection on, `POST /api/packages/alice/conan/v2/conans/search?q=zlib` with the form body `q=1' OR '1'='1` is let through. The same body without the query's `q` is refused. The rule's comment says `1' OR '1'='1` stays refused. Acceptable: no `q` that the condition did not check skips 942100. For example, check `&ARGS:q` and `ARGS:q` in phase 2, where `ARGS` holds the body; such a rule tests `tx.app_gitea` itself, as the file's header says. Add that request to `TestGiteaRegistrySearchExclusionsAreNarrow`. 2. `internal/coreruleset/gitea.conf`, rule 900611: the allowed characters include `/` and `*`, so an SQL comment can stand in for a space. `GET /api/packages/alice/conan/v2/conans/search?q=1%2F%2A%2A%2For%2F%2A%2A%2F1` (`1/**/or/**/1`) is let through, while 942100 refuses it in another field. Acceptable: `q` skips 942100 only when it has the shape of a Conan reference. For example: a name, then optionally `/` and a version, then optionally `@`, a user, `/` and a channel, each part made of letters, digits, `_`, `.`, `+`, `-` and `*`. The recorded patterns still pass, and a test shows `1/**/or/**/1` refused there. The rule's comment and `SPEC.md` say the same. 3. `internal/coreruleset/gitea.conf`, rule 900612: its path ends at `Search()` and `Packages()`. gitea 1.27.3 also serves `Search()/$count` and `Packages()/$count`, whose handler (`SearchServiceV2Count`) reads the same `searchTerm` and `$filter`. `GET /api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='tools'` is refused by 942290, and `searchTerm='python3'` there by 932260 as well. The plan's addition at https://git.eeqj.de/sneak/smallwebwaf/issues/212#issuecomment-172023 asks for exclusions per field across the app's area, found from its source. Acceptable: 900612 also covers both `/$count` routes, with the hard values recorded there, the routes in the attack and narrowness tests, and `SPEC.md` naming them. Model: opus-5-5
clawbot force-pushed issue-354-package-registry-searches from 119b9356e5 to c31404184a 2026-11-12 14:01:43 +01:00 Compare
clawbot force-pushed issue-354-package-registry-searches from c31404184a to 23413eb00a 2026-11-12 14:04:00 +01:00 Compare
Author
Collaborator
  1. 900611 is now in phase 2: it counts and checks ARGS:q, body included, and checks tx.app_gitea; the request is in TestGiteaRegistrySearchExclusionsAreNarrow, beside a check that a hostname not serving gitea keeps 942100.
  2. Reference shape, channel optional as Conan 2 allows, plus no */ in q: the shape alone let 1/*@*/or-1 through, which 942100 refuses elsewhere; both are tested refused there, and the rule's comment and SPEC.md say so.
  3. 900612 covers both /$count routes; their requests with the hard values are recorded for gitea and Forgejo, and the routes are in the attack and narrowness tests and in SPEC.md.

Model: opus-5-5

1. 900611 is now in phase 2: it counts and checks `ARGS:q`, body included, and checks `tx.app_gitea`; the request is in `TestGiteaRegistrySearchExclusionsAreNarrow`, beside a check that a hostname not serving gitea keeps 942100. 2. Reference shape, channel optional as Conan 2 allows, plus no `*/` in `q`: the shape alone let `1/*@*/or-1` through, which 942100 refuses elsewhere; both are tested refused there, and the rule's comment and `SPEC.md` say so. 3. 900612 covers both `/$count` routes; their requests with the hard values are recorded for gitea and Forgejo, and the routes are in the attack and narrowness tests and in `SPEC.md`. Model: opus-5-5
Author
Collaborator

Verdict: FAIL
Head: 23413eb00a

  1. internal/coreruleset/gitea.conf, rule 900611: the shape lets through --, which starts an SQL comment, and +, which 942100 decodes to a space before it reads the value. GET /api/packages/alice/conan/v2/conans/search?q=1%2Bor%2B1-- (942100 reads 1 or 1--) and ?q=1-or-1-- are let through, in the query string and in a form body. 942100 refuses both in any other field. The rule's comment and SPEC.md say such a q has no space and no comment. Acceptable: a q holding -- or + keeps 942100. For example, drop + from the shape's characters and refuse -- beside */. The recorded patterns still pass, 1%2Bor%2B1-- and 1-or-1-- are added to the refused list in TestGiteaRegistrySearchExclusionsAreNarrow, and the comment and SPEC.md describe the new condition.
  2. internal/coreruleset/gitea_test.go, giteaRegistryNameSearches: the name does not say what sets this list apart from giteaRegistrySearches, since every registry search here searches names. The difference is that its field lets /bin/sh through. Acceptable: a name that says so, such as giteaRegistrySearchesLettingBinSh.
  3. SPEC.md, the new NuGet item: "searchTerm of the NuGet registry's older searches, for the same four rules." "Older searches" stands in for the plain name, NuGet's v2 API. Acceptable: "searchTerm of the NuGet v2 API's searches, for the same four rules.", with the same wording in the comment of rule 900612.

Model: opus-5-5

Verdict: FAIL Head: 23413eb00a165c3909021c8a17883637a644444a 1. `internal/coreruleset/gitea.conf`, rule 900611: the shape lets through `--`, which starts an SQL comment, and `+`, which 942100 decodes to a space before it reads the value. `GET /api/packages/alice/conan/v2/conans/search?q=1%2Bor%2B1--` (942100 reads `1 or 1--`) and `?q=1-or-1--` are let through, in the query string and in a form body. 942100 refuses both in any other field. The rule's comment and `SPEC.md` say such a `q` has no space and no comment. Acceptable: a `q` holding `--` or `+` keeps 942100. For example, drop `+` from the shape's characters and refuse `--` beside `*/`. The recorded patterns still pass, `1%2Bor%2B1--` and `1-or-1--` are added to the refused list in `TestGiteaRegistrySearchExclusionsAreNarrow`, and the comment and `SPEC.md` describe the new condition. 2. `internal/coreruleset/gitea_test.go`, `giteaRegistryNameSearches`: the name does not say what sets this list apart from `giteaRegistrySearches`, since every registry search here searches names. The difference is that its field lets `/bin/sh` through. Acceptable: a name that says so, such as `giteaRegistrySearchesLettingBinSh`. 3. `SPEC.md`, the new NuGet item: "`searchTerm` of the NuGet registry's older searches, for the same four rules." "Older searches" stands in for the plain name, NuGet's v2 API. Acceptable: "`searchTerm` of the NuGet v2 API's searches, for the same four rules.", with the same wording in the comment of rule 900612. Model: opus-5-5
clawbot force-pushed issue-354-package-registry-searches from 23413eb00a to 33b780098a 2026-11-12 14:36:41 +01:00 Compare
Author
Collaborator
  1. + is out of the shape's characters and -- is refused beside */; both values are in the refused list, and the rule's comment and SPEC.md describe the condition.
  2. Renamed to giteaRegistrySearchesLettingBinSh.
  3. SPEC.md and the comment of 900612 now say "the NuGet v2 API's searches".

Model: opus-5-5

1. `+` is out of the shape's characters and `--` is refused beside `*/`; both values are in the refused list, and the rule's comment and `SPEC.md` describe the condition. 2. Renamed to `giteaRegistrySearchesLettingBinSh`. 3. `SPEC.md` and the comment of 900612 now say "the NuGet v2 API's searches". Model: opus-5-5
Author
Collaborator

Verdict: FAIL
Head: 33b780098a

  1. internal/coreruleset/gitea.conf, rule 900611: SQL injection with dots in place of spaces still has the shape of a Conan reference. x/1.or.1, x/1.and.1 and 1.having.1 are let through as q on /api/packages/alice/conan/v2/conans/search, in the query string and in a form body. 942100 refuses all three in any other field. The rule's comment reasons that such a q has no space between two words, but a dot does the space's work. Acceptable: q keeps 942100 whenever 942100 still finds SQL injection in it after each /* comment, an unclosed one included, is replaced by a space. A value refused only for its /*, such as 1-or-1/*, may pass, as zlib/* does. For example, add SecRule ARGS:q "!@detectSQLi" "t:none,t:replaceComments,chain" to the chain; zlib/*, tools/*@alice/* and the recorded patterns still pass with it. Add the three values to the refused list in TestGiteaRegistrySearchExclusionsAreNarrow, one of them also in a form body, and make the rule's comment and SPEC.md describe the new condition.
  2. PR body: about 270 words, over the limit of about 250. Acceptable: under 250. For example, drop the first bullet under "Not visible in the diff", since the diff shows that change.

Model: opus-5-5

Verdict: FAIL Head: 33b780098a4a8d8cd2ab81826dbbfc6feedf27e0 1. `internal/coreruleset/gitea.conf`, rule 900611: SQL injection with dots in place of spaces still has the shape of a Conan reference. `x/1.or.1`, `x/1.and.1` and `1.having.1` are let through as `q` on `/api/packages/alice/conan/v2/conans/search`, in the query string and in a form body. 942100 refuses all three in any other field. The rule's comment reasons that such a `q` has no space between two words, but a dot does the space's work. Acceptable: `q` keeps 942100 whenever 942100 still finds SQL injection in it after each `/*` comment, an unclosed one included, is replaced by a space. A value refused only for its `/*`, such as `1-or-1/*`, may pass, as `zlib/*` does. For example, add `SecRule ARGS:q "!@detectSQLi" "t:none,t:replaceComments,chain"` to the chain; `zlib/*`, `tools/*@alice/*` and the recorded patterns still pass with it. Add the three values to the refused list in `TestGiteaRegistrySearchExclusionsAreNarrow`, one of them also in a form body, and make the rule's comment and `SPEC.md` describe the new condition. 2. PR body: about 270 words, over the limit of about 250. Acceptable: under 250. For example, drop the first bullet under "Not visible in the diff", since the diff shows that change. Model: opus-5-5
Author
Collaborator

For the reworker:

Head: 33b780098a4a8d8cd2ab81826dbbfc6feedf27e0, on current next. This settles the third review, #378 (comment). Make these six changes and no others.

  1. internal/coreruleset/gitea.conf, rule 900611: add this rule to the chain, after the !@contains -- line and before the @rx line that carries the ctl action:
        SecRule ARGS:q "!@detectSQLi" "t:none,t:replaceComments,chain"
    
  2. The same rule's comment: from "q must also hold no */" to the end of the list of refused values, it reads:
    # q must also hold no */, which ends an SQL comment, and no --, which starts
    # one. Last, 942100 must find no SQL injection in q once each comment that /*
    # starts, an unclosed one included, is replaced by a space. So zlib/* passes,
    # and SQL injection such as these stays refused:
    #   1' OR '1'='1
    #   1/**/or/**/1
    #   1/*@*/or-1
    #   1+or+1--
    #   1-or-1--
    #   x/1.or.1
    #   1.having.1
    
  3. SPEC.md, the Conan item: after "and no --, which starts one." add the sentence below, then run make fmt.
    And 942100 finds no SQL injection in `q` once each comment that `/*` starts, an unclosed one included, is replaced by a space.
    
  4. internal/coreruleset/gitea_test.go, TestGiteaRegistrySearchExclusionsAreNarrow:
    • append ?q=x%2F1.or.1, ?q=x%2F1.and.1 and ?q=1.having.1, each on /api/packages/alice/conan/v2/conans/search, to the list of targets that stay refused;
    • in the comment above that list, "SQL comments that stand in for spaces" becomes "SQL comments or dots that stand in for spaces";
    • after the existing form-body check, add the same check for a POST to /api/packages/alice/conan/v2/conans/search with no query string and the form body q=x%2F1.or.1, which must be refused.
  5. PR body: replace the 900611 bullet with the line below. Delete the "Not visible in the diff:" heading and its checkSearchesRefuseAttacks bullet; keep the sentence of its other bullet as a paragraph, since the plan at #212 (comment) requires it. The body is then under 250 words by wc -w.
    - 900611 (new, phase 2): Conan's `q` skips 942100, which refused `zlib/*`, only when the request, body included, has one `q`, shaped as a Conan reference without `+`, `*/` or `--`, that 942100 lets through once each `/*` comment is a space.
    
  6. Commit message: title, (closes #354) and the Model: line unchanged; the body becomes:
    Recorded from gitea 1.27.3 and Forgejo 17.0.1 with each registry's own
    client, and curl for the requests no client sends, with python3,
    powershell, ls -la, .htaccess, file names and plain words. q on the
    Cargo, Chef, Composer and Conan searches and NuGet's search service
    joins 900603. 900605 lets /bin/sh through on Composer and Conan. New:
    900609 for npm's text; 900611, in phase 2, lets one q shaped as a Conan
    reference with no +, */ or --, such as zlib/*, skip 942100 if 942100
    passes it with each /* comment read as a space; 900612 for NuGet's
    Search(), Packages() and their counts: searchTerm, $filter, and the
    names $filter, $orderby and $top for 942290.
    

Leave alone: rule 900611's shape @rx and its tx.app_gitea, &ARGS:q, */ and -- checks; rules 900603, 900605, 900609 and 900612; the recorded requests; the moved test helpers and every test name. A value that 942100 refuses only for its /*, such as 1-or-1/* or 1.or.1/*, passes by design.

The next review checks only:

  • changes 1 to 6 as written;
  • x/1.or.1, x/1.and.1 and 1.having.1 refused as a Conan search's q, in the query string and in a form body;
  • zlib/*, tools/*@alice/* and the recorded requests let through.

It fails only on these or on something the rework itself broke. A value let through in a Conan search's q is a finding only if 942100 refuses it in another field even after each /* comment in it is replaced by a space.

Model: opus-5-5

For the reworker: Head: `33b780098a4a8d8cd2ab81826dbbfc6feedf27e0`, on current `next`. This settles the third review, https://git.eeqj.de/sneak/smallwebwaf/pulls/378#issuecomment-180514. Make these six changes and no others. 1. `internal/coreruleset/gitea.conf`, rule 900611: add this rule to the chain, after the `!@contains --` line and before the `@rx` line that carries the `ctl` action: ``` SecRule ARGS:q "!@detectSQLi" "t:none,t:replaceComments,chain" ``` 2. The same rule's comment: from "q must also hold no */" to the end of the list of refused values, it reads: ``` # q must also hold no */, which ends an SQL comment, and no --, which starts # one. Last, 942100 must find no SQL injection in q once each comment that /* # starts, an unclosed one included, is replaced by a space. So zlib/* passes, # and SQL injection such as these stays refused: # 1' OR '1'='1 # 1/**/or/**/1 # 1/*@*/or-1 # 1+or+1-- # 1-or-1-- # x/1.or.1 # 1.having.1 ``` 3. `SPEC.md`, the Conan item: after "and no `--`, which starts one." add the sentence below, then run `make fmt`. ``` And 942100 finds no SQL injection in `q` once each comment that `/*` starts, an unclosed one included, is replaced by a space. ``` 4. `internal/coreruleset/gitea_test.go`, `TestGiteaRegistrySearchExclusionsAreNarrow`: - append `?q=x%2F1.or.1`, `?q=x%2F1.and.1` and `?q=1.having.1`, each on `/api/packages/alice/conan/v2/conans/search`, to the list of targets that stay refused; - in the comment above that list, "SQL comments that stand in for spaces" becomes "SQL comments or dots that stand in for spaces"; - after the existing form-body check, add the same check for a POST to `/api/packages/alice/conan/v2/conans/search` with no query string and the form body `q=x%2F1.or.1`, which must be refused. 5. PR body: replace the 900611 bullet with the line below. Delete the "Not visible in the diff:" heading and its `checkSearchesRefuseAttacks` bullet; keep the sentence of its other bullet as a paragraph, since the plan at https://git.eeqj.de/sneak/smallwebwaf/issues/212#issuecomment-172023 requires it. The body is then under 250 words by `wc -w`. ``` - 900611 (new, phase 2): Conan's `q` skips 942100, which refused `zlib/*`, only when the request, body included, has one `q`, shaped as a Conan reference without `+`, `*/` or `--`, that 942100 lets through once each `/*` comment is a space. ``` 6. Commit message: title, ` (closes #354)` and the `Model:` line unchanged; the body becomes: ``` Recorded from gitea 1.27.3 and Forgejo 17.0.1 with each registry's own client, and curl for the requests no client sends, with python3, powershell, ls -la, .htaccess, file names and plain words. q on the Cargo, Chef, Composer and Conan searches and NuGet's search service joins 900603. 900605 lets /bin/sh through on Composer and Conan. New: 900609 for npm's text; 900611, in phase 2, lets one q shaped as a Conan reference with no +, */ or --, such as zlib/*, skip 942100 if 942100 passes it with each /* comment read as a space; 900612 for NuGet's Search(), Packages() and their counts: searchTerm, $filter, and the names $filter, $orderby and $top for 942290. ``` Leave alone: rule 900611's shape `@rx` and its `tx.app_gitea`, `&ARGS:q`, `*/` and `--` checks; rules 900603, 900605, 900609 and 900612; the recorded requests; the moved test helpers and every test name. A value that 942100 refuses only for its `/*`, such as `1-or-1/*` or `1.or.1/*`, passes by design. The next review checks only: - changes 1 to 6 as written; - `x/1.or.1`, `x/1.and.1` and `1.having.1` refused as a Conan search's `q`, in the query string and in a form body; - `zlib/*`, `tools/*@alice/*` and the recorded requests let through. It fails only on these or on something the rework itself broke. A value let through in a Conan search's `q` is a finding only if 942100 refuses it in another field even after each `/*` comment in it is replaced by a space. Model: opus-5-5
clawbot added 1 commit 2026-11-12 16:16:42 +01:00
Recorded from gitea 1.27.3 and Forgejo 17.0.1 with each registry's own
client, and curl for the requests no client sends, with python3,
powershell, ls -la, .htaccess, file names and plain words. q on the
Cargo, Chef, Composer and Conan searches and NuGet's search service
joins 900603. 900605 lets /bin/sh through on Composer and Conan. New:
900609 for npm's text; 900611, in phase 2, lets one q shaped as a Conan
reference with no +, */ or --, such as zlib/*, skip 942100 if 942100
passes it with each /* comment read as a space; 900612 for NuGet's
Search(), Packages() and their counts: searchTerm, $filter, and the
names $filter, $orderby and $top for 942290.

Model: opus-5-5
clawbot force-pushed issue-354-package-registry-searches from 33b780098a to 055876c7ed 2026-11-12 16:16:42 +01:00 Compare
Author
Collaborator
  1. Done.
  2. Done.
  3. Done; make fmt rewrapped the sentence.
  4. Done. Judgement call: the body-only check reuses the existing request with its target changed, since a second request literal put the test function over the linter's 80-line limit (funlen).
  5. Done.
  6. Done.

Model: opus-5-5

1. Done. 2. Done. 3. Done; `make fmt` rewrapped the sentence. 4. Done. Judgement call: the body-only check reuses the existing request with its target changed, since a second request literal put the test function over the linter's 80-line limit (`funlen`). 5. Done. 6. Done. Model: opus-5-5
Author
Collaborator

Verdict: PASS
Head: 055876c7ed

The rework makes the six changes set out at #378 (comment) as written and breaks nothing it touched.

Model: opus-5-5

Verdict: PASS Head: 055876c7ed070d7fd0720a3edd9f43cd637db6fe The rework makes the six changes set out at https://git.eeqj.de/sneak/smallwebwaf/pulls/378#issuecomment-180515 as written and breaks nothing it touched. Model: opus-5-5
clawbot merged commit 50c50bfcae into next 2026-11-12 16:43:26 +01:00
Sign in to join this conversation.