use case

SQL injection in AI-generated code: the string-built query problem

the short answer

AI coding tools often generate SQL by interpolating variables into query strings — f-strings and .format() in Python, template literals and concatenation in JavaScript — which works perfectly in testing and is classic SQL injection in production; the fix is parameterised queries (placeholders with values passed separately), and securevibes's injection checks flag every string-built query in a repo with the file, line, and a paste-ready prompt to convert it.

SQL injection is one of the oldest vulnerabilities on the web, and AI coding tools have given it a second life. Ask an agent to "get the user by email" and there's a real chance you get back a query with the email interpolated straight into the string. It runs, it returns the right rows, every test passes — and anyone who controls that input can rewrite your query.

This page covers why generated code keeps making this exact mistake, how to recognise the pattern in the languages vibe-coded apps are usually written in, and what the correct version looks like. It's the core of what securevibes's injection & unsafe code category — weight 20 in the overall score — exists to catch.

40.4%of 99 vibe-coded repos had at least one injection-category finding — the second most-hit categorySource: securevibes rules-engine study, 99 public AI/vibe-coded GitHub repos, collected june 10, 2026

Why generated code keeps doing this

String interpolation is the most direct way to get a value into a query, and the most direct version is what code generators reach for — especially because decades of tutorials, Stack Overflow answers, and example snippets in their training data do exactly this. The insecure version isn't an exotic failure; it's the statistically typical way the operation gets written.

It also survives because it's invisible to the vibe-coding feedback loop. An injectable query behaves identically to a safe one on every normal input; the difference only appears when someone sends a crafted one — a quote, a UNION SELECT, a stacked statement. "It works" tells you nothing here, which is why this bug ships so often.

How to spot it in your own repo

The tell is a variable inside a query string. In Python: an f-string with a brace inside SQL (f"SELECT * FROM users WHERE email = '{email}'"), .format() on a query, or % formatting and + concatenation building SQL. In JavaScript and TypeScript: a template literal with ${} inside a query, or strings glued together with +. If user input can reach that variable — a form field, a URL parameter, a JSON body — the query is injectable.

The parameterised fix has the same shape everywhere: a placeholder in the query, the value passed separately, so the database driver treats input as data rather than executable SQL. Python's drivers use %s or ? with a parameters tuple; node-postgres uses $1 with a values array; and if you're on an ORM like SQLAlchemy or Prisma, you're safe by default unless you've dropped into raw-SQL escape hatches with interpolation — which is exactly where generated code sometimes ends up.

Fixing every instance, not just the one you found

The dangerous failure mode is fixing the query you noticed and missing the four others written the same way — a codebase that built SQL with strings once almost always did it repeatedly. This is why a systematic pass beats spot-fixing, and why securevibes scans the whole repo: its injection checks flag SQL built with f-strings, .format(), concatenation, or template literals, alongside the neighbouring hazards (eval/exec, shell commands built from variables, subprocess shell=True, pickle, unsafe yaml.load, innerHTML and dangerouslySetInnerHTML).

Each finding lands with the file, the line, why it matters, the parameterised fix — and a ready-to-paste Claude prompt that includes the constraint "check for the same pattern elsewhere", so the agent that introduced the pattern hunts down every instance of it rather than patching one line. Worth saying plainly: this is pattern detection, not proof of exploitability; securevibes doesn't execute your code, so treat every flagged query as worth converting rather than waiting for certainty.

String-built (injectable) vs parameterised, per language

Language / stackInjectable pattern to look forParameterised fix
Python (DB-API drivers)f"...WHERE email = '{email}'", .format(), % or + building SQLcursor.execute("...WHERE email = %s", (email,)) — or ? depending on driver
JavaScript / TypeScript`...WHERE id = ${id}` template literals, + concatenationquery("...WHERE id = $1", [id]) with node-postgres or equivalent
ORMs (SQLAlchemy, Prisma...)Raw-SQL escape hatches with interpolated stringsStay on ORM query methods; pass bound parameters in raw queries

frequently asked

My inputs come from my own frontend — am I still at risk?
Yes. Attackers don't use your frontend; they send requests directly to your API. Any string-built query reachable from a request is injectable regardless of what your UI allows.
Is escaping or sanitising the input enough?
Parameterised queries are the reliable fix — the driver keeps data and SQL separate by construction. Hand-rolled escaping is exactly the kind of subtle, easy-to-get-wrong code you don't want, especially in a codebase you didn't write line-by-line yourself.
I use an ORM — can I skip this check?
Mostly safe, with one caveat: raw-SQL escape hatches. Generated code sometimes drops out of the ORM into a raw query with interpolation, which reintroduces the problem. A scan catches those spots.
How does securevibes report an injection finding?
With the severity, the file and line, the evidence, why it matters, the parameterised fix, and a paste-ready Claude prompt that includes "check for the same pattern elsewhere" — because string-built SQL almost never appears just once.

Published June 10, 2026 · Last updated June 11, 2026

ready to try securevibes?

scan your repo