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:
Niklas Ye
2026-10-07 21:57:00 +02:00
parent df83adfe47
commit 7cd6fbf571
5 changed files with 162 additions and 1 deletions
+7 -1
View File
@@ -94,10 +94,16 @@ func handleIntegrationWebhook(db *sql.DB, notify NotifyConfig) http.HandlerFunc
}
}
// maxWebhookBodyBytes is larger than maxBodyBytes: a real Alertmanager batch
// can carry many alerts, each with several labels and annotations, and the
// sender is a trusted piece of infrastructure rather than an arbitrary
// caller.
const maxWebhookBodyBytes = 8 << 20
func receiveWebhook(w http.ResponseWriter, r *http.Request, db *sql.DB, notify NotifyConfig, src alertSource) {
teamID := src.teamID
var payload amPayload
if err := decodeJSON(r, &payload); err != nil {
if err := decodeJSONLimit(r, &payload, maxWebhookBodyBytes); err != nil {
respond(w, http.StatusBadRequest, errResp("invalid payload"))
return
}