I asked Cursor to write a login endpoint last week. It gave me clean, readable code that worked on the first try. It also handed me a textbook SQL injection hole, and it did it with a straight face.
Here is the thing about AI-generated database code. It almost always works when you test it with normal input. Type in a real email, get a real user back. Ship it. The problem only shows up when someone types input that was never a name or an email in the first place.
This is the pattern I see in nearly every AI-generated query (CWE-89):
const email = req.body.email;
const query = `SELECT * FROM users WHERE email = '${email}'`;
const user = await db.query(query);
Looks fine. Reads fine. Now watch what happens when someone sends this as the email:
' OR '1'='1
The query becomes SELECT * FROM users WHERE email = '' OR '1'='1'
. That returns every row in the table. Swap the payload and an attacker can drop tables, read other schemas, or dump your entire user list. No exotic tooling required. Just a text field and five seconds.
AI editors learned to code from millions of tutorials and StackOverflow answers, and a huge chunk of those examples build SQL by mashing strings together. It is the shortest way to show a query in a blog post. The model reproduces the shortest, most common pattern, not the safest one. Template literals in JavaScript make it worse because the interpolation looks so clean that the danger disappears entirely.
The model is not reasoning about a malicious user. It is pattern-matching against what a query "usually looks like." Nobody in the training data labeled the interpolated version as the dangerous one.
Never put user input into the SQL string. Pass it separately as a parameter and let the driver handle escaping:
const email = req.body.email;
const query = 'SELECT * FROM users WHERE email = $1';
const user = await db.query(query, [email]);
The database now treats email strictly as a value, never as executable SQL. The malicious payload becomes a literal search for a user whose email is that exact string. It matches nothing. Attack neutralized.
Same rule in Python with psycopg2:
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
If you use an ORM like Prisma, Drizzle, or SQLAlchemy, the parameterization is handled for you, right up until you reach for a raw query helper. That is where the interpolation sneaks back in, so grep your codebase for raw query calls specifically.
I have been running SafeWeave for this. It hooks into Cursor and Claude Code as an MCP server and flags interpolated SQL the moment it gets generated, before I move on to the next file. That said, even a basic pre-commit hook with semgrep will catch most of what is in this post. The important thing is catching it early, whatever tool you use.