smallwebwaf watches SWWAF_STATE_DIR with fsnotify and takes in an edit of
bans.json, clients.json or lookups.json as soon as it is saved, in place
of what it held. It tells its own writes from an admin's by the SHA-256
of what it last read or wrote, and each write takes in an edit made since
first. An edit that does not parse is renamed to <name>.bad at the
file's next write, which writes the file again from memory and logs the
file and where the error is. README.md says how to add and lift a ban.
Judgement call: a broken edit is set aside at the file's next write, not
when seen, since an editor's file can be read half written.
Judgement call: a state file that cannot be read is not written over.
Model: opus-5-5
Each client, one IPv4 address or one IPv6 /64, is counted in two buckets
per window, the earlier weighted by how much of it the window covers; at
most 20,000 clients are kept, least recently seen dropped first. A
request over SWWAF_RATE_LIMIT_PER_MINUTE, _HOUR or _DAY (1000, 10000,
50000, or off) gets 429 before reaching the app. Refused requests count,
413s included. A clock set back over a second behind a bucket's start
restarts that window. The log line gains limit_hit and the action
rate_limited.
Deviation from SPEC.md, per the issue: the 20,000 bound and /64 are fixed.
Judgement call: golang-lru/v2 holds the table; httprate does not count refused requests.
Deviation: go.mod and go.sum hand-written; no make target tidies them.
Model: opus-5-5
Milestone 1, the repo's first code. smallwebwaf passes each request to the app and the answer back unchanged, streaming bodies and WebSocket upgrades, within four timeouts (client and app, request and response) and two size limits, and writes one JSON line per request to stdout. Every setting has an SWWAF_ name and a default, and an invalid value stops the start. The repo gets the standard layout: script/ entrypoints, make targets that call them, a Dockerfile that runs the checks, and the Gitea workflow.
Disclosure: SPEC.md changed. Go's server reads the request line and headers before smallwebwaf sees the request, so slow headers are closed without an answer, and neither slow nor oversized headers get a log line.
Disclosure: standard library only.
Model: opus-5-5