34

Secrets and injection: parameterization, not concatenation

#
  • practice
  • established
  • fragment

Injection is the erasure of the boundary between program and data: a concatenated string goes to the interpreter (SQL, shell, LDAP, HTML) whole, and it cannot know which part of it was user input. Parameterization does not “escape dangerous characters” — it sends the query and the values over separate channels, so a value never becomes code in the first place. Manual escaping is not equivalent: it again relies on you having guessed the receiver’s grammar.

The first widely read public description of SQL injection is usually traced to Jeff Forristal’s article in Phrack issue 54 (1998), and injection has been in every edition of the OWASP Top 10 since. The fix has been known for as long, which is why it is stated as a rule rather than a technique: the OWASP SQL Injection Prevention Cheat Sheet puts prepared statements with bound parameters first and treats escaping user input as a last resort. The same split exists outside SQL: in Python, subprocess.run(["convert", path, out]) passes an argument vector to the program with no shell in between, while shell=True with an f-string hands the whole line to /bin/sh to parse.

Code example (fragment)
fragment
Python
# The only correct way, in every language
cur.execute("SELECT * FROM users WHERE email = %s", (email,))  # not an f-string. ever.
Java
var ps = conn.prepareStatement("SELECT * FROM users WHERE email = ?");
ps.setString(1, email);

Parameterization covers values, not identifiers: table names, column names and the ORDER BY direction cannot be bound through a placeholder. There only an allowlist or a type-safe query builder works. Managing the secrets themselves is a separate topic (35); this entry is only about injection.

Where parameterization quietly stops. Not every driver sends values on a separate channel: psycopg2 interpolates on the client side (correctly, from type information), psycopg 3 binds on the server. The guarantee you rely on is that the driver encodes the value, never your code. It ends inside the database too: a PL/pgSQL function that builds a statement for EXECUTE by concatenation is injectable however the caller bound its arguments — use format('%I', …) for identifiers and USING for values. A bound value in a LIKE pattern is still a pattern, so % and _ from the user need escaping. And prompts have no such channel at all: instructions and data share one token stream, which is why prompt injection is handled by limiting what the model can do (48, 49), not by escaping.

Last technical review: 2026-08-04 · Reference · 112 principles