Skip to content
CWE-89A03:2021 – Injection

Prevention — Node.js

Placeholders in pg and mysql2, Knex raw bindings, and Prisma's tagged-template-vs-Unsafe distinction.

The Rule

Use placeholders ($1, ?) with the driver's parameter array, or a query builder. Never build SQL with template literals containing user input.

The Node-specific trap is template literals: they look like the natural way to build a string, and they are exactly the wrong tool. The exception is Prisma's $queryRaw, which is a tagged template that parameterises each ${} — but only if you do not defeat it by pre-building the string.

node-postgres (pg)

JavaScriptusers.jsVulnerable
// Template literal with user input. Injectable.
const { rows } = await client.query(
  `SELECT * FROM users WHERE name = '${name}'`
)

// Concatenation, same problem
const r = await client.query(
  "SELECT * FROM users WHERE id = " + id)

mysql2

JavaScriptVulnerable
const [rows] = await conn.query(
  `SELECT * FROM users WHERE name = '${name}'`)

Knex

JavaScriptVulnerable
// Template literal inside raw(), and string concatenation
// in whereRaw, are both injectable.
knex.raw(`SELECT * FROM users WHERE name = '${name}'`)
knex('users').whereRaw("name = '" + name + "'")
knex('products').orderByRaw(sort)   // identifier context

Prisma

TypeScriptVulnerable
// $queryRawUnsafe takes a plain string.
await prisma.$queryRawUnsafe(
  `SELECT * FROM users WHERE email = '${email}'`)

// Defeating the tagged template by pre-building the string:
const q = `SELECT * FROM users WHERE email = '${email}'`
await prisma.$queryRaw(Prisma.raw(q))   // one interpolation = whole query

Checklist

  • Placeholders ($1, ?) with a values array; never a template literal containing user input.
  • pg/mysql2: prefer execute() (real prepares) over query().
  • Knex: builder methods; ?/?? bindings for raw; allowlist for order.
  • Prisma: $queryRaw tagged template with bare ${}; ban $queryRawUnsafe and Prisma.raw(builtString) in review.
  • NoSQL/Mongo: validate types at the boundary — see NoSQL Injection.
  • Identifiers: allowlist.
  • Add an ESLint rule or Semgrep to flag $queryRawUnsafe and template literals in .query(...).
  • Least-privilege database role — see PostgreSQL.