From ea21f85887f05bb9bb0fcf2098d22dda36d23213 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Wed, 18 Mar 2026 19:26:03 +0100 Subject: [PATCH] Add http.CrossOriginProtection for CSRF defense using Sec-Fetch-Site headers Implements native Go 1.26 cross-origin protection to block state-changing cross-origin browser requests. Registers configured AllowedOrigins as trusted origins and wraps the API router to check all incoming requests. Non-browser clients (MCP, Slack webhooks) are unaffected as they lack the browser-only Sec-Fetch-Site header. Signed-off-by: gearnode Signed-off-by: Bryan Frimin --- pkg/server/api/api.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/pkg/server/api/api.go b/pkg/server/api/api.go index 7a302a343..f2017e8fa 100644 --- a/pkg/server/api/api.go +++ b/pkg/server/api/api.go @@ -16,6 +16,7 @@ package api import ( "errors" + "fmt" "net/http" "time" @@ -67,6 +68,7 @@ type ( Server struct { cfg Config + csrf *http.CrossOriginProtection compliancePageHandler http.Handler consoleHandler http.Handler filesHandler http.Handler @@ -119,8 +121,26 @@ func NewServer(cfg Config) (*Server, error) { return nil, ErrMissingSlackService } + csrf := http.NewCrossOriginProtection() + for _, origin := range cfg.AllowedOrigins { + if err := csrf.AddTrustedOrigin(origin); err != nil { + return nil, fmt.Errorf("cannot add trusted origin %q: %w", origin, err) + } + } + + csrf.SetDenyHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + httpserver.RenderJSON( + w, + http.StatusForbidden, + map[string]string{ + "error": "cross-origin request denied", + }, + ) + })) + return &Server{ - cfg: cfg, + cfg: cfg, + csrf: csrf, compliancePageHandler: trust_v1.NewMux( cfg.Logger.Named("trust.v1"), cfg.IAM, @@ -204,5 +224,5 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { router.Mount("/mcp/v1", http.StripPrefix("/mcp/v1", s.mcpHandler)) router.Mount("/slack/v1", http.StripPrefix("/slack/v1", s.slackHandler)) - router.ServeHTTP(w, r) + s.csrf.Handler(router).ServeHTTP(w, r) }