Backend hardening: missing IdleTimeout/ReadHeaderTimeout, no security headers, no trusted-proxy client IP #19
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The Go backend has partial production hardening. Three gaps, all explicitly required by the
REPO_POLICIES.mdHTTP hardening section. Verified onmainatfbfe1df.1. Two required
http.Servertimeouts are missingbackend/internal/server/http.go:19-25sets:ReadTimeout,WriteTimeout, andMaxHeaderBytesare present and correct.ReadHeaderTimeoutandIdleTimeoutare absent. Policy names both: "ReadTimeoutandReadHeaderTimeouton thehttp.Serverto defend against slowloris attacks" and "IdleTimeouton thehttp.Server."Without
IdleTimeout, keep-alive connections are never reaped and an attacker can pin server resources with idle sockets.2. No security-headers middleware
backend/internal/server/routes.go:17-21registersRecoverer,RequestID,Logging,CORS,Timeout. There is no middleware setting security headers, so every backend response — including/.well-known/healthcheckand/api/v1/reports— ships without HSTS, CSP,X-Content-Type-Options,X-Frame-Options,Referrer-Policy, orPermissions-Policy.The frontend nginx gap is tracked separately in #18; this is the Go server and needs its own fix.
3. Client IP is taken from
RemoteAddr, ignoring the reverse proxybackend/internal/middleware/middleware.go:99logs:ipFromHostPort(line 66) just splits host from port. Behind the reverse proxy this service is designed to run behind, every log line will record the proxy's IP, not the client's.Policy: "True client IP detection when behind a reverse proxy (
X-Forwarded-For,X-Real-IP). The application must accept forwarded headers only from a configured set of trusted proxy addresses — never trustX-Forwarded-Forunconditionally."The nginx frontend already does this correctly with
set_real_ip_fromfor RFC1918; the Go server does not do the equivalent.Definition of done
http.ServersetsReadHeaderTimeoutandIdleTimeoutalongside the existing timeouts. Values are named constants next to the existing ones inhttp.go, not magic numbers.SecurityHeaders()middleware exists ininternal/middleware/following the establishedfunc (s *Middleware) X() func(http.Handler) http.Handlerconvention, and is registered inSetupRoutes(). It sets HSTS (max-age>= 31536000,includeSubDomains), CSP,X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy, andPermissions-Policy.default-src 'none'; frame-ancestors 'none'CSP is appropriate here — this endpoint does not have the probe-origin problem that #18 does. Justify whatever you choose in the PR.X-Forwarded-For/X-Real-IPonly from a configured trusted-proxy allowlist, defaulting to the RFC1918 ranges (10/8, 172.16/12, 192.168/16) to matchnginx.conf. The trusted set is configurable via environment variable through the existingviperconfig ininternal/config/config.go, following the established config conventions.X-Forwarded-Forfrom an untrusted source is ignored, and the log records the direct peer instead. Add a unit test proving both directions: trusted proxy -> forwarded IP is used; untrusted peer -> header is ignored.cd backend && make checkpasses; rootmake checkpasses.TODO.mdupdated in the same commit.(closes #N).Implementation requirements
GO_HTTP_SERVER_CONVENTIONS.md: fx params structs,Newfactory pattern,slogfor logging,viperfor config withAutomaticEnv()and aSetDefaultfor any new setting.net/netipandnet.ParseCIDRsuffice — they do.backend/README.md.maketargets only; never rawgoinvocations.