You adopted AI but the delivery timeline didn’t change. Our paper explains why →
October 1, 2026

When we realized that using parameterized queries isn’t enough

By Bhumika Shah
Security testing illustration showing why parameterized queries alone may not prevent SQL injection vulnerabilities, highlighting the need for broader application security testing.

How Anthara caught a real SQL injection in its own generated code, after the tests had already passed.

Every developer has said it at some point, in a code review or a Slack thread defending a pull request. “We use parameterized queries, we’re safe from SQL injection.” That’s true, but it’s also incomplete. The gap rarely shows up until something breaks it.

We found the gap in a live implementation that had passed all its tests. An AI coding agent generated it as part of an internal benchmark we run to compare ways of building software with AI. The test suite was green and the lint was clean, yet an exploitable SQL injection was sitting in the code the whole time.

A second AI process caught it when it reviewed the first agent’s work even after everything looked okay.

How a Review Step Caught What Parameterized Queries Missed

We were benchmarking Anthara, our own AI-assisted development plugin, on a small backend service. It was a car dealership inventory system with a handful of REST endpoints: register, log in, list vehicles, search, update, purchase, restock. Standard stuff. The bug lived in one of them, the endpoint that updates a vehicle’s details.

The agent split the work into two layers. A service layer (vehicleService.ts) figured out which fields to update, and a data layer (db.ts) built and ran the SQL. Every value that touched the database went through a parameterized query, with ? placeholders and bound parameters and nothing concatenated into the SQL string. This is the pattern every security guideline tells you to follow.

The problem starts with this line in the service layer:

const fields = Object.keys(updates) as (keyof VehicleUpdate)[];

This takes whatever keys exist on the incoming update object, and that object was built straight from the request body. Whatever field names a client sent in their JSON payload became the field names the service layer worked with.

Right below it, each field was supposed to run through a validator before doing anything:

updateFieldValidators[field]?.(value);

That ?. is optional chaining. If field isn’t one of the recognized keys, this line doesn’t throw, doesn’t reject, and doesn’t log a warning. It skips the field and moves on, so the validator never ran on it.

The column names came from the request body

Those unvalidated field names flowed straight from the service layer into the data layer, where they became the column names in a dynamically built SET clause:

db.prepare(`UPDATE vehicles SET ${setClause} WHERE id = ?`).run(…values);

The values in that query were bound and parameterized, as they should be. But the column names in setClause came straight from the request body, and nothing had ever checked them.

Most SQL injection advice is about values. Don’t concatenate user input into the query string where data goes. This vulnerability lived one level up, in the identifier position, the part of the query that names which column gets updated. Parameterized queries only protect values, because SQL doesn’t let you parameterize a column name the way you parameterize a value. If your code is choosing column names based on user input, that needs its own check, and this code skipped it.

The AI agent that wrote this code proved the vulnerability was real by exploiting it itself, as part of its own review process. It sent a request body with a field name built to inject extra SQL into the SET clause, and the response came back showing price: -500, quantity: 999999. That one request got past the business rules meant to block negative prices and absurd quantities.

A second review caught it after the tests passed

By the time this vulnerability was in the codebase, the implementation had already reached “complete,” with the tests green and the lint clean. In a typical AI coding session that’s where things stop. Tests pass, ship it.

Anthara doesn’t stop at green tests. Its process includes an automated end-of-spec review, a second pass where a dedicated reviewer step re-reads the finished implementation and looks for what a test suite doesn’t check: security patterns, architectural soundness, edge cases nobody wrote a test for. It works like handing the finished code to a reviewer who wasn’t in the room while it was written and asking them to look with fresh eyes.

This review flagged the vulnerability as a high-severity finding, and the fix was a whitelist.

const ALLOWED_UPDATE_FIELDS = new Set([“make”, “model”, “category”, “price”, “quantity”]);

The service now checks every incoming field name against this list before it goes anywhere near a query. If a field isn’t on the list, the service throws a validation error and rejects the

request. The fix also came with a regression test that uses the exact exploit payload, so if this pattern ever comes back, the test will catch it.

Parameterized queries and passing tests are not enough

“We use parameterized queries” is a true statement about a useful practice, but it isn’t a complete security posture. A codebase can follow that rule perfectly for every value in every query and still be wide open, because the rule only ever covered values.

The second lesson is about process. Passing tests and clean lint are necessary for shipping code, but they aren’t enough on their own, and this vulnerability sailed through both. No stricter test would have caught it. It took a separate review pass, built into Anthara’s workflow, that runs after the tests and lint have passed and looks for the things they don’t check.

“The code works” and “the code is safe” are two different questions. They need different kinds of checking, and Anthara runs both.

This finding came out of an internal benchmark comparing approaches to AI-assisted software development, using Anthara, Incubyte’s AI-assisted development plugin. The vulnerability, the exploit, and the fix described here are real, taken directly from a live run.

About the Authors

Bhumika Shah

Product Craftsperson, Incubyte

Bhumika writes about AI in software engineering, model evaluation, and what it takes to adopt new tools without compromising craft. She holds a PhD in Computer Science and brings together years of academic research with hands-on industry work -- currently focused on evaluating AI models across craft, speed, and cost to help engineering teams make sharper decisions. She is equally invested in helping engineers grow, and approaches both research and mentorship with the same rigor and curiosity that have defined her career.

Avani Prajapati

Software Craftsperson, Incubyte

Avani writes about full-stack development, AI-assisted engineering, and the day-to-day practices that make software reliable, secure, and worth inheriting. At Incubyte, she works across the full stack with a craftsperson's attention to quality and a particular focus on how AI tools are reshaping the way developers build -- without lowering the bar on what actually gets shipped. Outside of code, she paints and draws.

Table of Contents

    Share this resource