mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 11:48:42 +02:00
fix(security): add Content-Security-Policy response header (#822)
Adds CSP middleware to the global middleware chain as a browser-level defense against XSS: script-src 'self', object-src 'none', frame-ancestors 'none', base-uri 'self', form-action 'self'. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,7 @@ func NewRouter(pool *pgxpool.Pool, hub *realtime.Hub, bus *events.Bus) chi.Route
|
||||
r.Use(chimw.RequestID)
|
||||
r.Use(middleware.RequestLogger)
|
||||
r.Use(chimw.Recoverer)
|
||||
r.Use(middleware.ContentSecurityPolicy)
|
||||
origins := allowedOrigins()
|
||||
|
||||
// Share allowed origins with WebSocket origin checker.
|
||||
|
||||
20
server/internal/middleware/csp.go
Normal file
20
server/internal/middleware/csp.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
const cspHeader = "default-src 'self'; " +
|
||||
"script-src 'self'; " +
|
||||
"style-src 'self' 'unsafe-inline'; " +
|
||||
"img-src 'self' https: data:; " +
|
||||
"connect-src 'self' wss:; " +
|
||||
"frame-ancestors 'none'; " +
|
||||
"object-src 'none'; " +
|
||||
"base-uri 'self'; " +
|
||||
"form-action 'self'"
|
||||
|
||||
func ContentSecurityPolicy(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Security-Policy", cspHeader)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
36
server/internal/middleware/csp_test.go
Normal file
36
server/internal/middleware/csp_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContentSecurityPolicy(t *testing.T) {
|
||||
handler := ContentSecurityPolicy(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
csp := rec.Header().Get("Content-Security-Policy")
|
||||
if csp == "" {
|
||||
t.Fatal("Content-Security-Policy header is missing")
|
||||
}
|
||||
|
||||
required := []string{
|
||||
"script-src 'self'",
|
||||
"object-src 'none'",
|
||||
"frame-ancestors 'none'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'self'",
|
||||
}
|
||||
for _, directive := range required {
|
||||
if !strings.Contains(csp, directive) {
|
||||
t.Errorf("CSP missing directive %q; got: %s", directive, csp)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user