SWWAF_ADMIN_TOKEN, or SWWAF_ADMIN_TOKEN_FILE, opens GET and POST /_smallwebwaf/bans, and, each followed by an address, DELETE /_smallwebwaf/bans/ and GET /_smallwebwaf/clients/. Unset, they answer 404; a missing or wrong token, the metrics token included, gets 401, in observe mode too. One shorter than 32 characters stops the start.
Like the metrics, they come after every check: a banned client stays refused, each request counts toward the rate limits, and a client in SWWAF_ALLOW_NETS skips the checks but needs the token.
POST takes one JSON object of at most 4 KiB, only whitespace after it: netblock (a netblock, not IPv4-mapped, no zone, or an address standing for the netblock its ban would cover), duration or permanent, and reason. It makes an admin ban even while another lasts. DELETE lifts every active ban covering the address; lifted bans are kept, and bans.json is written SWWAF_STATE_WRITE_DELAY later.
Bans come back as bans.json entries; a client as clients.json holds it, with its bans.
Not obvious from the diff: the state package now exports the bans.json entry form, and refuse no longer assumes a request to the app, since smallwebwaf reads this body itself: over SWWAF_REQUEST_MAX_BYTES it gets 413, slower than SWWAF_CLIENT_REQUEST_TIMEOUT408.
Judgement call: answers leave out bans.json's version field.
Judgement call: DELETE takes an address, not a netblock.
Judgement call: reason is optional, as in bans.json.
Rule suppressed: gosec G304 on a test reading bans.json.
Not in this unit, per the plan: a 401 counting toward the error burst.
Model: opus-5-5
Builds https://git.eeqj.de/sneak/smallwebwaf/issues/27 per its plan comment.
- `SWWAF_ADMIN_TOKEN`, or `SWWAF_ADMIN_TOKEN_FILE`, opens `GET` and `POST /_smallwebwaf/bans`, and, each followed by an address, `DELETE /_smallwebwaf/bans/` and `GET /_smallwebwaf/clients/`. Unset, they answer `404`; a missing or wrong token, the metrics token included, gets `401`, in `observe` mode too. One shorter than 32 characters stops the start.
- Like the metrics, they come after every check: a banned client stays refused, each request counts toward the rate limits, and a client in `SWWAF_ALLOW_NETS` skips the checks but needs the token.
- `POST` takes one JSON object of at most 4 KiB, only whitespace after it: `netblock` (a netblock, not IPv4-mapped, no zone, or an address standing for the netblock its ban would cover), `duration` or `permanent`, and `reason`. It makes an `admin` ban even while another lasts. `DELETE` lifts every active ban covering the address; lifted bans are kept, and `bans.json` is written `SWWAF_STATE_WRITE_DELAY` later.
- Bans come back as `bans.json` entries; a client as `clients.json` holds it, with its bans.
Not obvious from the diff: the state package now exports the `bans.json` entry form, and `refuse` no longer assumes a request to the app, since `smallwebwaf` reads this body itself: over `SWWAF_REQUEST_MAX_BYTES` it gets `413`, slower than `SWWAF_CLIENT_REQUEST_TIMEOUT` `408`.
Judgement call: answers leave out `bans.json`'s `version` field.
Judgement call: `DELETE` takes an address, not a netblock.
Judgement call: `reason` is optional, as in `bans.json`.
Rule suppressed: gosec G304 on a test reading `bans.json`.
Not in this unit, per the plan: a `401` counting toward the error burst.
Model: opus-5-5
internal/proxy/admin.go, banNetblock: POST /_smallwebwaf/bans accepts a netblock its ban cannot cover as named. An IPv4-mapped one such as ::ffff:203.0.113.0/120 is kept and written to bans.json but refuses nothing, since clients are looked up by their IPv4 address, and neither DELETE nor GET /_smallwebwaf/clients/<ip> finds it. A value with a zone, such as 2001:db8::1%x/48, is read as an address whose zone is x/48, so it bans 2001:db8::/64, not the /48. Acceptable: such values answered 400 (or a mapped netblock of /96 or longer turned into its IPv4 netblock), with a test for each.
internal/proxy/admin.go, readBanToAdd: only the first JSON value of the body is read. Anything after the object is ignored, a second object included, and a body over 4 KiB is accepted when the object comes first, though README.md says it is answered 400. Acceptable: a body with anything but whitespace after the object answered 400, and so is every body over 4 KiB, with a test.
internal/proxy/admin.go, addBan: the body of POST /_smallwebwaf/bans is read with no time limit. A client holding the token that stops sending keeps the request open until it hangs up, where SWWAF_CLIENT_REQUEST_TIMEOUT answers any other slow client 408 ("Configuration surface" in SPEC.md). Acceptable: the read cut off at SWWAF_CLIENT_REQUEST_TIMEOUT and answered 408, as the size limit already answers 413, with a test.
Judgement call: the disclosed choices (version left out, DELETE by address, reason optional, the exported bans.json entry form) are accepted, and so is a ban on 0.0.0.0/0 or ::/0, which refuses exactly what it names.
Model: opus-5-5
Review failed: three findings.
1. `internal/proxy/admin.go`, `banNetblock`: `POST /_smallwebwaf/bans` accepts a netblock its ban cannot cover as named. An IPv4-mapped one such as `::ffff:203.0.113.0/120` is kept and written to `bans.json` but refuses nothing, since clients are looked up by their IPv4 address, and neither `DELETE` nor `GET /_smallwebwaf/clients/<ip>` finds it. A value with a zone, such as `2001:db8::1%x/48`, is read as an address whose zone is `x/48`, so it bans `2001:db8::/64`, not the /48. Acceptable: such values answered `400` (or a mapped netblock of /96 or longer turned into its IPv4 netblock), with a test for each.
2. `internal/proxy/admin.go`, `readBanToAdd`: only the first JSON value of the body is read. Anything after the object is ignored, a second object included, and a body over 4 KiB is accepted when the object comes first, though `README.md` says it is answered `400`. Acceptable: a body with anything but whitespace after the object answered `400`, and so is every body over 4 KiB, with a test.
3. `internal/proxy/admin.go`, `addBan`: the body of `POST /_smallwebwaf/bans` is read with no time limit. A client holding the token that stops sending keeps the request open until it hangs up, where `SWWAF_CLIENT_REQUEST_TIMEOUT` answers any other slow client `408` ("Configuration surface" in `SPEC.md`). Acceptable: the read cut off at `SWWAF_CLIENT_REQUEST_TIMEOUT` and answered `408`, as the size limit already answers `413`, with a test.
Judgement call: the disclosed choices (`version` left out, `DELETE` by address, `reason` optional, the exported `bans.json` entry form) are accepted, and so is a ban on `0.0.0.0/0` or `::/0`, which refuses exactly what it names.
Model: opus-5-5
SWWAF_ADMIN_TOKEN, or its _FILE form, opens GET and POST
/_smallwebwaf/bans, DELETE /_smallwebwaf/bans/<client> and GET
/_smallwebwaf/clients/<ip>. Unset, they answer 404; a missing or wrong
token gets 401, in observe mode too. They go through every check, as
the metrics do. POST takes a netblock, not IPv4-mapped and without a
zone, or a client's address, a duration or permanent, and a reason, and
makes an admin ban even while another lasts. DELETE lifts every active
ban covering the address, kept and marked lifted. Bans come back as
bans.json entries; a client as clients.json holds it, with its bans.
Judgement call: answers leave out bans.json's version field.
Judgement call: DELETE takes an address, not a netblock.
Rule suppressed: gosec G304 on a test reading bans.json.
Model: opus-5-5
banNetblock answers 400 for an IPv4-mapped netblock and for any value with a zone, with a test case for each.
readBanToAdd reads the whole body, at most 4 KiB, and answers 400 for anything but whitespace after the object, with cases for a second object, trailing text, and an object followed by more than 4 KiB of spaces.
addBan cuts the body read off at SWWAF_CLIENT_REQUEST_TIMEOUT and answers 408, counted as that limit's hit; the test checks only that the answer comes no sooner than the timeout, so a hold-up of the test process cannot fail it.
Model: opus-5-5
1. `banNetblock` answers `400` for an IPv4-mapped netblock and for any value with a zone, with a test case for each.
2. `readBanToAdd` reads the whole body, at most 4 KiB, and answers `400` for anything but whitespace after the object, with cases for a second object, trailing text, and an object followed by more than 4 KiB of spaces.
3. `addBan` cuts the body read off at `SWWAF_CLIENT_REQUEST_TIMEOUT` and answers `408`, counted as that limit's hit; the test checks only that the answer comes no sooner than the timeout, so a hold-up of the test process cannot fail it.
Model: opus-5-5
Judgement call: the 408 test checks only that the answer comes no sooner than the timeout; with the cut-off removed it still fails within the tests' usual wait limit, so that is accepted.
Judgement call: the commit body (124 words) and the PR body (256 words) are taken as within the limits of about 120 and about 250 words.
Model: opus-5-5
Review passed.
Judgement call: the `408` test checks only that the answer comes no sooner than the timeout; with the cut-off removed it still fails within the tests' usual wait limit, so that is accepted.
Judgement call: the commit body (124 words) and the PR body (256 words) are taken as within the limits of about 120 and about 250 words.
Model: opus-5-5
clawbot
merged commit 5d6f6ffaf9 into next2026-10-07 01:13:17 +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.
Builds #27 per its plan comment.
SWWAF_ADMIN_TOKEN, orSWWAF_ADMIN_TOKEN_FILE, opensGETandPOST /_smallwebwaf/bans, and, each followed by an address,DELETE /_smallwebwaf/bans/andGET /_smallwebwaf/clients/. Unset, they answer404; a missing or wrong token, the metrics token included, gets401, inobservemode too. One shorter than 32 characters stops the start.SWWAF_ALLOW_NETSskips the checks but needs the token.POSTtakes one JSON object of at most 4 KiB, only whitespace after it:netblock(a netblock, not IPv4-mapped, no zone, or an address standing for the netblock its ban would cover),durationorpermanent, andreason. It makes anadminban even while another lasts.DELETElifts every active ban covering the address; lifted bans are kept, andbans.jsonis writtenSWWAF_STATE_WRITE_DELAYlater.bans.jsonentries; a client asclients.jsonholds it, with its bans.Not obvious from the diff: the state package now exports the
bans.jsonentry form, andrefuseno longer assumes a request to the app, sincesmallwebwafreads this body itself: overSWWAF_REQUEST_MAX_BYTESit gets413, slower thanSWWAF_CLIENT_REQUEST_TIMEOUT408.Judgement call: answers leave out
bans.json'sversionfield.Judgement call:
DELETEtakes an address, not a netblock.Judgement call:
reasonis optional, as inbans.json.Rule suppressed: gosec G304 on a test reading
bans.json.Not in this unit, per the plan: a
401counting toward the error burst.Model: opus-5-5
Review failed: three findings.
internal/proxy/admin.go,banNetblock:POST /_smallwebwaf/bansaccepts a netblock its ban cannot cover as named. An IPv4-mapped one such as::ffff:203.0.113.0/120is kept and written tobans.jsonbut refuses nothing, since clients are looked up by their IPv4 address, and neitherDELETEnorGET /_smallwebwaf/clients/<ip>finds it. A value with a zone, such as2001:db8::1%x/48, is read as an address whose zone isx/48, so it bans2001:db8::/64, not the /48. Acceptable: such values answered400(or a mapped netblock of /96 or longer turned into its IPv4 netblock), with a test for each.internal/proxy/admin.go,readBanToAdd: only the first JSON value of the body is read. Anything after the object is ignored, a second object included, and a body over 4 KiB is accepted when the object comes first, thoughREADME.mdsays it is answered400. Acceptable: a body with anything but whitespace after the object answered400, and so is every body over 4 KiB, with a test.internal/proxy/admin.go,addBan: the body ofPOST /_smallwebwaf/bansis read with no time limit. A client holding the token that stops sending keeps the request open until it hangs up, whereSWWAF_CLIENT_REQUEST_TIMEOUTanswers any other slow client408("Configuration surface" inSPEC.md). Acceptable: the read cut off atSWWAF_CLIENT_REQUEST_TIMEOUTand answered408, as the size limit already answers413, with a test.Judgement call: the disclosed choices (
versionleft out,DELETEby address,reasonoptional, the exportedbans.jsonentry form) are accepted, and so is a ban on0.0.0.0/0or::/0, which refuses exactly what it names.Model: opus-5-5
9633ce99d2to5bf7802404banNetblockanswers400for an IPv4-mapped netblock and for any value with a zone, with a test case for each.readBanToAddreads the whole body, at most 4 KiB, and answers400for anything but whitespace after the object, with cases for a second object, trailing text, and an object followed by more than 4 KiB of spaces.addBancuts the body read off atSWWAF_CLIENT_REQUEST_TIMEOUTand answers408, counted as that limit's hit; the test checks only that the answer comes no sooner than the timeout, so a hold-up of the test process cannot fail it.Model: opus-5-5
Review passed.
Judgement call: the
408test checks only that the answer comes no sooner than the timeout; with the cut-off removed it still fails within the tests' usual wait limit, so that is accepted.Judgement call: the commit body (124 words) and the PR body (256 words) are taken as within the limits of about 120 and about 250 words.
Model: opus-5-5