How SQL injection works, parameterized queries in Node.js and Python, ORM safety, and common bypass techniques to test for.
How SQL injection works, parameterized queries in Node.js and Python, ORM safety, and common bypass techniques to test for.
BeforeMerge offers hundreds of code review rules, guides, and detection patterns to help your team ship better code.
SQL injection remains one of the most critical web vulnerabilities. Understanding how it works is essential to preventing it.
When user input is concatenated directly into SQL:
// VULNERABLE — never do this
const query = `SELECT * FROM users WHERE email = '${email}'`;An attacker can input:
' OR '1'='1' --Resulting in:
SELECT * FROM users WHERE email = '' OR '1'='1' --'This returns all users.
// SAFE — parameterized query
const result = await client.query(
"SELECT * FROM users WHERE email = $1",
[email]
);// SAFE — Supabase client handles parameterization
const { data } = await supabase
.from("users")
.select("*")
.eq("email", email);# SAFE — parameterized
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))// SAFE — sql tagged template (e.g., postgres.js)
const users = await sql`SELECT * FROM users WHERE email = ${email}`;ORMs like Prisma, Drizzle, and SQLAlchemy parameterize queries automatically:
// Prisma — safe by default
const user = await prisma.user.findUnique({ where: { email } });
// Drizzle — safe by default
const user = await db.select().from(users).where(eq(users.email, email));Danger zone: Raw query methods bypass safety:
// VULNERABLE — raw query with string interpolation
await prisma.$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`);
// SAFE — raw query with parameters
await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;When building queries with variable columns or table names:
// VULNERABLE — dynamic column name
const query = `SELECT * FROM users ORDER BY ${sortColumn}`;
// SAFE — allowlist approach
const ALLOWED_COLUMNS = ["name", "email", "created_at"];
if (!ALLOWED_COLUMNS.includes(sortColumn)) {
throw new Error("Invalid sort column");
}
const query = `SELECT * FROM users ORDER BY ${sortColumn}`;| Technique | Payload | Tests for |
|---|---|---|
| Basic | ' OR 1=1 -- |
Unparameterized queries |
| UNION | ' UNION SELECT password FROM users -- |
Data exfiltration |
| Blind boolean | ' AND 1=1 -- vs ' AND 1=2 -- |
Boolean-based blind injection |
| Time-based | '; SELECT pg_sleep(5) -- |
Time-based blind injection |
| Second-order | Store payload, trigger later | Stored input used in queries |