Add project:manage_members, CSP frame-src, task-run member view, CreateUser tx error handling, ERR_NO_PROJECT chat gate, and creator-scoped project name/client_key uniqueness to stop duplicate creates and auth gaps. Co-authored-by: Cursor <cursoragent@cursor.com>
42 lines
1.4 KiB
Go
42 lines
1.4 KiB
Go
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)
|
|
}
|