package server import ( "net/http" "strings" ) // securityCSP keeps scripts and fonts on this origin. style-src allows inline // styles because KaTeX and Mermaid inject them into the page. connect-src // 'self' covers same-origin JSON and SSE. frame-src allows same-origin and // blob: iframes for PDF preview. frame-ancestors none matches X-Frame-Options DENY. const securityCSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: blob:; connect-src 'self'; frame-src 'self' blob:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'" func applySecurityHeaders(w http.ResponseWriter) { h := w.Header() h.Set("X-Content-Type-Options", "nosniff") h.Set("X-Frame-Options", "DENY") h.Set("Referrer-Policy", "strict-origin-when-cross-origin") h.Set("Content-Security-Policy", securityCSP) } func applyCORSHeaders(w http.ResponseWriter, r *http.Request, allowedOrigins []string) { origin := strings.TrimSpace(r.Header.Get("Origin")) if origin == "" || len(allowedOrigins) == 0 { return } for _, allowed := range allowedOrigins { if strings.TrimSpace(allowed) == origin { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Vary", "Origin") return } } } func (s *Server) writeCORS(w http.ResponseWriter, r *http.Request) { if s.cfg == nil { return } applyCORSHeaders(w, r, s.cfg.CORSAllowedOrigins) }