A buffer failure on POST /api/v1/reports now returns 500 instead of a
false `ok`, so clients can retry. An over-limit body returns 413
(errors.As on `*http.MaxBytesError`); malformed JSON stays 400. A
MaxBodyBytes middleware (1 MiB) caps every route, rejecting an
oversized Content-Length up front and capping the read otherwise; a
route group can only lower that limit. The raw geo blob is no longer
logged, only its length; client_id, timestamp and decode error text
are length-bounded before logging. A decodeJSON handler helper is
added. Panic recovery routes the stack through slog as structured
JSON. Storage failure uses 500: a full buffer or write error is
server-side and retryable.
Model: opus-5-5
The old backend/.golangci.yml declared version "2" but used v1 schema
keys, so under v2 it never validated and its thresholds were inert: the
linter ran at defaults. Replace it verbatim with the org-standard file,
repin the Dockerfile.backend lint stage to golangci-lint v2.12.2, and
assert the config's sha256 as the first step of the backend lint target
so it cannot silently drift again -- a local hash check, no network.
The standard config surfaces findings only in the tests: the repeated
IP literals in middleware_test.go become named constants (goconst) and
its request switches to NewRequestWithContext (noctx). reportbuf.go's
gosec suppression gains a plain justification comment. The rest of the
backend, including the fx-based server lifecycle, is already clean.
TODO.md updated.
Model: opus-4-8
Add ReadHeaderTimeout and IdleTimeout to the http.Server as named constants beside the existing timeouts. Add a SecurityHeaders middleware (HSTS, a JSON-API CSP of default-src 'none'; frame-ancestors 'none', X-Frame-Options DENY, nosniff, Referrer-Policy, Permissions-Policy), registered before CORS so preflight responses carry it. Resolve the client IP from X-Forwarded-For / X-Real-IP only when the direct peer is in the trusted-proxy allowlist (loopback plus RFC1918 by default, configurable via TRUSTED_PROXIES); an untrusted peer's forwarded headers are ignored. Uses net/netip; no new dependency.
Model: opus-4-8 (implementation and review); claude-fable-5 (merge)