Skip to content

Wrap router with http.CrossOriginProtection for CSRF defence#102

Merged
umputun merged 1 commit into
umputun:masterfrom
paskal:feat/csrf-protection
Apr 17, 2026
Merged

Wrap router with http.CrossOriginProtection for CSRF defence#102
umputun merged 1 commit into
umputun:masterfrom
paskal:feat/csrf-protection

Conversation

@paskal
Copy link
Copy Markdown
Contributor

@paskal paskal commented Apr 17, 2026

Adds Go 1.25's http.NewCrossOriginProtection().Handler to the global middleware chain in app/server/server.go. One line of code, ~50 lines of test.

Why

Today the only CSRF defence on the cookie session is SameSite=Strict, which has known gaps:

  • Firefox does not default to SameSite=Lax on its release channel and has no plans to change in 2025
  • subdomain attacks bypass SameSite entirely -- it operates on site (registrable domain), not origin, so an attacker controlling any subdomain on the same registrable domain can issue same-site POSTs with the session cookie attached
  • Chrome's "Lax+POST" two-minute window keeps cookies without an explicit SameSite available across sites for 120s after navigation

http.CrossOriginProtection checks the browser-set Sec-Fetch-Site header (a forbidden header JS cannot forge, shipped in all major browsers since 2023) with an Origin vs Host fallback for older clients. Unlike SameSite, it distinguishes same-origin from same-site -- subdomain attacks are blocked.

OWASP elevated this algorithm from defence-in-depth to a primary CSRF defence in its cheatsheet in December 2025.

What changed

  • app/server/server.go -- one line in the router.Use(...) block
  • app/server/server_test.go -- new TestServer_CrossOriginProtection covering same-origin / cross-site / origin-host-mismatch / non-browser cases

Behaviour notes

  • HTMX-driven POSTs from the web UI run as same-origin requests, so Sec-Fetch-Site: same-origin is sent and the middleware passes them through. Verified end-to-end via the new test.
  • /kv/* API consumers (curl, scripts, server-to-server, the Go client library) do not send Sec-Fetch-Site -- the middleware treats this as a non-browser request and lets them through. Token-authenticated API integrations are unaffected.
  • A cross-origin browser POST/PUT/DELETE to any state-changing endpoint (e.g. an attacker page issuing fetch() against /kv/foo or /web/keys) is now rejected with 403 before reaching the handler.

References

Add Go 1.25's http.NewCrossOriginProtection().Handler to the global
middleware chain. Previously the only CSRF defence on the cookie
session was SameSite=Strict, which Firefox does not enforce by
default and which subdomain attacks can bypass.

The middleware checks Sec-Fetch-Site (forbidden header, set by all
major browsers since 2023) with an Origin/Host fallback. Same-origin
htmx requests pass through; non-browser clients (no Sec-Fetch-Site)
also pass through, so token-authenticated /kv/* API consumers are
unaffected.
@paskal paskal requested a review from umputun as a code owner April 17, 2026 01:38
Copy link
Copy Markdown
Owner

@umputun umputun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, thx

@umputun umputun merged commit e1f69ee into umputun:master Apr 17, 2026
5 checks passed
@paskal paskal deleted the feat/csrf-protection branch April 17, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants