Federated via graft
0

Configure Feed

Select the types of activity you want to include in your feed.

Add low-risk HTTP hardening headers

X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
Permissions-Policy, Cross-Origin-Opener-Policy, and a conservative HSTS
(180 days, no includeSubDomains, no preload) — the nuclei/nikto findings
that carry no realistic regression risk for a session-less, form-less,
auto-escaped dashboard.

CSP and Cross-Origin-Embedder-Policy are deliberately left out: the
dashboard's stylesheet is one inline <style> block that a naive CSP would
break outright, and COEP protects cross-origin-isolated contexts graft
has no use for. Both need real testing before shipping, not a blanket
header.

+30 -1
+30 -1
cmd/sync/main.go
··· 151 151 mux.Handle("/", tracker.Handler()) 152 152 153 153 go func() { 154 - if err := http.ListenAndServe(*listen, mux); err != nil { 154 + if err := http.ListenAndServe(*listen, securityHeaders(mux)); err != nil { 155 155 log.Error("status server stopped", "err", err) 156 156 } 157 157 }() ··· 244 244 } 245 245 } 246 246 } 247 + } 248 + 249 + // securityHeaders sets a fixed, unconditional set of hardening response 250 + // headers on every request. Deliberately just static assignments — no 251 + // per-request logic, nothing that can panic or branch incorrectly — since 252 + // this wraps every route graft serves (dashboard, /social, ActivityPub, 253 + // /inbox) and a bug here would affect all of them at once. 254 + // 255 + // CSP is intentionally left out: the dashboard's entire stylesheet is one 256 + // inline <style> block, which a naive CSP would break outright (style-src 257 + // blocks inline styles by default) without a nonce wired through every 258 + // template render — worth doing properly later, not as a blanket header. 259 + // Cross-Origin-Embedder-Policy is left out too: it exists to protect 260 + // cross-origin-isolated contexts (SharedArrayBuffer, WASM threads) that 261 + // graft has no use for, and would only be a trap for a future contributor 262 + // who adds an external resource without thinking to CORS-enable it. 263 + func securityHeaders(next http.Handler) http.Handler { 264 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 265 + h := w.Header() 266 + h.Set("X-Content-Type-Options", "nosniff") 267 + h.Set("X-Frame-Options", "DENY") 268 + h.Set("Referrer-Policy", "strict-origin-when-cross-origin") 269 + h.Set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()") 270 + h.Set("Cross-Origin-Opener-Policy", "same-origin") 271 + // 180 days, no includeSubDomains (f1/f2/radicle aren't verified 272 + // all-HTTPS as a unit) and no preload (effectively irreversible). 273 + h.Set("Strict-Transport-Security", "max-age=15552000") 274 + next.ServeHTTP(w, r) 275 + }) 247 276 } 248 277 249 278 func lastSlash(s string) int {