Parameterized Query

A parameterized query (also called a prepared statement with bound parameters) sends the SQL text and the values it operates on to the database separately. The text contains placeholders such as ? or :name, and the values are supplied alongside. The database treats the placeholders as values rather than SQL syntax. Preparing, caching, and recompiling the statement depend on the database and driver. Because values are never parsed as SQL, an input cannot change the structure of the query.

The problem it solves

SQL injection happens when a program builds a statement by pasting text into a string. The database receives one string and cannot tell which parts the programmer wrote and which came from outside. In the demo below, a tool that looks up an order checks both the order id and the calling customer.

import sqlite3
db = sqlite3.connect(":memory:")
db.executescript("""create table orders(id text, customer text);
insert into orders values('A-1','ana'),('B-1','ben');""")
caller = "ana"
arg = "B-1' OR customer='ben' --"

q = "SELECT id, customer FROM orders WHERE id = '%s' AND customer = '%s'" % (arg, caller)
print(db.execute(q).fetchall())
print(db.execute("SELECT id, customer FROM orders WHERE id = ? AND customer = ?", (arg, caller)).fetchall())

The first call prints [('B-1', 'ben')]. The pasted string became ... WHERE id = 'B-1' OR customer='ben' --' AND customer = 'ana': the quote closed the literal, OR customer='ben' added a condition, and -- commented out the caller check. The second call prints [], because the whole string, quotes and all, is compared as an id and matches nothing.

Why this is stronger than escaping

Escaping tries to turn dangerous characters into harmless ones before pasting. Doubling the single quote in the demo does stop that particular input: the escaped string returned [] as well. The weakness is that correctness depends on knowing every rule of the target dialect and on applying escaping in the right place every time. The rules differ across databases and character encodings, and they depend on the context: a value inside a quoted literal needs different treatment than one in a numeric position. The numeric case shows it. With the argument 0 OR 1=1 pasted into WHERE rowid = ..., no quote is involved, so escaping quotes changes nothing, and the count query reported 2 matching rows. The parameterized form returned 0, because the string 0 OR 1=1 is just a value that equals no row id. Parameterization removes the class of error rather than avoiding individual strings, which is why OWASP's guidance names it, rather than input filtering, as the primary defense.

What it does not cover

Placeholders stand for values, not for parts of the syntax. Table names, column names, sort directions, and operators cannot be bound. In SQLite, SELECT ? FROM orders with the value "customer" returns the literal string customer for each row rather than the column. When a tool must choose an identifier from user input, check it against an explicit allow-list of names, then insert the approved name into the statement:

ALLOWED = {"id", "customer"}
col = "customer"
if col not in ALLOWED:
    raise ValueError("unsupported column")
db.execute(f"SELECT {col} FROM orders")

Parameterization also does nothing about authorization. The query above still needs its customer = ? condition to prevent one user from reading another's rows. And it protects only the SQL boundary: shell commands, file paths, and outbound URLs built from model-proposed arguments each need their own safe construction, such as passing an argument list instead of a shell string or resolving a path against a fixed base directory.

Use with tool-calling systems

A model-driven tool receives arguments that are whatever the model emitted, possibly influenced by text it read. Validating the argument's type and pattern narrows what reaches the query, but the query construction is the last line of defense. Use placeholders everywhere a value enters a statement, including values that look safe such as integers and ids.

References


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.