← All fixes

Fix it

Missing CSRF protection on state-changing routes — how to fix it

Medium severityCWE-352 (Cross-Site Request Forgery)
We scanned nearly 2,000 AI-built apps and 1 in 8 shipped a high-severity flaw. Is this one in yours?Scan free →

Cross-site request forgery happens when another site causes a logged-in user's browser to send a request to your app, and the browser attaches the session cookie automatically. Without a defense, that forged request runs with the user's privileges. The fix is two layered pieces: set sameSite on your session cookie, and require an unguessable CSRF token on state-changing requests.

Why it's a problem

Because the browser sends your cookie on any request to your domain, an attacker does not need the user's credentials, only a page the user visits while logged in. A hidden form or image request can change an email, transfer a balance, or delete data as the victim. Any cookie-authenticated POST, PUT, PATCH, or DELETE without a token is a candidate.

The pattern

// cookie session, no token check: a cross-site form can trigger this
app.post("/account/email", requireLogin, (req, res) => {
  updateEmail(req.user.id, req.body.email);
  res.json({ ok: true });
});

The fix

import csrf from "csurf";
const csrfProtection = csrf({ cookie: false });

// session cookie should also be sameSite: 'lax' or 'strict'
app.post("/account/email", requireLogin, csrfProtection, (req, res) => {
  // request is rejected unless it carries the token issued to this session
  updateEmail(req.user.id, req.body.email);
  res.json({ ok: true });
});

Why AI tools write this

CSRF is invisible in the happy path an assistant is building: the form works, the update succeeds, nothing looks wrong. The defense requires wiring a token through the template and the request, which adds steps that are not needed to make the feature function, so the assistant ships the working route without it.

The quick fix

  • Set sameSite: 'lax' or 'strict' on the session cookie as the first layer.
  • Require a per-session CSRF token on every state-changing request and verify it server-side.
  • Prefer token-in-header for APIs called by your own frontend.
  • Exempt only endpoints that use a non-cookie auth scheme like a bearer token.

Want to know if this pattern is already in a repo you shipped? Scan a public repo free, no account needed.

Scan a repo →

Related: Insecure session cookie flags

Missing CSRF Protection — Why It Matters and How to Add It: Prbl