Harden http.Server: slowloris timeouts and form body limit (closes #92) #123

Merged
clawbot merged 4 commits from issue-92-server-hardening into next 2026-09-21 20:59:25 +02:00
4 Commits
Author SHA1 Message Date
sneak 345a002cc2 feat: cap form POST body size and return 413 (closes #92)
check / check (push) Failing after 0s
Add a LimitBody middleware that caps the request body at MaxFormBytes
(1 MiB) on POST / and POST /generate and rejects an oversized body with
413. It parses the form under the cap before the CSRF middleware, which
reads its token from the body with PostFormValue and would otherwise see a
truncated body as a missing token (403); a successful parse is cached, so
the CSRF check and handler reuse it. Wired ahead of CSRF in SetupRoutes.

This makes the limit explicit rather than resting on ParseForm's incidental
10 MB cap, which would silently vanish if a handler switched to io.ReadAll
or multipart.

Model: opus-4-8
2026-09-21 18:22:22 +00:00
sneak c53e53b7b1 test: reject oversized form POST body with 413 before CSRF parses it
Failing test first for the per-route body limit. The key regression:
an oversized POST / or POST /generate carrying an otherwise valid CSRF
cookie and token must return 413. CSRF landed on next and reads its token
from the form body, so if the limit ran after CSRF the truncated body
would read as a missing token (403) and if it ran after the handler a
valid token would succeed (303). 413 proves the limit fires first. Two
within-limit tests guard against the limit disturbing a normal request.

Model: opus-4-8
2026-09-21 18:22:22 +00:00
sneak 78cbb2617e feat: set ReadHeaderTimeout and IdleTimeout on the http.Server
Add HTTPReadHeaderTimeout (10s) and HTTPIdleTimeout (120s) constants and
wire them onto the server. ReadHeaderTimeout bounds the header-read phase
specifically, dropping a slowloris client that dribbles headers; ReadTimeout
alone bounds the whole request but not that phase. IdleTimeout bounds
keep-alive reuse so idle connections cannot accumulate without limit.

Server construction moves into newHTTPServer so the timeout configuration
is assertable without binding a listener.

Model: opus-4-8
2026-09-21 18:22:22 +00:00
sneak 930e4eb026 test: assert http.Server carries ReadHeaderTimeout and IdleTimeout
Failing test first: the server-hardening policy requires a slowloris
defense (ReadHeaderTimeout) and a keep-alive bound (IdleTimeout) on the
http.Server, neither of which is set today. The test asserts every
timeout field is wired onto the constructed server.

Model: opus-4-8
2026-09-21 18:22:22 +00:00