Safeguard
Security Guides•

What should a code review check for CSRF protection?

Check that cookie authenticated, state changing requests carry a token or custom header, GET is safe, and SameSite is extra defense, not the fix.

Safeguard Security Team
4 min read

For every state changing request that relies on cookies, check that the framework's CSRF protection covers it, or that a token or custom header is verified. Confirm GET requests never change state, and treat SameSite cookies as defense in depth. XSS defeats all of these, so it still needs its own fix.

When CSRF applies

The OWASP CSRF Prevention Cheat Sheet explains that cookie authenticated sites need protection because browsers attach cookies automatically to cross-site requests. It advises using the framework's built-in protection first, and adding tokens to all state changing requests if none exists.

If a request is authenticated by a header that scripts add, such as a bearer token from local code, the browser does not attach it on its own, which changes the risk. Know which model the endpoint uses before commenting.

The options a reviewer will see

ApproachReview notes
Synchronizer tokenServer creates a secret per session and checks it on each state changing request. Token goes in a form or header, not in a cookie
Signed double submit cookieStateless option. The signed version bound to the session is recommended. The naive version is discouraged
Custom request headerWorks for AJAX and APIs because cross-origin requests with custom headers need a CORS preflight. Only trusted origins may be allowed
SameSite Lax or StrictHelps, but not a full defense

Steps for a review

  1. List the new or changed routes that create, update or delete data.
  2. For each, find what proves the request came from your own page. If the answer is only the cookie, flag it.
  3. Check that any exemption list in the middleware is justified. A growing skip list is a smell.
  4. Make sure no state change happens on GET, HEAD or OPTIONS. The cheat sheet is clear that these must be safe, and SameSite Lax does not stop a top level GET navigation.
  5. Check CORS settings on the same routes. A wildcard subdomain policy weakens the custom header approach.
# red flag: a GET that changes state
@app.get("/account/delete")
def delete_account(): ...

# better: POST with a CSRF token checked by middleware
@app.post("/account/delete")
def delete_account(): ...

Why SameSite is not the whole answer

The cheat sheet notes that Lax still allows cookies on top level GET navigations, that cookie scope follows the registrable domain so sibling subdomains matter, and that browser coverage is not universal. MDN adds that a cookie with no SameSite attribute is treated as Lax by default. Use these as a second layer.

XSS undoes the rest

Injected script runs as your origin. It can read tokens and send valid requests. The cheat sheet says CSRF defenses do not replace XSS prevention. In practice, a clean CSRF review in an app with an open XSS hole is a false comfort.

Try it yourself

Run the web scan in the demo and see how a dynamic scan reports missing protections on a form based page.

Click through it at your own pace, or open it full screen.

Where Safeguard fits

Safeguard scans dependencies and SBOMs, which covers the web framework and its security middleware versions. The free scan is at gold.safeguard.sh/scan.

Quick check

Does SameSite replace a CSRF token?

No. OWASP treats it as defense in depth. Lax still allows cookies on top level GET navigations.

Why must GET be safe?

Because SameSite Lax and similar defenses do not stop state changes made through GET.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.