Critical CWE-89 OWASP A03:2021

SQL Injection in Vibe Coded Apps

How to find and fix the vulnerability that lets attackers steal your entire database

Quick Answer

SQL injection lets attackers run their own commands on your database by typing malicious code into form fields. They can steal every user record, delete tables, or bypass login entirely. It's ranked #3 on OWASP Top 10 and has been for 20+ years. AI coding tools generate vulnerable patterns by default, so vibe coded apps are especially at risk.

#3
OWASP Ranking
CWE-89
20+ years
In OWASP Top 10
Critical
Severity

Source: OWASP Top 10 (2021)

What is SQL injection?

SQL injection is what happens when user input touches database queries without protection. An attacker types malicious SQL into a form field, your code concatenates it into a query, and suddenly they're running whatever commands they want on your database. Entire user tables get dumped in seconds.

Think of your database as a vault and your queries as the combination. SQL injection is when someone tricks you into adding their numbers to the combination. They don't break in. You open the door for them.

According to OWASP Top 10 (2021), injection attacks rank #3 in web application security risks. SQL injection has been in every OWASP Top 10 since the list began in 2003. It's in the CWE Top 25. Two decades later, we're still getting this wrong. AI tools generate vulnerable patterns faster than ever.

How do AI tools cause SQL injection?

Every major AI coding tool generates injectable queries by default. They use template literals because the code looks clean. Clean code that opens a backdoor to your database.

The pattern behind most breaches

Ask any AI tool for a database query. This is what you get:

// Cursor, Bolt, Claude Code - all of them generate this
const getUser = async (userId) => {
  const result = await db.query(`
    SELECT * FROM users WHERE id = ${userId}
  `)
  return result.rows[0]
}

Looks clean. Runs fine. Also completely vulnerable. The userId goes straight into the query string with no escaping or parameterization, so attackers can run whatever SQL they want.

Why AI does this: The models are trained on public code, and most public code is insecure. Template literals are everywhere because developers like readable code. AI gives you what it's seen most, not what's safest. It doesn't know the difference.

Cursor, Claude Code, Bolt, v0, GitHub Copilot. Every major tool generates this pattern. Not a bug in any specific tool. Just how AI code generation works. Catching it is on you.

What could happen if I have SQL injection?

All of this has happened. Multiple times. To companies bigger than yours.

  • Full database dump: Attacker runs one UNION SELECT and walks away with every user record, every password hash, every piece of payment data stored. Incident responders watch this happen in real-time. It takes minutes.
  • Silent data manipulation: Change product prices to $0. Grant themselves admin access. Edit records to cover their tracks. Companies don't notice for weeks. Then customers complain. Revenue looks wrong.
  • Authentication bypass: Classic ' OR '1'='1 injection. They log in as admin without knowing any password. Suddenly they have full access to dashboards, user data, everything.
  • Total destruction: One DROP TABLE command, entire database gone. Hope there are backups. Hope those backups work.
  • Server takeover: Some database configs allow command execution. Attackers go from SQL injection to shell access. Now they own the server, not just the data.

The incident response bill alone costs more than the time it takes to fix your queries now.

How do I detect SQL injection?

In post-breach forensics, this is the first thing investigators check. Search for template literals or string concatenation anywhere near a database query. If you see $${variable} or + variable inside a query, you've found the problem.

Patterns to search for
// Template literals in queries (DANGEROUS)
db.query(`SELECT * FROM users WHERE id = ${userId}`)

// String concatenation (DANGEROUS)
db.query("SELECT * FROM users WHERE id = " + userId)

// Raw queries with interpolation (DANGEROUS)
prisma.$queryRaw(`SELECT * FROM users WHERE email = '${email}'`)

// Regex to find these patterns:
// \.(query|execute)\s*\(\s*`[^}]*\$\{
// \$queryRaw\s*\(\s*`[^}]*\$\{

Don't want to search manually?

Scan your code free

How do I fix SQL injection?

The fix is straightforward once you know what to look for: parameterized queries. Every time user input is passed as a parameter instead of concatenated, this entire class of attack gets blocked. Codebases with dozens of injectable queries can be cleaned up in an afternoon. Here's how.

AI Fix Prompt

This prompt has been tested on production codebases. Copy it into Cursor, Claude Code, or Bolt and let the AI do the tedious search-and-replace:

Copy-paste this prompt
Fix all SQL injection vulnerabilities in my codebase. ## What to look for Search for these dangerous patterns: 1. Template literals in database queries: - `SELECT * FROM table WHERE column = ${variable}` - `INSERT INTO table VALUES ('${value}')` 2. String concatenation in queries: - "SELECT * FROM users WHERE id = " + userId - 'DELETE FROM posts WHERE id = ' + postId 3. Raw query methods with interpolation: - prisma.$queryRaw(`...${var}...`) - sequelize.query(`...${var}...`) - knex.raw(`...${var}...`) ## How to fix Replace all string interpolation with parameterized queries: ### For raw SQL (pg, mysql2, better-sqlite3): ```javascript // Before (vulnerable) const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`) // After (secure) const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]) ``` ### For Prisma: ```javascript // Before (vulnerable) const users = await prisma.$queryRaw(`SELECT * FROM users WHERE email = '${email}'`) // After (secure) const users = await prisma.$queryRaw(Prisma.sql`SELECT * FROM users WHERE email = ${email}`) // Or better - use Prisma's built-in methods: const users = await prisma.user.findMany({ where: { email } }) ``` ### For Knex: ```javascript // Before (vulnerable) const results = await knex.raw(`SELECT * FROM orders WHERE user_id = ${userId}`) // After (secure) const results = await knex.raw('SELECT * FROM orders WHERE user_id = ?', [userId]) ``` ## Framework-specific notes - Next.js API routes: Check all /app/api/ and /pages/api/ files - Express: Check all route handlers that interact with databases - Prisma: Prefer findMany/findUnique over $queryRaw when possible - Supabase: The JS client methods are safe; check any custom SQL functions ## After fixing 1. Search for remaining patterns: `\$\{.*\}` inside any query-related code 2. Add input validation before queries as defense in depth 3. List all files you modified with before/after snippets Please proceed systematically through my codebase.

Manual Fix

If you want to understand what's happening under the hood (and you should), here's the change. One pattern causes breaches, the other doesn't.

VULNERABLE
// Template literal - DANGEROUS
const getUser = async (userId) => {
  const result = await db.query(`
    SELECT * FROM users WHERE id = ${userId}
  `)
  return result.rows[0]
}

// An attacker inputs: 1 OR 1=1
// Query becomes: SELECT * FROM users WHERE id = 1 OR 1=1
// Result: Returns ALL users
SECURE
// Parameterized query - SAFE
const getUser = async (userId) => {
  const result = await db.query(
    'SELECT * FROM users WHERE id = $1',
    [userId]
  )
  return result.rows[0]
}

// An attacker inputs: 1 OR 1=1
// Query becomes: SELECT * FROM users WHERE id = '1 OR 1=1'
// Result: No match found (treated as literal string)

What changed: The variable is now passed as a parameter ($1) instead of being concatenated into the query string. The database treats it as data, not as SQL code. That ' OR '1'='1 the attacker types? It's now just a weird string that matches nothing. Same code structure, completely different security posture.

Frequently asked questions

Is SQL injection still a real threat in 2025?

Yes. SQL injection breaches happen regularly. It's been in the OWASP Top 10 every year since 2003, currently ranked #3. The attacks have gotten more automated, not less. AI coding tools make it worse by generating vulnerable patterns faster than ever. Modern frameworks don't automatically save you.

Does using Prisma protect me from SQL injection?

Mostly, but Prisma apps still get compromised. The standard methods like findMany() and create() are safe. The danger is $queryRaw and $executeRaw. The moment you use string interpolation with those, you're vulnerable. Security audits flag this constantly. Use Prisma.sql template tags or stick to the built-in methods.

Can SQL injection happen in NoSQL databases like MongoDB?

Yes, and it catches people off guard. NoSQL injection is real. Different syntax, same damage. MongoDB's $where clauses are especially dangerous. Entire document collections have been dumped through query manipulation. The rule is universal: never concatenate user input into any database query, SQL or not.

Why do AI coding tools generate SQL injection vulnerabilities?

Because AI optimizes for "works and reads clean" not "secure by default." Template literals look nice. The training data is full of insecure patterns. When you vibe code a database query, the AI gives you working code that's also vulnerable. Startups have shipped apps where every database call was injectable. The AI doesn't know better. You need to.

How quickly can an attacker exploit SQL injection?

Under 60 seconds with the right tools. sqlmap automates detection, exploitation, and data extraction. By the time an alert fires (if monitoring even exists), the user table is already dumped. There's no "fix it when we see suspicious activity." Prevention is the only real option.

Related content

Find these before an attacker does

Too many incidents start with "we thought our code was fine."

Scan your code now