Cross-site request forgery is the security bug of the 2000s. It is well-understood. It is easy to fix. There are entire libraries whose only job is to fix it for you. And yet, in 2026, we audit AI-generated apps and find it by default, in nearly every project.
Here's the pattern.
The setup
The AI generates an app with:
- Session cookies for auth (
Set-Cookie: session=...; HttpOnly) - A REST API at
/api/*that reads those cookies - No CSRF token
- No
SameSitecookie attribute (orSameSite=None) - CORS configured to allow the frontend origin
Everything works. The user logs in, the cookie is set, the app reads it on every request. Ship it.
The attack
An attacker sends the user a link to https://cool-cat-pictures.example. That page contains:
<form action="https://your-app.example/api/account/delete" method="POST">
<input type="hidden" name="confirm" value="yes" />
</form>
<script>document.forms[0].submit();</script>The user's browser cheerfully sends the request to your app with their session cookie attached, because that's what browsers do. Your app sees an authenticated request with the right cookie and deletes the account.
The user never clicked anything on your site. They clicked a link on someone else's site. Your app authenticated them, and then executed an action they never intended.
Why the AI ships it
Because it works in development. The bug is invisible until a hostile third-party site is involved. Every LLM's training data is full of API examples where nobody thought about the attacker.
The fix, in order of preference
1. SameSite=Lax cookies
The cheapest fix in web history:
response.cookie('session', session_id, {
httpOnly: true,
secure: true,
sameSite: 'lax',
});SameSite=Lax tells the browser: "don't send this cookie on cross-site POST requests." That single attribute kills 95% of practical CSRF attacks. It has been supported by every browser since 2020.
2. CSRF tokens for anything sensitive
For state-changing endpoints, issue a per-session CSRF token, put it in a header on requests from your frontend, and verify it server-side:
const verify_csrf = (request) => {
const header_token = request.headers['x-csrf-token'];
const session_token = request.session?.csrf_token;
if (!header_token || header_token !== session_token) {
throw { status: 403, message: 'Bad CSRF token.' };
}
};The attacker's page can trigger a request, but it can't read your session's token to include, because same-origin policy blocks it.
3. Origin / Referer checking
For endpoints that must accept cross-origin requests (webhooks, etc.), verify the Origin header explicitly against an allowlist. Never trust the Origin header for auth — trust it only to reject mismatches.
None of this is new. All of it is easy. The next audit you get, ask the auditor to find your CSRF story. If they can't find one, that is the story.