Where it shows up
- Raw queries written with template strings, such as a search box that builds a WHERE clause.
- Database functions that run dynamic SQL with EXECUTE and concatenated arguments.
- Filter strings passed to REST layers, for example a value dropped into an or() expression without escaping.
- Sort and column names taken from the URL, because parameters cannot stand in for identifiers.
The unsafe and the safe version
The first query lets the input change its meaning. The second sends the value separately so the database never parses it as SQL.
// unsafe
await db.query(`select * from products where name = '${name}'`);
// safe
await db.query('select * from products where name = $1', [name]);Identifiers need an allow-list
Column names and sort directions cannot be parameters. Accept only values from a fixed list and reject everything else.
const sortable = new Set(['created_at', 'price', 'name']);
const column = sortable.has(req.query.sort) ? req.query.sort : 'created_at';How to test your own app
- Search your codebase for query calls that contain a template string or a plus sign next to a variable.
- In a staging copy, submit a single quote in each text field and URL parameter. A database error message in the response means input reaches the query unescaped.
- Give the app database user only the permissions it needs, so a bug cannot drop tables.
Frequently asked questions
Does using an ORM or a database client library make me safe?
Mostly, for normal queries, because they send values as parameters. Raw query helpers, dynamic SQL inside database functions and string-built filters are still risky.
Is escaping quotes enough?
No. Escaping is easy to get wrong and does not cover identifiers or numeric contexts. Use parameters.