feat: add Content-Security-Policy middleware
All checks were successful
check / check (push) Successful in 4s

Add CSP header to all HTTP responses for defense-in-depth against XSS.

The policy restricts all resource loading to same-origin and disables
dangerous features (object embeds, framing, base tag injection). The
embedded SPA requires no inline scripts or inline style attributes
(Preact applies styles programmatically via DOM properties), so a
strict policy without 'unsafe-inline' works correctly.

Directives:
  default-src 'self'      — baseline same-origin restriction
  script-src 'self'       — same-origin scripts only
  style-src 'self'        — same-origin stylesheets only
  connect-src 'self'      — same-origin fetch/XHR only
  img-src 'self'          — same-origin images only
  font-src 'self'         — same-origin fonts only
  object-src 'none'       — no plugin content
  frame-ancestors 'none'  — prevent clickjacking
  base-uri 'self'         — prevent base tag injection
  form-action 'self'      — restrict form submissions
This commit is contained in:
clawbot
2026-03-10 03:15:02 -07:00
parent f287fdf6d1
commit 369eef7bc3
2 changed files with 34 additions and 0 deletions

View File

@@ -180,3 +180,36 @@ func (mware *Middleware) MetricsAuth() func(http.Handler) http.Handler {
}, },
) )
} }
// cspPolicy is the Content-Security-Policy header value applied to all
// responses. The embedded SPA loads scripts and styles from same-origin
// files only (no inline scripts or inline style attributes), so a strict
// policy works without 'unsafe-inline'.
const cspPolicy = "default-src 'self'; " +
"script-src 'self'; " +
"style-src 'self'; " +
"connect-src 'self'; " +
"img-src 'self'; " +
"font-src 'self'; " +
"object-src 'none'; " +
"frame-ancestors 'none'; " +
"base-uri 'self'; " +
"form-action 'self'"
// CSP returns middleware that sets the Content-Security-Policy header on
// every response for defense-in-depth against XSS.
func (mware *Middleware) CSP() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(
writer http.ResponseWriter,
request *http.Request,
) {
writer.Header().Set(
"Content-Security-Policy",
cspPolicy,
)
next.ServeHTTP(writer, request)
})
}
}

View File

@@ -29,6 +29,7 @@ func (srv *Server) SetupRoutes() {
} }
srv.router.Use(srv.mw.CORS()) srv.router.Use(srv.mw.CORS())
srv.router.Use(srv.mw.CSP())
srv.router.Use(middleware.Timeout(routeTimeout)) srv.router.Use(middleware.Timeout(routeTimeout))
if srv.sentryEnabled { if srv.sentryEnabled {