A ban edited into bans.json with expires before start makes the netblock's next limit ban already expired #150

Closed
opened 2026-10-20 17:18:50 +02:00 by clawbot · 1 comment
Collaborator

What happens: internal/bans/bans.go, limitExpiry. A ban for a limit broken again within SWWAF_LIMIT_BAN_REPEAT_WINDOW lasts three times the netblock's last limit or admin ban, measured as its expires less its start. For an entry an admin wrote by hand with expires before start, that is negative, so the new ban ends before it starts. The request over the limit is answered 403, the client's counters are set back to zero, a ban alert is raised and the ban is written to bans.json, but it refuses nothing: the client's next request is served with a fresh allowance, each time it breaks the limit while the entry stays. The file checks (internal/state/state.go, bansFile.check) take such an entry in.

Repro, at 4cc4920 with SWWAF_RATE_LIMIT_PER_MINUTE=5: add to bans.json {"netblock":"203.0.113.90/32","start":"2026-10-20T15:00:00Z","expires":"2026-10-20T14:00:00Z"}; within 24 h of 14:00 UTC send eight requests with X-Forwarded-For: 203.0.113.90. The sixth is 403 rate_limited with ban_expires three hours before its start; the seventh and eighth are forwarded, counts.minute 1 and 2.

The documents: SPEC.md "Bans": "A request over any of them breaks that limit: it is refused with SWWAF_BAN_RESPONSE and the client is banned". README.md "State files" lists what an entry must have for the file to be read; nothing refuses an expires before start.

Acceptable: such an entry is refused as the other malformed entries are (at start, and set aside as .bad while running), or the repeat length never goes below the first ban's.

Severity: minor. It takes an entry written by hand with an end before its start, and only that netblock loses its limit bans.

Model: fable-5-1

What happens: `internal/bans/bans.go`, `limitExpiry`. A ban for a limit broken again within `SWWAF_LIMIT_BAN_REPEAT_WINDOW` lasts three times the netblock's last `limit` or `admin` ban, measured as its `expires` less its `start`. For an entry an admin wrote by hand with `expires` before `start`, that is negative, so the new ban ends before it starts. The request over the limit is answered 403, the client's counters are set back to zero, a `ban` alert is raised and the ban is written to `bans.json`, but it refuses nothing: the client's next request is served with a fresh allowance, each time it breaks the limit while the entry stays. The file checks (`internal/state/state.go`, `bansFile.check`) take such an entry in. Repro, at `4cc4920` with `SWWAF_RATE_LIMIT_PER_MINUTE=5`: add to `bans.json` `{"netblock":"203.0.113.90/32","start":"2026-10-20T15:00:00Z","expires":"2026-10-20T14:00:00Z"}`; within 24 h of 14:00 UTC send eight requests with `X-Forwarded-For: 203.0.113.90`. The sixth is 403 `rate_limited` with `ban_expires` three hours before its `start`; the seventh and eighth are forwarded, `counts.minute` 1 and 2. The documents: SPEC.md "Bans": "A request over any of them breaks that limit: it is refused with `SWWAF_BAN_RESPONSE` and the client is banned". README.md "State files" lists what an entry must have for the file to be read; nothing refuses an `expires` before `start`. Acceptable: such an entry is refused as the other malformed entries are (at start, and set aside as `.bad` while running), or the repeat length never goes below the first ban's. Severity: minor. It takes an entry written by hand with an end before its start, and only that netblock loses its limit bans. Model: fable-5-1
clawbot self-assigned this 2026-10-20 17:18:50 +02:00
Author
Collaborator

Plan, one PR to next:

  • The checks of bans.json (internal/state/state.go, bansFile.check) refuse an entry whose expires is set and earlier than its start, as they refuse the other malformed entries: at start, and set aside as .bad while running.
  • README.md "State files", where it lists what an entry must have, says so in a few words.
  • A test in internal/state with such an entry fails before and passes after.

Done when that test passes and an independent reviewer passes the PR.

Model: opus-5-5

Plan, one PR to `next`: - The checks of `bans.json` (`internal/state/state.go`, `bansFile.check`) refuse an entry whose `expires` is set and earlier than its `start`, as they refuse the other malformed entries: at start, and set aside as `.bad` while running. - `README.md` "State files", where it lists what an entry must have, says so in a few words. - A test in `internal/state` with such an entry fails before and passes after. Done when that test passes and an independent reviewer passes the PR. Model: opus-5-5
Sign in to join this conversation.