Skip to content

Wrap router with http.CrossOriginProtection for CSRF defence#40

Open
paskal wants to merge 1 commit intoumputun:masterfrom
paskal:feat/csrf-protection
Open

Wrap router with http.CrossOriginProtection for CSRF defence#40
paskal wants to merge 1 commit intoumputun: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 server/server.go. Bumps go.mod from 1.24.1 to 1.25.0 (and CI/release workflows accordingly) since the middleware is new in 1.25.

Why

The admin htmx UI issues many state-changing requests (POST /api/v1/feeds, PUT /api/v1/feeds/{id}, POST /api/v1/feeds/{id}/enable, DELETE /api/v1/feeds/{id}, POST /api/v1/topics, PUT /api/v1/preferences, etc) that today have no CSRF defence. An attacker page running JavaScript in a victim's browser can issue cross-origin POSTs to these endpoints; if the user is logged in, the requests succeed.

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. OWASP elevated this algorithm from defence-in-depth to a primary CSRF defence in its cheatsheet in December 2025.

What changed

  • server/server.go -- one line in setupMiddleware()
  • server/server_test.go -- new TestServer_CrossOriginProtection covering same-origin / cross-site / origin-host-mismatch / non-browser cases
  • go.mod -- go 1.24.1 → 1.25.0 (required for http.NewCrossOriginProtection)
  • .github/workflows/ci.yml, .github/workflows/release.yml -- Go version bumped to match

Behaviour notes

  • HTMX-driven POST/PUT/DELETE 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.
  • Non-browser API consumers (curl, scripts) do not send Sec-Fetch-Site -- treated as non-browser, allowed.
  • A cross-origin browser POST to any state-changing endpoint is now rejected with 403 before reaching the handler.

References

bumps go to 1.25 to pick up http.newcrossoriginprotection. the
middleware checks sec-fetch-site (forbidden header set by all major
browsers since 2023) with an origin/host fallback and rejects
cross-origin state-changing requests at the http layer.

ci and release workflows updated to go 1.25 to match go.mod.
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.

1 participant