Admin endpoints for bans and clients on the single listener #92

Merged
clawbot merged 1 commits from issue-27-admin-endpoints into next 2026-10-07 01:13:17 +02:00
Collaborator

Builds #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

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
clawbot added the needs-review label 2026-10-07 00:32:58 +02:00
clawbot self-assigned this 2026-10-07 00:32:58 +02:00
Author
Collaborator

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

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
clawbot added needs-rework and removed needs-review labels 2026-10-07 00:47:36 +02:00
clawbot added 1 commit 2026-10-07 01:01:25 +02:00
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
clawbot force-pushed issue-27-admin-endpoints from 9633ce99d2 to 5bf7802404 2026-10-07 01:01:25 +02:00 Compare
Author
Collaborator
  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

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
clawbot added needs-review and removed needs-rework labels 2026-10-07 01:02:16 +02:00
Author
Collaborator

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

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 next 2026-10-07 01:13:17 +02:00
clawbot deleted branch issue-27-admin-endpoints 2026-10-07 01:13:17 +02:00
Sign in to join this conversation.