DEV Community

Cover image for SQL Injection in Cursor-Generated Code: What Gets Missed
Charles Kern
Charles Kern

Posted on

SQL Injection in Cursor-Generated Code: What Gets Missed

TL;DR

  • Cursor and most AI editors generate SQL queries with user input interpolated directly into template literals
  • This is CWE-89 SQL injection — parameterized queries fix it completely in about ten seconds
  • Automated SAST catches this before it ships; most AI-generated codebases don't run any

I've been reviewing codebases built mostly with Cursor and Claude Code for a few months. The SQL injection pattern keeps showing up. Not the obvious string concatenation from old PHP tutorials. The modern version. Template literals.

Ask Cursor to write a basic filter route and it generates something that looks clean, passes all your tests, runs perfectly in development, and doesn't trip any warnings. The problem only surfaces when someone sends a crafted input to a production endpoint.

The Vulnerable Pattern (CWE-89)

Here's what gets generated for a basic user filter endpoint:

app.get('/api/users', async (req, res) => {
  const { filter } = req.query;
  const result = await db.query(
    `SELECT * FROM users WHERE name = '${filter}'`
  );
  res.json(result.rows);
});
Enter fullscreen mode Exit fullscreen mode

This is SQL injection. The string interpolation puts filter directly into the query. Send ' OR '1'='1 as the filter value and you get every row in the table. The ${filter} part isn't special syntax the database understands. It's string concatenation happening before the query even reaches the database.

This pattern is CWE-89. It's been in the OWASP Top 10 for over a decade. It just looks different now because backtick strings replaced concatenation operators, making it less visually obvious than the old "SELECT * FROM users WHERE name = '" + filter + "'".

Why AI Editors Keep Writing This

The training data is the answer. These models learned from millions of code examples, a significant portion of which use string interpolation for database queries. Tutorials from 2014 to 2018. Stack Overflow answers written before parameterized queries were the default. GitHub repos that work perfectly and were never audited.

The model learned that template literals work. Because they do. The code runs. Tests pass. Nothing flags it in development. There's no feedback mechanism that would teach the model this specific pattern is dangerous.

There's also a subtlety here: the code is structurally correct. The async/await pattern is right. The route handler shape is right. The variable names are reasonable. Only the query construction is wrong, and that wrong part is exactly one line.

The Fix

Parameterized queries. Every modern database driver supports them.

app.get('/api/users', async (req, res) => {
  const { filter } = req.query;
  const result = await db.query(
    'SELECT * FROM users WHERE name = $1',
    [filter]
  );
  res.json(result.rows);
});
Enter fullscreen mode Exit fullscreen mode

The query string and the user data are now separate arguments. The pg driver handles all escaping. $1 is a placeholder, not a string interpolation point. For mysql2, use ? instead. For Sequelize or Prisma, use the ORM's query builder methods rather than raw SQL strings and this whole class of problem disappears.

The fix takes about ten seconds once you know what to look for. A codebase-wide search for db.query calls where the first argument contains a backtick will surface most instances quickly.

I've been using SafeWeave to catch this automatically. It hooks into Cursor and Claude Code as an MCP server and runs Semgrep with SQL injection detection rules as part of its scan. A pre-commit hook with semgrep --config p/sql-injection also works if you want something lighter. The pattern is well-documented enough that automated detection is reliable. The important thing is catching it before it ships, not during an incident review.

Top comments (0)