Adds SWWAF_RATE_LIMIT_EXEMPT_PATHS for #77: a comma-separated list of path prefixes, empty by default. A request whose path as sent, the path the app receives, before the query string and not percent-decoded, starts with one of them is neither counted nor refused by the request rate limits; SWWAF_DENY_NETS, bans and the country lists still refuse it. A prefix not starting with / stops the start.
As the corrections on the issue rule, a request whose decoded path contains .. anywhere or a backslash, or whose path as sent holds %2F or %2f, is never exempt: an app may act on /assets/..%2Flogin as /login, and on /assets%2Fx as one path segment.
Matching the path as sent keeps /%61ssets/x counted, since a router that matches the path as received serves it outside /assets/.
The path is not resolved: /assets/ matches /assets//x, but not //assets/x.
No wildcards, case counts, and a prefix matches only at the start.
The exemption joins SWWAF_RATE_LIMIT_EXEMPT_NETS in checkClient, at the rate-limit step only; observe mode never logs such a request as one the rate limits would refuse.
Disclosures:
Judgement call: the backslash check is on the decoded path, so an encoded backslash (%5C) is never exempt either.
Judgement call: the path as sent is read as URL.EscapedPath(), the path the app receives; it differs from the client's bytes only when Go re-encodes a path it cannot pass on as sent.
Model: opus-5-5
Adds `SWWAF_RATE_LIMIT_EXEMPT_PATHS` for https://git.eeqj.de/sneak/smallwebwaf/issues/77: a comma-separated list of path prefixes, empty by default. A request whose path as sent, the path the app receives, before the query string and not percent-decoded, starts with one of them is neither counted nor refused by the request rate limits; `SWWAF_DENY_NETS`, bans and the country lists still refuse it. A prefix not starting with `/` stops the start.
- As the corrections on the issue rule, a request whose decoded path contains `..` anywhere or a backslash, or whose path as sent holds `%2F` or `%2f`, is never exempt: an app may act on `/assets/..%2Flogin` as `/login`, and on `/assets%2Fx` as one path segment.
- Matching the path as sent keeps `/%61ssets/x` counted, since a router that matches the path as received serves it outside `/assets/`.
- The path is not resolved: `/assets/` matches `/assets//x`, but not `//assets/x`.
- No wildcards, case counts, and a prefix matches only at the start.
- The exemption joins `SWWAF_RATE_LIMIT_EXEMPT_NETS` in `checkClient`, at the rate-limit step only; `observe` mode never logs such a request as one the rate limits would refuse.
Disclosures:
- Judgement call: the backslash check is on the decoded path, so an encoded backslash (`%5C`) is never exempt either.
- Judgement call: the path as sent is read as `URL.EscapedPath()`, the path the app receives; it differs from the client's bytes only when Go re-encodes a path it cannot pass on as sent.
Model: opus-5-5
internal/proxy/request.go, check: a client can escape the rate limits through an exempt prefix. The prefix is compared with the path as sent, and that same path goes to the app unchanged, so with /assets/ set, /assets/..%2Flogin (and /assets/%2e%2e/login, /assets/../login) is never counted, while an app that decodes and resolves the path, as nginx does, serves /login. traefik's default cleaning removes .. segments, written plainly or as %2e%2e, but leaves ..%2F as it is, and a request that reaches smallwebwaf without that cleaning gets all three through. Acceptable: exempt a request only when the path the app will act on starts with the prefix, that is the percent-decoded path with its . and .. segments and repeated slashes resolved, or never exempt a path whose decoded form holds a . or .. segment; tests that see those three paths counted; README.md, the code comment and the commit message describing the path that is matched. (Today's "as the client sent it, character for character" is also not what the log's path holds for characters Go encodes again, such as non-ASCII ones.)
internal/proxy/ratelimits_test.go: no test shows the country lists still refusing a path under a prefix. It needs neither GeoJS nor the real clock: load the client's country with server.GeoJS.Load before the request, as metrics_test.go does. Acceptable: a test in which a client from a denied country asking for a path under a prefix is refused with country_denied, and the PR body's disclosure removed.
internal/proxy/ratelimits_test.go: no test shows that a prefix matches only at the start of the path. Acceptable: the test also sends a path that holds a prefix after its start, such as /static/assets/app.js, which README.md says is not matched, and sees it counted.
Judgement call: finding 1 departs from the issue's "path as received", since that matching lets a client escape the limits for paths the app serves outside the prefix.
Model: opus-5-5
Review failed: needs rework.
1. `internal/proxy/request.go`, `check`: a client can escape the rate limits through an exempt prefix. The prefix is compared with the path as sent, and that same path goes to the app unchanged, so with `/assets/` set, `/assets/..%2Flogin` (and `/assets/%2e%2e/login`, `/assets/../login`) is never counted, while an app that decodes and resolves the path, as nginx does, serves `/login`. traefik's default cleaning removes `..` segments, written plainly or as `%2e%2e`, but leaves `..%2F` as it is, and a request that reaches `smallwebwaf` without that cleaning gets all three through. Acceptable: exempt a request only when the path the app will act on starts with the prefix, that is the percent-decoded path with its `.` and `..` segments and repeated slashes resolved, or never exempt a path whose decoded form holds a `.` or `..` segment; tests that see those three paths counted; `README.md`, the code comment and the commit message describing the path that is matched. (Today's "as the client sent it, character for character" is also not what the log's `path` holds for characters Go encodes again, such as non-ASCII ones.)
2. `internal/proxy/ratelimits_test.go`: no test shows the country lists still refusing a path under a prefix. It needs neither GeoJS nor the real clock: load the client's country with `server.GeoJS.Load` before the request, as `metrics_test.go` does. Acceptable: a test in which a client from a denied country asking for a path under a prefix is refused with `country_denied`, and the PR body's disclosure removed.
3. `internal/proxy/ratelimits_test.go`: no test shows that a prefix matches only at the start of the path. Acceptable: the test also sends a path that holds a prefix after its start, such as `/static/assets/app.js`, which `README.md` says is not matched, and sees it counted.
Judgement call: finding 1 departs from the issue's "path as received", since that matching lets a client escape the limits for paths the app serves outside the prefix.
Model: opus-5-5
Fixed: prefixes are matched against path.Clean of the percent-decoded URL.Path; /assets/../login, /assets/%2e%2e/login and /assets/..%2Flogin are tested counted, and README.md, the code comment and the commit message describe that path.
Fixed: the exempt-path test loads the client's country with server.GeoJS.Load and sees /assets/app.js refused with country_denied; the PR body's disclosure is gone.
Fixed: /static/assets/app.js is tested counted.
Rebased onto next: with observe mode landed, the exemption now sits in checkClient.
Model: opus-5-5
Rework of https://git.eeqj.de/sneak/smallwebwaf/pulls/80#issuecomment-129353:
1. Fixed: prefixes are matched against `path.Clean` of the percent-decoded `URL.Path`; `/assets/../login`, `/assets/%2e%2e/login` and `/assets/..%2Flogin` are tested counted, and `README.md`, the code comment and the commit message describe that path.
2. Fixed: the exempt-path test loads the client's country with `server.GeoJS.Load` and sees `/assets/app.js` refused with `country_denied`; the PR body's disclosure is gone.
3. Fixed: `/static/assets/app.js` is tested counted.
Rebased onto `next`: with `observe` mode landed, the exemption now sits in `checkClient`.
Model: opus-5-5
internal/proxy/request.go, checkClient: a client can still escape the rate limits through an exempt prefix. path.Clean of the percent-decoded path takes an encoded slash (%2F) for a slash and resolves .. across it, but an app that keeps %2F inside a path segment, as Go's own router and gitea's do, does not. With /assets/ set, /sneak/app/src/branch/main/..%2F..%2F..%2F..%2F..%2F..%2Fassets/x is matched as /assets/x and never counted, while the app's route for /{owner}/{repo}/src/... serves it; /assets%2Fx reaches a one-segment route such as /{page} the same way. traefik's defaults let %2F through, and its cleaning leaves ..%2F as it is. The disclosed /assets/..;/login, which an app that drops ; parameters (as Java servers do) serves as /login, is the same escape. Acceptable: count every request whose percent-decoded path contains .. anywhere, or whose path as sent holds an encoded slash, and match the rest as now; tests that see such paths counted; README.md and the code comment stating the rule (/static/../assets/app.js then no longer matches).
README.md, TODO: it still lists exemptions as milestone 3 work to come, while the status paragraph says the paths the rate limits do not count are built, and #77 is the build order's exemptions. Acceptable: the TODO no longer names exemptions.
Judgement calls:
Finding 1 goes past the matching rule in #77 (comment), since that rule still lets a client escape the limits for apps that keep %2F inside a segment.
Plain prefixes accepted, as the issue asks: /assets also matches /assetsX, and / exempts every path.
No test covers the exemption in observe mode; the definition of done does not ask for one.
Model: opus-5-5
Review failed: needs rework.
1. `internal/proxy/request.go`, `checkClient`: a client can still escape the rate limits through an exempt prefix. `path.Clean` of the percent-decoded path takes an encoded slash (`%2F`) for a slash and resolves `..` across it, but an app that keeps `%2F` inside a path segment, as Go's own router and gitea's do, does not. With `/assets/` set, `/sneak/app/src/branch/main/..%2F..%2F..%2F..%2F..%2F..%2Fassets/x` is matched as `/assets/x` and never counted, while the app's route for `/{owner}/{repo}/src/...` serves it; `/assets%2Fx` reaches a one-segment route such as `/{page}` the same way. traefik's defaults let `%2F` through, and its cleaning leaves `..%2F` as it is. The disclosed `/assets/..;/login`, which an app that drops `;` parameters (as Java servers do) serves as `/login`, is the same escape. Acceptable: count every request whose percent-decoded path contains `..` anywhere, or whose path as sent holds an encoded slash, and match the rest as now; tests that see such paths counted; `README.md` and the code comment stating the rule (`/static/../assets/app.js` then no longer matches).
2. `README.md`, TODO: it still lists exemptions as milestone 3 work to come, while the status paragraph says the paths the rate limits do not count are built, and https://git.eeqj.de/sneak/smallwebwaf/issues/77 is the build order's exemptions. Acceptable: the TODO no longer names exemptions.
Judgement calls:
- Finding 1 goes past the matching rule in https://git.eeqj.de/sneak/smallwebwaf/issues/77#issuecomment-129359, since that rule still lets a client escape the limits for apps that keep `%2F` inside a segment.
- Plain prefixes accepted, as the issue asks: `/assets` also matches `/assetsX`, and `/` exempts every path.
- No test covers the exemption in `observe` mode; the definition of done does not ask for one.
Model: opus-5-5
Fixed, following #77 (comment); the path is no longer resolved, and a test sees each named path counted.
Fixed.
Model: opus-5-5
Rework for https://git.eeqj.de/sneak/smallwebwaf/pulls/80#issuecomment-129508:
1. Fixed, following https://git.eeqj.de/sneak/smallwebwaf/issues/77#issuecomment-129519; the path is no longer resolved, and a test sees each named path counted.
2. Fixed.
Model: opus-5-5
internal/proxy/request.go, pathExempt: a client can still escape the rate limits through an exempt prefix, by percent-encoding a character of the prefix itself. The prefix is compared with the percent-decoded path, while the app receives the path as sent: with /assets/ set, /%61ssets/x is never counted and reaches the app as /%61ssets/x, which a router that matches the path as received (chi, which gitea uses, and Express) serves from a route outside /assets/, such as gitea's /{username}/{reponame}. traefik decodes such characters only from v3.4.1, and a request that reaches smallwebwaf without traefik is not decoded at all. Acceptable: compare the prefixes with the path as sent (URL.EscapedPath(), the path the app receives), keeping the checks for .., backslashes and encoded slashes as they are; a test that sees /%61ssets/x counted; README.md ("percent-decoded ... starts with a prefix", "A prefix is written without percent-encoding"), the code comment and the commit message stating that path.
Judgement calls:
Double-encoded paths such as /assets/%252e%252e/login stay exempt: only an app that decodes a path twice acts on them as a path outside the prefix.
"Path as sent" read as the path the app receives (URL.EscapedPath()), as the PR body discloses.
The PR body, at about 260 words, accepted as within about 250.
Model: opus-5-5
Review failed: needs rework.
1. `internal/proxy/request.go`, `pathExempt`: a client can still escape the rate limits through an exempt prefix, by percent-encoding a character of the prefix itself. The prefix is compared with the percent-decoded path, while the app receives the path as sent: with `/assets/` set, `/%61ssets/x` is never counted and reaches the app as `/%61ssets/x`, which a router that matches the path as received (chi, which gitea uses, and Express) serves from a route outside `/assets/`, such as gitea's `/{username}/{reponame}`. traefik decodes such characters only from v3.4.1, and a request that reaches `smallwebwaf` without traefik is not decoded at all. Acceptable: compare the prefixes with the path as sent (`URL.EscapedPath()`, the path the app receives), keeping the checks for `..`, backslashes and encoded slashes as they are; a test that sees `/%61ssets/x` counted; `README.md` ("percent-decoded ... starts with a prefix", "A prefix is written without percent-encoding"), the code comment and the commit message stating that path.
Judgement calls:
- Double-encoded paths such as `/assets/%252e%252e/login` stay exempt: only an app that decodes a path twice acts on them as a path outside the prefix.
- "Path as sent" read as the path the app receives (`URL.EscapedPath()`), as the PR body discloses.
- The PR body, at about 260 words, accepted as within about 250.
Model: opus-5-5
#80 (comment): prefixes are now compared with the path as sent (URL.EscapedPath()), with the .., backslash and encoded-slash checks unchanged; a test sees /%61ssets/x counted, and README.md, the code comment and the commit message state that path.
Model: opus-5-5
https://git.eeqj.de/sneak/smallwebwaf/pulls/80#issuecomment-129716: prefixes are now compared with the path as sent (`URL.EscapedPath()`), with the `..`, backslash and encoded-slash checks unchanged; a test sees `/%61ssets/x` counted, and `README.md`, the code comment and the commit message state that path.
Model: opus-5-5
internal/proxy/ratelimits_test.go, TestRateLimitCountsPathsThatAreNotExempt: no test fails with the encoded-slash rule broken. /assets%2Fx and /assets%2fx do not start with /assets/ as sent, so they are counted whether or not pathExempt checks for %2F and %2f; with that check removed, or made case-sensitive, every test still passes, while the rule on #77 makes a request whose path as sent holds an encoded slash never exempt. Acceptable: the test also sends paths under the prefix that hold an encoded slash, such as /assets/x%2Fy and /assets/x%2fy, and sees them counted, with the comment above those cases saying what they show.
Judgement calls:
A prefix holding a character the path as sent always carries percent-encoded, such as a raw space or é, is accepted at start and never matches; not raised, since such requests are only counted, never wrongly exempt.
An app that takes its path from a request header such as X-Original-URL could serve an exempt request as another path; not raised, like the double-encoded paths already accepted.
Model: opus-5-5
Review failed: needs rework.
1. `internal/proxy/ratelimits_test.go`, `TestRateLimitCountsPathsThatAreNotExempt`: no test fails with the encoded-slash rule broken. `/assets%2Fx` and `/assets%2fx` do not start with `/assets/` as sent, so they are counted whether or not `pathExempt` checks for `%2F` and `%2f`; with that check removed, or made case-sensitive, every test still passes, while the rule on https://git.eeqj.de/sneak/smallwebwaf/issues/77 makes a request whose path as sent holds an encoded slash never exempt. Acceptable: the test also sends paths under the prefix that hold an encoded slash, such as `/assets/x%2Fy` and `/assets/x%2fy`, and sees them counted, with the comment above those cases saying what they show.
Judgement calls:
- A prefix holding a character the path as sent always carries percent-encoded, such as a raw space or `é`, is accepted at start and never matches; not raised, since such requests are only counted, never wrongly exempt.
- An app that takes its path from a request header such as `X-Original-URL` could serve an exempt request as another path; not raised, like the double-encoded paths already accepted.
Model: opus-5-5
A request is neither counted nor refused by the request rate limits
when its path as sent, the path the app receives, not percent-decoded,
starts with one of the comma-separated prefixes in
SWWAF_RATE_LIMIT_EXEMPT_PATHS, so /%61ssets/x is not under /assets/. A
request whose decoded path contains .. or a backslash, or whose path as
sent holds an encoded slash, is never exempt, since an app may act on
it as a path outside every prefix, such as /assets/..%2Flogin as
/login. The static lists, bans and the country lists still apply, and
its log line has no counts. The setting is empty by default, and a
prefix that does not start with / stops the start. README.md documents
it.
Model: opus-5-5
#80 (comment): TestRateLimitCountsPathsThatAreNotExempt now sends /assets/x%2Fy and /assets/x%2fy, under a comment saying they are under the prefix and counted for their encoded slash alone. Every other check in pathExempt already had a case that fails without it, so no other case was added.
Rebased onto next after #82: the TODO no longer lists exemptions; README.md and the exempt-path test now say such a request's log line has no counts; two rows of TestInvalidValueStopsTheStart now share a line, which keeps the function within the linter's length limit.
Model: opus-5-5
https://git.eeqj.de/sneak/smallwebwaf/pulls/80#issuecomment-129895: `TestRateLimitCountsPathsThatAreNotExempt` now sends `/assets/x%2Fy` and `/assets/x%2fy`, under a comment saying they are under the prefix and counted for their encoded slash alone. Every other check in `pathExempt` already had a case that fails without it, so no other case was added.
Rebased onto `next` after https://git.eeqj.de/sneak/smallwebwaf/pulls/82: the TODO no longer lists exemptions; `README.md` and the exempt-path test now say such a request's log line has no `counts`; two rows of `TestInvalidValueStopsTheStart` now share a line, which keeps the function within the linter's length limit.
Model: opus-5-5
Judgement call: two rows of TestInvalidValueStopsTheStart on one line accepted as plain, since that table already puts several rows for one setting on a line.
Judgement call: README.md does not say the prefixes are separated by commas, as it says this for none of its list settings; not raised.
Model: opus-5-5
Review passed.
- Judgement call: two rows of `TestInvalidValueStopsTheStart` on one line accepted as plain, since that table already puts several rows for one setting on a line.
- Judgement call: `README.md` does not say the prefixes are separated by commas, as it says this for none of its list settings; not raised.
Model: opus-5-5
clawbot
merged commit 74bdc6a449 into next2026-10-06 18:47:14 +02:00
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.
Adds
SWWAF_RATE_LIMIT_EXEMPT_PATHSfor #77: a comma-separated list of path prefixes, empty by default. A request whose path as sent, the path the app receives, before the query string and not percent-decoded, starts with one of them is neither counted nor refused by the request rate limits;SWWAF_DENY_NETS, bans and the country lists still refuse it. A prefix not starting with/stops the start...anywhere or a backslash, or whose path as sent holds%2For%2f, is never exempt: an app may act on/assets/..%2Floginas/login, and on/assets%2Fxas one path segment./%61ssets/xcounted, since a router that matches the path as received serves it outside/assets/./assets/matches/assets//x, but not//assets/x.SWWAF_RATE_LIMIT_EXEMPT_NETSincheckClient, at the rate-limit step only;observemode never logs such a request as one the rate limits would refuse.Disclosures:
%5C) is never exempt either.URL.EscapedPath(), the path the app receives; it differs from the client's bytes only when Go re-encodes a path it cannot pass on as sent.Model: opus-5-5
Review failed: needs rework.
internal/proxy/request.go,check: a client can escape the rate limits through an exempt prefix. The prefix is compared with the path as sent, and that same path goes to the app unchanged, so with/assets/set,/assets/..%2Flogin(and/assets/%2e%2e/login,/assets/../login) is never counted, while an app that decodes and resolves the path, as nginx does, serves/login. traefik's default cleaning removes..segments, written plainly or as%2e%2e, but leaves..%2Fas it is, and a request that reachessmallwebwafwithout that cleaning gets all three through. Acceptable: exempt a request only when the path the app will act on starts with the prefix, that is the percent-decoded path with its.and..segments and repeated slashes resolved, or never exempt a path whose decoded form holds a.or..segment; tests that see those three paths counted;README.md, the code comment and the commit message describing the path that is matched. (Today's "as the client sent it, character for character" is also not what the log'spathholds for characters Go encodes again, such as non-ASCII ones.)internal/proxy/ratelimits_test.go: no test shows the country lists still refusing a path under a prefix. It needs neither GeoJS nor the real clock: load the client's country withserver.GeoJS.Loadbefore the request, asmetrics_test.godoes. Acceptable: a test in which a client from a denied country asking for a path under a prefix is refused withcountry_denied, and the PR body's disclosure removed.internal/proxy/ratelimits_test.go: no test shows that a prefix matches only at the start of the path. Acceptable: the test also sends a path that holds a prefix after its start, such as/static/assets/app.js, whichREADME.mdsays is not matched, and sees it counted.Judgement call: finding 1 departs from the issue's "path as received", since that matching lets a client escape the limits for paths the app serves outside the prefix.
Model: opus-5-5
611ae5c008to890dcedfd7Rework of #80 (comment):
path.Cleanof the percent-decodedURL.Path;/assets/../login,/assets/%2e%2e/loginand/assets/..%2Floginare tested counted, andREADME.md, the code comment and the commit message describe that path.server.GeoJS.Loadand sees/assets/app.jsrefused withcountry_denied; the PR body's disclosure is gone./static/assets/app.jsis tested counted.Rebased onto
next: withobservemode landed, the exemption now sits incheckClient.Model: opus-5-5
Review failed: needs rework.
internal/proxy/request.go,checkClient: a client can still escape the rate limits through an exempt prefix.path.Cleanof the percent-decoded path takes an encoded slash (%2F) for a slash and resolves..across it, but an app that keeps%2Finside a path segment, as Go's own router and gitea's do, does not. With/assets/set,/sneak/app/src/branch/main/..%2F..%2F..%2F..%2F..%2F..%2Fassets/xis matched as/assets/xand never counted, while the app's route for/{owner}/{repo}/src/...serves it;/assets%2Fxreaches a one-segment route such as/{page}the same way. traefik's defaults let%2Fthrough, and its cleaning leaves..%2Fas it is. The disclosed/assets/..;/login, which an app that drops;parameters (as Java servers do) serves as/login, is the same escape. Acceptable: count every request whose percent-decoded path contains..anywhere, or whose path as sent holds an encoded slash, and match the rest as now; tests that see such paths counted;README.mdand the code comment stating the rule (/static/../assets/app.jsthen no longer matches).README.md, TODO: it still lists exemptions as milestone 3 work to come, while the status paragraph says the paths the rate limits do not count are built, and #77 is the build order's exemptions. Acceptable: the TODO no longer names exemptions.Judgement calls:
%2Finside a segment./assetsalso matches/assetsX, and/exempts every path.observemode; the definition of done does not ask for one.Model: opus-5-5
890dcedfd7toae5445f70fae5445f70ftofc469d96a9Rework for #80 (comment):
Model: opus-5-5
Review failed: needs rework.
internal/proxy/request.go,pathExempt: a client can still escape the rate limits through an exempt prefix, by percent-encoding a character of the prefix itself. The prefix is compared with the percent-decoded path, while the app receives the path as sent: with/assets/set,/%61ssets/xis never counted and reaches the app as/%61ssets/x, which a router that matches the path as received (chi, which gitea uses, and Express) serves from a route outside/assets/, such as gitea's/{username}/{reponame}. traefik decodes such characters only from v3.4.1, and a request that reachessmallwebwafwithout traefik is not decoded at all. Acceptable: compare the prefixes with the path as sent (URL.EscapedPath(), the path the app receives), keeping the checks for.., backslashes and encoded slashes as they are; a test that sees/%61ssets/xcounted;README.md("percent-decoded ... starts with a prefix", "A prefix is written without percent-encoding"), the code comment and the commit message stating that path.Judgement calls:
/assets/%252e%252e/loginstay exempt: only an app that decodes a path twice acts on them as a path outside the prefix.URL.EscapedPath()), as the PR body discloses.Model: opus-5-5
fc469d96a9toc26fffca1e#80 (comment): prefixes are now compared with the path as sent (
URL.EscapedPath()), with the.., backslash and encoded-slash checks unchanged; a test sees/%61ssets/xcounted, andREADME.md, the code comment and the commit message state that path.Model: opus-5-5
Review failed: needs rework.
internal/proxy/ratelimits_test.go,TestRateLimitCountsPathsThatAreNotExempt: no test fails with the encoded-slash rule broken./assets%2Fxand/assets%2fxdo not start with/assets/as sent, so they are counted whether or notpathExemptchecks for%2Fand%2f; with that check removed, or made case-sensitive, every test still passes, while the rule on #77 makes a request whose path as sent holds an encoded slash never exempt. Acceptable: the test also sends paths under the prefix that hold an encoded slash, such as/assets/x%2Fyand/assets/x%2fy, and sees them counted, with the comment above those cases saying what they show.Judgement calls:
é, is accepted at start and never matches; not raised, since such requests are only counted, never wrongly exempt.X-Original-URLcould serve an exempt request as another path; not raised, like the double-encoded paths already accepted.Model: opus-5-5
c26fffca1etoc12b901274#80 (comment):
TestRateLimitCountsPathsThatAreNotExemptnow sends/assets/x%2Fyand/assets/x%2fy, under a comment saying they are under the prefix and counted for their encoded slash alone. Every other check inpathExemptalready had a case that fails without it, so no other case was added.Rebased onto
nextafter #82: the TODO no longer lists exemptions;README.mdand the exempt-path test now say such a request's log line has nocounts; two rows ofTestInvalidValueStopsTheStartnow share a line, which keeps the function within the linter's length limit.Model: opus-5-5
Review passed.
TestInvalidValueStopsTheStarton one line accepted as plain, since that table already puts several rows for one setting on a line.README.mddoes not say the prefixes are separated by commas, as it says this for none of its list settings; not raised.Model: opus-5-5