export async function updateProfile(formData: FormData) {
const session = await auth();
if (!session?.user?.id) {
throw new Error('Neautorizat');
}
const rawData = Object.fromEntries(formData);
const parsed = profileSchema.safeParse(rawData);
if (!parsed.success) {
return { error: 'Date invalide' };
}
// Folosim session.user.id, NU un ID pasat din client
await db.user.update({
where: { id: session.user.id },
data: parsed.data,
});
}Next.js îți dă o falsă senzație de siguranță. Știi că React face escape automat la variabile în JSX și că un ORM modern te scapă de SQL injection clasic, așa că lași garda jos. Am văzut iluzia asta spulberată anul trecut la un audit pentru un SaaS cu vreo 14.000 de useri activi: aveau trei endpoint-uri complet deschise pentru că echipa a presupus că framework-ul le protejează din oficiu.
Nu e nevoie să memorezi tot documentul OWASP pe de rost. Când faci review pe un PR, sunt câteva locuri critice unde apar problemele în 90% din cazuri.
Server Actions nu sunt funcții private, sunt API-uri publice
Cea mai mare greșeală pe care o văd în PR-uri de când a apărut App Router: tratarea unei funcții marcate cu 'use server' ca și cum ar fi un cod intern, securizat prin magie. Nu este.
Sub capotă, Next.js expune un endpoint HTTP POST accesibil oricui are URL-ul și ID-ul acțiunii. Dacă ai o acțiune deleteUser(id) și nu verifici manual sesiunea și permisiunile chiar în prima linie a funcției, ai creat o vulnerabilitate de tip IDOR (Insecure Direct Object Reference) ca la carte. Oricine poate trimite un curl direct către endpoint cu ID-ul altcuiva.
La PR review, dau instant reject dacă văd o Server Action care primește ID-ul entității direct din client fără să valideze că userul logat deține resursa respectivă.
XSS-ul modern vine prin Markdown și sanitizare oarbă
Nimeni nu mai scrie document.write de mână. Astăzi, XSS-ul în Next.js intră aproape exclusiv prin două locuri: CMS-uri headless și parsere de Markdown pentru conținut generat de utilizatori.
Am avut cazul clasic în care un coleg a băgat dangerouslySetInnerHTML pentru preview-ul unui editor de comentarii. Justificarea lui a fost: „e ok, folosim DOMPurify”. Problema e că pe server (SSR), DOMPurify simplu nu rulează fără un jsdom în spate, așa că a pus un workaround rapid găsit pe StackOverflow care scăpa taguri SVG malitioase. Dacă ai nevoie de HTML raw, sanitizarea trebuie făcută strict pe server la scriere, nu lăsată pe render.
SQL Injection încă trăiește prin queryRaw
Framework-ul te împinge spre Prisma sau Drizzle, deci pare că ai scăpat de clasicul ' OR '1'='1. Dar când baza de date începe să gâfâie și apar query-uri complexe cu agregate, inevitabil cineva bagă un $queryRawUnsafe ca să scape rapid de limitările ORM-ului.
Când văd cuvântul raw într-un PR, caut imediat concatenări de stringuri sau template literals JS. Dacă parametrul e interpolat direct ($1 vs ${input}), PR-ul se blochează pe loc.
Trade-off-ul pe care trebuie să-l accepți
Server Actions aduc un DX excelent pentru formulare, dar sparg centralizarea securității. Înainte aveai un middleware de Express sau controllere de Nest unde vedeai clar toate filtrele de auth la un loc. Acum, responsabilitatea e împrăștiată în zeci de fișiere individuale. Dacă un junior uită verificarea într-un singur fișier, bresa e vie.
Voi cum gestionați autorizarea în Server Actions: folosiți un wrapper custom peste acțiuni sau vă bazați doar pe vigilența la code review?