SQL injection prevention in Node.js — parametrize everything
SQL injection is a solved problem — and AI coding assistants keep re-introducing it. Here are the exact patterns to watch for in Prisma, Supabase, Drizzle, and raw pg.
Every ORM supports parameterized queries. AI-generated code sometimes reaches for raw SQL anyway — to ship faster, to match a copy-pasted example. This guide covers the exact patterns that slip through code review.
What it is
SQL injection happens when user input is interpolated into a SQL string. The fix is parameter binding, where the database receives SQL and values as separate inputs and never compiles user input as SQL.
Vulnerable example
// Vulnerable in every ORM:
// 1. Raw pg
const result = await pool.query(`SELECT * FROM users WHERE email = '${email}'`);
// 2. Prisma raw query
const users = await prisma.$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`);
// 3. Drizzle sql template with interpolation
const users = await db.execute(sql`${sql.raw(`SELECT * FROM users WHERE email = '${email}'`)}`);Fixed example
// Fixed: parameter binding
// 1. Raw pg
const result = await pool.query("SELECT * FROM users WHERE email = $1", [email]);
// 2. Prisma typed
const users = await prisma.user.findMany({ where: { email } });
// 3. Prisma raw with bindings
const users = await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;
// 4. Drizzle typed
const users = await db.select().from(usersTable).where(eq(usersTable.email, email));How Securie catches it
apps/web/app/api/route.ts:22SQL injection prevention in Node.js
Securie's taint analyzer traces every user input into every SQL sink across the most common Node ORMs, flagging any string interpolation that reaches SQL.
// Fixed: parameter binding
// 1. Raw pg
const result = await pool.query("SELECT * FROM users WHERE email = $1", [email]);
// 2. Prisma typed
const users = await prisma.user.findMany({ where: { email } });
// 3. Prisma raw with bindings
const users = await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;
// 4. Drizzle typed
const users = await db.select().from(usersTable).where(eq(usersTable.email, email));Checklist
- Zero string concatenation in SQL
- Raw query APIs (`$queryRawUnsafe`, `pool.query` with template strings) are grep'd for and each use is reviewed
- LIKE clauses use parameterized %s with explicit escape
- Dynamic identifiers (table/column names from user input) are whitelisted, not concatenated
- Database users have read-only where possible; prod DB cannot DROP
FAQ
What about NoSQL injection?
Same principle. MongoDB drivers bind parameters correctly; `$where` with interpolation is the equivalent anti-pattern. Securie covers MongoDB too.
Related guides
Row-Level-Security bypass is the most common data leak in vibe-coded apps. Here is exactly how it happens, how attackers find it, and how to fix it in Next.js + Supabase with one policy update.
BOLA is the top item on the OWASP API Security Top 10 for a reason — every AI coding assistant introduces it by default. Learn what it looks like in Next.js, how to exploit it, and how to fix it.
IDOR is the classic name for an authorization bug where a user can change an ID in a URL and access data they should not see. It is BOLA's older cousin and still ships in half of all new apps.
Every week founders tweet about their OpenAI bill going from $10 to $10,000 overnight. Usually the cause is an API key committed to a public repo. Here is why it happens in Next.js specifically and how to stop it in five minutes.