SQLFix Blog

Parameterized queries vs generated SQL

A model that writes a correct-looking query has not solved injection. The security property that matters lives in how the query meets the data, and that is the job of parameterized queries with prepared statements. Generated text is a starting point for review; it is not a substitute for the boundary that prepared statements enforce between code and data.

What a prepared statement actually does

When the driver receives a prepare request it sends the SQL text to the server. The server parses the text, builds an execution plan, and stores that plan. Later, when the application calls execute, the driver sends only the parameter values. The server applies the stored plan to those values without re‑parsing the SQL text. Because the values never become part of the statement string, a payload such as ' OR 1=1-- is treated as a literal value, not as part of the command.

I have seen this work in MySQL 5.0, PostgreSQL 8.0, SQL Server 2005, and Oracle 9i. Every mainstream language provides a binding API: JDBC’s PreparedStatement, Python’s cursor.execute with placeholders, Go’s database/sql.Query, and Node’s mysql2 pool.execute. The model can output the shape SELECT * FROM orders WHERE customer_id = ? AND status = ? ; the surrounding code supplies the two values at runtime.

If you see the quote inside the statement text, you are not using a true prepared statement; you are likely doing client‑side string interpolation, which does not give the injection safety.

Why reviewed SQL still needs parameters

Human review can confirm that the query shape matches the intended business rule. It can spot missing joins, wrong column names, or inefficient filters. However, the values that flow into those placeholders still come from external sources: web forms, API payloads, message queues, or legacy files. Those sources are not under the developer’s direct control at the moment of execution.

OWASP’s SQL Injection Prevention Cheat Sheet states clearly: “Defense Option 1: Prepared Statements (Parameterized Queries)”. The cheat sheet does not list “human approval” as a substitute. Approval addresses logical correctness; parameterization addresses the trust boundary between code and data. Dropping either leaves the other unguarded.

Even though the human reviewer saw a sensible INSERT statement, the injection happened at the value‑binding step, which the review did not cover.

This way the reviewer’s work remains valuable, and the execution step retains its safety guarantee.

Stored procedures are not automatic safety

Many teams assume that moving logic into a stored procedure eliminates injection risk. That assumption fails when the procedure itself builds dynamic SQL.

Consider a procedure written in T‑SQL:

CREATE PROCEDURE GetOrders @Customer NVARCHAR(50) AS BEGIN DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM Orders WHERE Customer = ''' + @Customer + ''''; EXEC sp_executesql @sql; END If @Customer contains '); DROP TABLE Orders;-- , the resulting string becomes a valid batch that drops the table. The procedure wrapper did not protect the data; the concatenation inside the procedure did.

The same pattern appears in PL/SQL with EXECUTE IMMEDIATE, in PostgreSQL with EXECUTE format, and in MySQL with PREPARE/EXECUTE built from a variable.

After the rewrite, run the procedure with a value that contains a quote and verify that the error is a data‑type mismatch, not a syntax error.

Keep generation and execution as separate layers

The safest architecture treats the AI model as a suggestion engine, not as a data‑access component. The model outputs a candidate query shape. A human or automated policy gate reviews that shape for correctness, compliance, and performance. Only after approval does the application code take the shape, prepare it with the driver, and bind the real‑time parameters.

Because the model never sees a live connection, it cannot be tricked into revealing schema details through error messages. Because the execution layer never trusts raw strings, even a compromised model cannot cause injection.

This process keeps the model’s output in the review loop and guarantees that the execution step uses only parameterized statements.

What this is NOT

I want to be clear about the limits of the approach described above. It does not guarantee that every possible bug will be caught. It does not promise 100 % protection against all classes of flaws. It does not claim to eliminate the need for other defenses such as input validation, output encoding, or least‑privilege database accounts.

What the method does is:

If you rely solely on the model’s output and skip the review or the parameter binding step, you lose the protection. If you allow the execution layer to concatenate values after preparation, you reintroduce the risk. The technique is a component of a defense‑in‑depth strategy, not a standalone silver bullet.

Real‑world workflow example

Imagine a SaaS product that lets managers export a list of overdue invoices. The product uses SQLForm to draft the query.

This trace shows how the generation step stays independent from the execution step, and how the parameter boundary blocks injection even when the user tries to smuggle SQL into a field that the model never sees.

Common pitfalls and how we avoid them

Even with a solid design, teams sometimes slip into habits that weaken the protection. Below are frequent mistakes I have observed and the concrete steps we take to keep the system safe.

Some developers build SELECT * FROM tags WHERE tag IN (?, ?, ?) by first creating a string with the right number of placeholders, then concatenating the values directly into the SQL before preparing. This defeats the purpose because the values become part of the statement text.

A statement prepared for SELECT * FROM orders WHERE status = ? is later executed with a value that actually contains a whole new SELECT statement, relying on the driver to treat the value as data. While the value is safely quoted, the application logic may be wrong because the developer assumed the statement could handle any SQL.

Internal dashboards sometimes bypass the human review gate because the team assumes the data is trusted. Later, a service account is compromised and begins feeding malicious payloads into the dashboard.

For debugging, teams sometimes log the exact parameter values. If those logs are exposed, they can leak sensitive data such as passwords or personally identifiable information.

An ORM may offer a method that appears to accept parameters, but under the hood it builds a string and calls ExecuteNonQuery on a raw SQL string.

By watching for these pitfalls and applying the corresponding fixes, the separation between generation and execution stays strong, and the parameterized boundary continues to do the work it was designed for.

References

https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html

https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html

https://cwe.mitre.org/data/definitions/798.html

https://cwe.mitre.org/data/definitions/89

FAQ

No. Injection depends on how values meet the query, not on whether the query is logically correct. Parameterized queries keep values separate from command text regardless of the values' content.

Not automatically. A procedure that concatenates input and executes it with EXECUTE IMMEDIATE can still inject. Safety comes from parameter binding inside the procedure.

An ORM is safe only when you use its parameterized interface. Building a string and passing it to the ORM reintroduces injection. The boundary between code and data still has to be enforced.

No. Keep generation and execution separate: the model drafts, a gate approves, and application code binds parameters at runtime. The model should never be the execution path.

Try the product: SQLForm

2026-09-16 · primary sources only · no fabricated traffic metrics

Related articles

Natural language to SQL: safety checks that matter

Explore the full AI micro-SaaS topic matrix →

Does a correct query protect against SQL injection?

No. Injection depends on how values meet the query, not on whether the query is logically correct. Parameterized queries keep values separate from command text regardless of the values' content.

Are stored procedures safe by default?

Not automatically. A procedure that concatenates input and executes it with EXECUTE IMMEDIATE can still inject. Safety comes from parameter binding inside the procedure.

Can an ORM replace parameterized queries?

An ORM is safe only when you use its parameterized interface. Building a string and passing it to the ORM reintroduces injection. The boundary between code and data still has to be enforced.

Should a model ever hold the database connection?

No. Keep generation and execution separate: the model drafts, a gate approves, and application code binds parameters at runtime. The model should never be the execution path.

Decision-support only. Not a substitute for DBA review or production load testing. No performance guarantees.

← Back to blog · Read full English article

Was this result helpful?Feedback