Skip to content

CORS is not CSRF

wiki edited this page Sep 4, 2026 · 1 revision

CORS is not CSRF protection

Worth its own page, because the belief that it is causes real vulnerabilities.

The gap

A simple cross-origin request — a form POST with application/x-www-form-urlencoded, multipart/form-data or text/plain — gets no preflight. The browser sends it. The server receives it. The server commits the state change.

CORS then stops the attacker from reading the response — which does nothing about the transfer that already happened.

<!-- On attacker.example. No preflight; the browser just sends it. -->
<form action="https://bank.example/transfer" method="POST">
  <input name="to" value="attacker">
  <input name="amount" value="10000">
</form>
<script>document.forms[0].submit()</script>

The victim's cookies ride along. A CORS policy that allows no origin at all does not stop this request being made — it only stops the attacker reading the {"status":"ok"} that comes back.

Two questions, one allowlist

Question Answered by
CORS may this origin read my responses? rextension-cors
CSRF did this request really come from my own application? rextension-security

They share an origin allowlist — rextension.OriginPolicy, declared in the contract module — and nothing else.

origins := rextension.OriginPolicy{
	AllowedOrigins:   []string{"https://app.example.com"},
	AllowCredentials: true,
}

app := rex.New(
	cors.WithCORS(cors.NewConfig(cors.WithPolicy(origins))),
	security.WithSecurity(&security.Config{
		CSRF: security.CSRFConfig{Policy: origins},
	}),
)

When you need CSRF protection

If the application authenticates with cookies, you need it. The browser attaches cookies to cross-site requests automatically, which is the entire attack.

If it authenticates with an Authorization header — a bearer token held in memory or in localStorage and set by script — CSRF is largely moot, because an attacker's page cannot make the browser attach that header. But note that this holds only while nothing in the application accepts a cookie as a fallback.

The related rules

CSRF protection applies only to unsafe methods. rextension.SafeMethod reports which those are, in the RFC 9110 sense. That is not a convenience: a GET that changes state is a bug in its own right, and one CSRF protection cannot fix, because a browser will issue it from an <img> tag with no way for the server to distinguish it.

Referer is not a fallback for Origin. It is stripped by privacy settings, by referrer policies and by some proxies — so treating its absence as "no cross-origin request" would be a bypass, and treating its presence as authoritative would let a referrer policy weaken the check. rextension.RequestOrigin reads Origin and only Origin.

Further reading

Clone this wiki locally