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.
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 vulnerabilityasync 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 ofdb.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 stringsFix Prompt
This SQL query uses string interpolation with user-supplied values, which is aSQL injection vulnerability. Rewrite every raw query to use parameterizedstatements ($1, $2 placeholders for pg, or the tagged template literal form forpostgres.js). Never interpolate variables directly into SQL strings. If thequery is complex, migrate it to Prisma or Drizzle ORM instead.Test
# Detect template literal interpolation inside SQL-looking stringsgrep -rn 'query(`\|sql`\|execute(`' --include="*.ts" --include="*.tsx" . \ | grep '\${' \ | grep -v "node_modules" \ && echo "FAIL: interpolated SQL found" || echo "OK"