What is SQL Injection?
SQL injection happens when your app builds a database query by concatenating user input directly into the SQL string. An attacker sends a value like ' OR 1=1 -- in a login form, and your query goes from "find this specific user" to "return every user in the table." The database doesn't know the difference between your code and the attacker's — it just runs the whole string.
The consequences are as bad as it gets. An attacker can dump your entire database, modify records, delete tables, or — in some configurations — execute system commands on the server. SQL injection has been behind some of the largest data breaches in history, from the 2011 Sony PlayStation Network hack to the 2019 Fortnite vulnerability.
Modern ORMs like Prisma and Drizzle prevent SQL injection by default because they use parameterized queries under the hood. But the moment you drop down to raw SQL — which AI tools do frequently for complex queries — you're back in the danger zone.
How does SQL Injection work?
The vulnerability exists whenever user input is interpolated into a SQL string using template literals or string concatenation. The database parser can't distinguish between the query structure and the injected data — it treats everything as SQL.
Here's a login endpoint where the email field is vulnerable:
// app/api/login/route.ts
export async function POST(req) {
const { email, password } = await req.json();
// User input stitched directly into the query
const result = await db.query(
`SELECT * FROM users
WHERE email = '${email}'
AND password = '${password}'`
);
// Input: email = "' OR 1=1 --"
// Resulting query: SELECT * FROM users
// WHERE email = '' OR 1=1 --' AND password = ''
return Response.json(result.rows[0]);
}// app/api/login/route.ts
export async function POST(req) {
const { email, password } = await req.json();
// $1 and $2 are parameter placeholders
// The database treats them as data, never as SQL
const result = await db.query(
'SELECT * FROM users WHERE email = $1 AND password_hash = crypt($2, password_hash)',
[email, password]
);
if (result.rows.length === 0) {
return Response.json({ error: "Invalid credentials" }, { status: 401 });
}
return Response.json({ user: result.rows[0] });
}Why do AI tools generate SQL Injection vulnerabilities?
AI models know what SQL injection is — they can explain it perfectly if you ask. But when generating API routes, they default to the simplest pattern that works: template literals. The code runs, the query returns results, and there's no visible error to signal that anything is wrong.
- Template literals are the path of least resistance. When the model generates a raw SQL query, backtick interpolation is the most natural JavaScript pattern. Parameterized queries require knowing the database driver's specific placeholder syntax ($1 for pg, ? for mysql2).
- Complex queries push models away from ORMs. "Join users with orders and filter by date range" is hard to express in Prisma. The model drops to raw SQL — and when it does, it often skips parameterization.
- No runtime feedback. A SQL injection vulnerability doesn't throw an error during development. The query works correctly with normal input. The bug only surfaces when someone sends malicious input — which doesn't happen in testing.
SQL injection was first documented in 1998. Nearly three decades later, it's still in the OWASP Top 10 because every new generation of tools rediscovers the same mistake: treating user input as trusted code.
Common SQL Injection patterns
Template literal queries
db.query(`SELECT * FROM users WHERE id = ${userId}`) — the most common form. The variable is interpolated before the database sees it.
String concatenation in ORDER BY
"SELECT * FROM products ORDER BY " + sortColumn — parameterized queries can't handle column names, so developers concatenate them. Attackers inject a subquery.
LIKE clauses with unescaped wildcards
Search features that pass user input into a LIKE clause without escaping % and _ characters, or worse, interpolate the whole string.
Bulk operations with dynamic IN clauses
"DELETE FROM items WHERE id IN (" + ids.join(",") + ")" — building the IN list from user input without parameterizing each value.
How Flowpatrol detects SQL Injection
Flowpatrol tests for SQL injection by sending real payloads to your app's endpoints and analyzing the responses — the same technique used in professional penetration testing:
- 1Identifies data entry points — login forms, search fields, URL parameters, API request bodies, and any input that might reach a database query.
- 2Sends injection payloads tailored to common databases: single quotes, UNION SELECT statements, boolean-based blind payloads, and time-based delays.
- 3Analyzes response differences — compares responses between normal and injected input to detect error messages, data leaks, or timing anomalies that confirm the query was modified.
- 4Reports the exact injection point with the payload that worked, the parameter it was injected through, and the parameterized query pattern to fix it.
SQL injection is one of the easiest vulnerabilities to fix — use parameterized queries — and one of the most dangerous to miss. Flowpatrol catches it before your users do.
Related terms
Check your app for SQL injection.
Flowpatrol sends real injection payloads to your endpoints and checks if they land. Paste your URL and find out.
Try it free