CSRF & Mutation Safety
Cross-Module · AUTH-22, ADM-19 · Priority: P1
Why It Matters
Cross-Site Request Forgery (CSRF) tricks a logged-in user's browser into making requests to your app without their knowledge. If your app has no CSRF protection, an attacker can craft a malicious page that — when visited by a logged-in admin — deletes users, changes billing, or exports data.
In Tenzai's testing of AI-built apps, 0 out of 15 apps implemented CSRF protection. AI code generators create Server Actions and API routes that accept mutations without any origin verification.
Priority: P1 — Enables unauthorized mutations through social engineering.
Affected Stack: Next.js Server Actions, API routes, any framework with cookie-based auth
AUTH-22: CSRF on Server Actions
The Problem
Next.js Server Actions are invoked via POST requests with the session cookie automatically attached by the browser. If there's no CSRF token or origin check, a malicious website can trigger the action.
// ❌ Server Action with no CSRF protection
'use server';
export async function deleteAccount() {
const user = await getAuthenticatedUser();
await supabase.from('users').delete().eq('id', user.id);
}
A malicious page can trigger this:
<!-- On attacker's website -->
<form action="https://yourapp.com/delete-account" method="POST">
<input type="hidden" name="$ACTION_ID" value="..." />
<button>Click for free prize!</button>
</form>
The Fix
Next.js 15 has built-in CSRF protection for Server Actions via the Origin header check. Ensure it's not disabled, and add explicit verification for sensitive actions.
// ✅ Server Action with explicit origin check
'use server';
import { headers } from 'next/headers';
export async function deleteAccount() {
const headerList = headers();
const origin = headerList.get('origin');
const host = headerList.get('host');
// Verify request comes from our own domain
if (!origin || !origin.includes(host!)) {
throw new Error('CSRF validation failed');
}
const user = await getAuthenticatedUser();
await supabase.from('users').delete().eq('id', user.id);
}
For API routes, use the SameSite cookie attribute and verify the Origin header:
// ✅ API route with CSRF protection
export async function POST(req: Request) {
const origin = req.headers.get('origin');
if (origin !== process.env.NEXT_PUBLIC_APP_URL) {
return new Response('Forbidden', { status: 403 });
}
// ... process mutation
}
ADM-19: CSRF for Admin Mutations
Admin mutations (user deletion, role changes, data export) require the same CSRF protections — with higher stakes. A CSRF attack against an admin session can compromise the entire application.
Additional protection for admin actions:
- Require re-authentication or MFA for destructive operations
- Use custom CSRF tokens for critical admin forms
- Log all admin mutations with origin/IP for audit
References
- OWASP: CSRF Prevention Cheat Sheet
- Next.js: Server Actions Security
- CWE-352: Cross-Site Request Forgery
Related Checks
- Unsafe Code Patterns — AUTH-14
- Server-Side Auth for Protected Routes — AUTH-04
- MFA for Admin Roles — ADM-13
Is your app safe? Run Free Scan →