P PasteCode
Failure

How to Fix AI Writing Insecure SQL

AI agents build SQL queries with string interpolation instead of parameterized statements, introducing SQL injection vulnerabilities into production database code.

CursorClaude CodeCodexWindsurf PostgreSQLTypeScript
.md .json Updated Jun 8, 2026

The agent reaches for template literals to build SQL queries, creating classic SQL injection vectors that appear to work fine in tests but are exploitable in production.

The symptom

User-controlled values are interpolated directly into a SQL string.

// WRONG — SQL injection vulnerability
async function getUserByEmail(email: string) {
const result = await db.query(
`SELECT * FROM users WHERE email = '${email}'`
// ^^^^^^^ attacker-controlled
);
return result.rows[0];
}
// Attacker input: ' OR '1'='1
// Resulting query: SELECT * FROM users WHERE email = '' OR '1'='1'
// Returns every row in the table.

Why it happens

Template literals are the most natural string-building tool in JavaScript, and many tutorial examples the model trained on use them for SQL without parameterization. The agent also doesn’t model adversarial inputs — it imagines well-formed data passing through.

How to spot it

  • SQL strings that contain ${...} interpolations.
  • Query functions that accept user input and pass it to a raw query helper without a separate params array.
  • db.query(sql) called with a single argument instead of db.query(sql, [params]).
  • LIKE '%${term}%' patterns.

How to fix it

Always use parameterized queries. The database driver handles escaping; your code never touches quoting.

// CORRECT — parameterized query (node-postgres / pg)
async function getUserByEmail(email: string) {
const result = await db.query(
"SELECT id, name, email FROM users WHERE email = $1",
[email] // second argument: params array
);
return result.rows[0] ?? null;
}
// CORRECT — with Postgres.js (template tag)
async function searchUsers(term: string) {
return sql`SELECT id, name FROM users WHERE name ILIKE ${"%" + term + "%"}`;
// postgres.js automatically parameterizes template expressions
}
[ ] No ${...} inside raw SQL strings — use $1/$2 placeholders instead
[ ] Every db.query() call passes user input via the params array, not the SQL string
[ ] Use an ORM (Prisma, Drizzle) or query builder for complex queries
[ ] LIKE wildcards are appended in the param value, not concatenated into the SQL
[ ] Run sqlfluff or a SQL linter in CI to catch interpolated strings

Fix Prompt

Fix Prompt
This SQL query uses string interpolation with user-supplied values, which is a
SQL injection vulnerability. Rewrite every raw query to use parameterized
statements ($1, $2 placeholders for pg, or the tagged template literal form for
postgres.js). Never interpolate variables directly into SQL strings. If the
query is complex, migrate it to Prisma or Drizzle ORM instead.

Test

Terminal window
# Detect template literal interpolation inside SQL-looking strings
grep -rn 'query(`\|sql`\|execute(`' --include="*.ts" --include="*.tsx" . \
| grep '\${' \
| grep -v "node_modules" \
&& echo "FAIL: interpolated SQL found" || echo "OK"