SQL injection and safe queries
The most common and most damaging database vulnerability has one cause: building SQL by gluing user input into a query string. When input becomes part of the SQL text, an attacker can rewrite your query. The fix is simple and absolute - and you should adopt it as a reflex.
The attack
Say a login looks up a user by name, and the app builds the query by concatenation:
// DANGEROUS - never do this
const sql = "SELECT * FROM users WHERE name = '" + input + "'";
For a normal name it works. But an attacker types ' OR '1'='1 as the name. The string becomes ... WHERE name = '' OR '1'='1' - always true - and every row comes back. Run it:
Every user and token leaked. Worse inputs can read other tables, or with stacked statements like '; DROP TABLE users; -- destroy data. The query did exactly what the text said - the problem is that the attacker got to write part of the text.
The fix: parameterized queries
Never assemble SQL from input. Use parameterized queries (also called prepared statements): write placeholders, and pass the values separately. The driver sends the SQL and the data on different channels, so input is only ever treated as a value, never as SQL to parse.
// safe - the driver binds `input` as a value
db.query('SELECT * FROM users WHERE name = $1', [input]);
# safe - placeholders, values passed separately
cur.execute("SELECT * FROM users WHERE name = %s", (input,))
Now ' OR '1'='1 is searched for literally as a name - it matches nothing, exactly as intended. ORMs and query builders parameterize by default, which is a big part of why they improve safety. There is no performance reason to concatenate; parameterized queries are also faster to reuse.
Defense in depth
Parameterizing is the main fix. Layer more behind it:
- Least privilege. If the app's role can only
SELECTwhat it needs, an injection that slips through can do far less. (See DCL.) - Validate and allowlist. You cannot parameterize a table or column name - placeholders are for values only. When SQL must include a dynamic identifier (a sort column, say), check it against an allowlist of known-good names; never interpolate raw input.
- Avoid dynamic SQL where you can; keep generated SQL minimal and reviewed.
The rule in 2026 is unchanged because the fix has never changed: parameterize every query that touches user input. One extra note - SQL generated by an LLM or copied from the web deserves the same scrutiny; review it for concatenated input before it ships.
Quick quiz
SQL injection
4 questions1What is the root cause of SQL injection?
2How do parameterized queries prevent injection?
3You need to let users sort by a column they pick. Why can't you just parameterize the column name?
4Which extra layer limits the damage if an injection does get through?
ORMs and migrations - how application code talks to the database, and how the schema evolves safely over time.