SQL injection is older than most engineers reading this. It still ships every year — because ORMs have escape hatches, because string concatenation is convenient, because some teams ship dynamic SQL builders. The fix has been known for 25 years; the discipline still slips.

Advertisement

Parameterized queries — the whole answer

db.query('SELECT * FROM users WHERE id = ?', [user_id]). The driver sends the query and parameters separately; the database treats parameters as data, not SQL. No string concat = no injection. Every language driver supports this; use it.

ORM escape hatches

Most ORMs have .raw() or .query() methods accepting raw SQL. Often combined with string interpolation 'for performance' or 'for dynamic ORDER BY'. Audit these — they're where injection lives in 2026.

Advertisement

Dynamic identifiers

You can't parameterize table names or column names. So ORDER BY ${user_field} is fundamentally unsafe. Whitelist allowed values; reject unknowns. Don't try to escape — there's no portable safe way.

Stored procedures aren't a fix

A vulnerable proc that builds dynamic SQL inside is just as vulnerable. The proc must use parameterized internal queries too.

Detection in CI

CodeQL queries for tainted-data-to-SQL flow. Semgrep rules for string concat in DB calls. Snyk/Sonar dataflow rules. Run on every PR; gate merges.

Parameterized queries everywhere, whitelist identifiers, static analysis in CI. The fix has not changed since 2001.