fix(server): SEC-003 — keep securityHeadersMiddleware in rate-limit stack

Sprint 1 unified-master-audit closure. cmd/server/main.go built two
middleware stacks: a default (line ~2054) and a rate-limit-enabled
rebuild (line ~2079). The rebuild dropped securityHeadersMiddleware,
silently turning off five browser-side defenses (Strict-Transport-
Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
Content-Security-Policy) the moment an operator flipped
CERTCTL_RATE_LIMIT_ENABLED=true.

Fix: re-insert securityHeadersMiddleware at the same position as the
default stack and place rateLimiter immediately after, so even a 429
response carries the same headers as a 200.

Regression coverage:
  - cmd/server/main_test.go TestMain_RateLimitedStack_EmitsSecurityHeaders
    mirrors the production stack composition and asserts each of the
    five headers lands on the response. A future regression that
    removes securityHeadersMiddleware (or reorders it after the rate
    limiter such that a 429 misses the headers) surfaces here.

Closes SEC-003.
This commit is contained in:
shankar0123
2026-05-16 03:32:08 +00:00
parent 037dab7b6f
commit 7d2e7043b9
2 changed files with 72 additions and 0 deletions
+11
View File
@@ -2076,11 +2076,22 @@ func main() {
PerUserRPS: cfg.RateLimit.PerUserRPS,
PerUserBurstSize: cfg.RateLimit.PerUserBurstSize,
})
// SEC-003 closure (Sprint 1, 2026-05-16). Pre-fix the
// rate-limit-enabled stack was rebuilt without
// securityHeadersMiddleware, silently dropping HSTS,
// X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
// and Content-Security-Policy across every response when an
// operator flipped CERTCTL_RATE_LIMIT_ENABLED=true — a
// defensive-config toggle weakened browser-side security.
// The fixed stack keeps securityHeadersMiddleware at the same
// position as the default and inserts rateLimiter right after
// so a 429 response still carries the same headers as a 200.
middlewareStack = []func(http.Handler) http.Handler{
middleware.RequestID,
structuredLogger,
middleware.Recovery,
bodyLimitMiddleware,
securityHeadersMiddleware,
rateLimiter,
corsMiddleware,
// Phase 6 chain: Auth (session-then-Bearer fallback) → CSRF