security headers middleware: CSP, Referrer-Policy, nosniff on HTML pages (#59)
- web.SecurityHeaders middleware wired into the chi router - Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' (page scripts are inline); frame-ancestors 'none' - Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff - Applied only to text/html responses; JSON API and /raw pass through unchanged - Regression test internal/web/securityheaders_test.go
This commit is contained in:
@@ -290,3 +290,23 @@ func (h *Handlers) HandleAdminPage(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Handlers builds a web.Handlers bound to this UI.
|
||||
func (u *UI) Handlers() *Handlers { return &Handlers{UI: u} }
|
||||
|
||||
// #59: security headers for rendered HTML pages. Applied wherever the
|
||||
// response is text/html (page templates and the inline can page); JSON API
|
||||
// responses and /raw content pass through untouched. script-src allows
|
||||
// 'unsafe-inline' because the page templates carry inline scripts; CSP
|
||||
// default-src 'self' still blocks external content and object/frame embeds,
|
||||
// and frame-ancestors 'none' closes the clickjacking gap flagged in the #34
|
||||
// pentest. Runs after the handler so the Content-Type is already set.
|
||||
func SecurityHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(w, r)
|
||||
h := w.Header()
|
||||
if strings.HasPrefix(h.Get("Content-Type"), "text/html") {
|
||||
h.Set("Content-Security-Policy",
|
||||
"default-src 'self'; script-src 'self' 'unsafe-inline'; frame-ancestors 'none'")
|
||||
h.Set("Referrer-Policy", "no-referrer")
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user