diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-06-03 00:35:02 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-06-03 00:35:02 +0000 |
| commit | eb089ef4c9416bd3d5642bb5a4437355ffa2fdd0 (patch) | |
| tree | 8e4fbd833defa346f506c95a80345b3ab0fc379a /internal/auth/middleware.go | |
| parent | 3a9263abd0a5b2b759a8f691f001ff71f5417748 (diff) | |
fix: add CSRF and WebSocket origin protection to Claudomator proxy
Token-based CSRF is impractical for a reverse proxy whose UI doesn't
know Doot's session tokens, so state-changing requests to /claudomator/*
are now validated against the configured WebAuthn origin via Origin/Referer
header. WebSocket upgrades reject mismatched or missing Origin headers
before hijacking the connection. Both checks are no-ops when WebAuthnOrigin
is unset (local dev).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/auth/middleware.go')
| -rw-r--r-- | internal/auth/middleware.go | 26 |
1 files changed, 26 insertions, 0 deletions
diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go index 78f3b53..04e7111 100644 --- a/internal/auth/middleware.go +++ b/internal/auth/middleware.go @@ -6,6 +6,7 @@ import ( "crypto/subtle" "encoding/base64" "net/http" + "net/url" "strings" "github.com/alexedwards/scs/v2" @@ -120,6 +121,31 @@ func GetCSRFTokenFromContext(ctx context.Context) string { return token } +// OriginCheck returns middleware that validates the Origin (or Referer) header +// against allowedOrigin for state-changing requests. Use for reverse-proxy +// routes where injecting a CSRF token into the proxied UI is not practical. +// When allowedOrigin is empty the check is skipped (dev/no-TLS setups). +func OriginCheck(allowedOrigin string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if allowedOrigin != "" && + (r.Method == "POST" || r.Method == "PUT" || r.Method == "DELETE" || r.Method == "PATCH") { + origin := r.Header.Get("Origin") + if origin == "" { + if ref, err := url.Parse(r.Header.Get("Referer")); err == nil && ref.Host != "" { + origin = ref.Scheme + "://" + ref.Host + } + } + if origin != allowedOrigin { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + } + next.ServeHTTP(w, r) + }) + } +} + func generateToken() (string, error) { b := make([]byte, 32) _, err := rand.Read(b) |
