All posts

Field guide

SQL injection in AI-generated code: why it still happens and the one-line fix

SQL injection is one of the oldest bugs in web development, and AI coding tools still write it, because the way they naturally build a query is the exact recipe for it. Here is why a solved problem keeps reappearing in generated code, and the one change that closes it for good.

Last updated

SQL injection has been understood for decades, and the fix is not a secret. Yet it keeps showing up in AI-generated code, because the most natural way to write a query, gluing the input into the SQL string, is precisely what creates the vulnerability. The generated code works in every demo, so nothing signals the problem until someone sends input the author never pictured.

How the bug works

When user input is concatenated into a SQL string, the database cannot tell your intended query from the attacker's addition, because it parses the whole string as one piece of SQL. Special characters in the input, like a quote, let the attacker end your intended value and start writing their own SQL.

// the vulnerable pattern: input is part of the SQL text
db.query(
  "SELECT * FROM users WHERE email = '" + email + "'"
);

// a crafted email closes the string and adds SQL:
//   ' OR '1'='1
// turns the query into one that matches every row

From there the impact scales quickly. The attacker is running SQL with your application's database access, so one injectable query can be enough to read the whole table, and often to change data or reach beyond it. The concept in full is covered in what SQL injection is.

Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.

See a live scan →

The one-line fix

The fix is a parameterized query, sometimes called a prepared statement. You write the SQL with placeholders and pass the values separately, so the database treats the input as data, never as part of the query. The input can contain any characters it likes and it will never change the meaning of the SQL.

// parameterized: the value is sent apart from the SQL
db.query(
  "SELECT * FROM users WHERE email = $1",
  [email]
);

That is the whole change. Same query, same result on valid input, and the injection is gone because the input can no longer reach the parser. The full walkthrough, including a few framework variations, is in the fix for SQL string concatenation.

ORMs help, until you drop to raw SQL

Most ORMs parameterize values automatically through their normal query methods, which is a good reason to use them as the default. The danger returns when you drop to a raw query or raw fragment for something the ORM makes awkward, and interpolate input into it. A raw query with concatenated input is exactly as vulnerable inside an ORM as without one, so the rule is the same everywhere: parameterize the values, never build the SQL text from input.

The same shape of bug appears with document databases too, where a crafted object rather than a string does the work; see the fix for NoSQL injection.

Frequently asked questions

Why does AI still write SQL injection when the fix is so well known?

Because both the vulnerable and the safe version produce identical results on normal input, so nothing in the model's objective favors one over the other. Building the query by concatenating a string is a common, natural-looking pattern that appears throughout its training data, and it runs correctly in every test with clean input. The model has no signal that the input will ever be hostile, so it ships the version that works, which happens to be the unsafe one.

Does using an ORM mean I am safe from SQL injection?

Mostly, but not automatically. An ORM's normal query methods parameterize values for you, which is why they are a good default. The risk returns the moment you drop to a raw query or a raw fragment and interpolate input into it, which people do for a complex query the ORM makes awkward. So an ORM removes most of the surface, but a raw query with concatenated input is just as vulnerable inside an ORM as without one.

Is escaping the input a valid fix?

Parameterized queries are the reliable fix; manual escaping is not. Escaping by hand is easy to get wrong, differs by database, and misses edge cases, and one missed spot reopens the hole. A parameterized query sends the SQL and the values separately so the database never treats the input as code, which removes the class of bug rather than trying to sanitize your way around it. Reach for parameters, not escaping.

How bad is one SQL injection bug, really?

A single injectable query can be enough to read your entire database, since the attacker is running SQL with your app's database access. Depending on the query and permissions it can also mean modifying or deleting data, and in some setups reading files or running commands on the database host. It is one of the higher-impact bugs precisely because one weak spot can expose everything behind it.

Find it before an attacker does

Injection is one of the patterns a scan catches reliably, because concatenated input in a query has a recognizable shape. A scan reads the repo and points at the exact line, so you can swap in a parameterized query before the endpoint meets its first hostile input. Run a free scan and see whether any of your queries build SQL from input.

Prbl scans the AI-generated parts of your codebase for exactly the kinds of issues above.

SQL Injection in AI-Generated Code: Why It Still Happens and the One-Line Fix: Prbl