Cap request body size and add baseline security headers
Part of a security-hardening pass (see wiki for the full backlog). decodeJSON had no size limit at all, so every JSON endpoint -- including the two unauthenticated ones (bootstrap, the Alertmanager webhook) -- would buffer an attacker-supplied body of unbounded size before it was even validated. decodeJSON now wraps the body in http.MaxBytesReader at a 1 MiB default; the webhook gets its own 8 MiB cap via decodeJSONLimit, since a real Alertmanager batch can be bigger than an ordinary API body. Also adds a securityHeaders middleware, applied globally: nosniff on every response (previously only the static site got it), and HSTS (180-day max-age, conservative on purpose) whenever cookieSecure's signal says the browser is on HTTPS. Checked the chart/gateway config first -- neither sets HSTS anywhere, so this was a real gap. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,28 @@ func AuthMiddleware(db *sql.DB) func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// securityHeaders sets headers that cost nothing to send on every response,
|
||||
// API or static site alike. nosniff is unconditional; HSTS only fires once
|
||||
// cookieSecure's signal says the browser is actually looking at this server
|
||||
// over HTTPS — TLS terminates at the gateway, which (as of this writing) sets
|
||||
// neither header itself.
|
||||
//
|
||||
// max-age is 180 days rather than the usual year-plus: short enough that if
|
||||
// HTTPS here ever broke for real, the header would age out of a browser's
|
||||
// cache well within a release cycle instead of locking anyone out of a
|
||||
// working server. Raise it once this has run clean for a while.
|
||||
func securityHeaders(publicURL string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
if cookieSecure(publicURL, r) {
|
||||
w.Header().Set("Strict-Transport-Security", "max-age=15552000; includeSubDomains")
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AdminOnly rejects a caller who is not a system administrator. It runs inside
|
||||
// AuthMiddleware's group, so by the time it sees a request the caller is known.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user