FlawOpen

flawopen.com/SQL Injection in JavaScript/Knex.js

Is Knex.js safe from SQL injection?

Reference guide
Short answer

Think of SQL as a printed form: a binding puts input in a value field; string interpolation pastes it into the form’s instructions. Knex’s query builder and knex.raw(sql, bindings) keep values separate, but table and column names are SQL structure and need a trusted allowlist.

UNSAFE — SQL assembled from input
// Input is concatenated into SQL source.
const query = knex.raw(
  `SELECT * FROM users WHERE id = ${userId}`
);
SAFE — bind values, allowlist structure
// Keep the value separate from SQL syntax.
const allowedSortColumns = new Set(["created_at", "name"]);

const rows = await knex("users")
  .where("id", userId)
  .orderBy(
    allowedSortColumns.has(requestedSort)
      ? requestedSort
      : "created_at",
    requestedDirection === "asc" ? "asc" : "desc"
  );

// Raw: ? is a value; ?? is an identifier.
knex.raw("SELECT * FROM users WHERE id = ?", [userId]);

How Knex bindings protect values

Knex query-builder methods such as .where(), .insert(), and .select() separate values from generated SQL. For raw expressions, pass values as bindings: knex.raw("... = ?", [value]). Knex documents ? as a value placeholder and ?? as an identifier placeholder. Do not interpolate input into a SQL template string.

Dynamic identifiers need an allowlist

A binding does not authorize a user-selected column, table, sort direction, operator, or SQL fragment. Knex can quote an identifier with ??, but quoting does not decide whether the caller should be allowed to choose it. Map user options to a fixed allowlist before passing them to .orderBy() or identifier bindings. Keep SQL keywords and operators fixed in code.

How to audit raw queries

Search for every knex.raw() call and review its SQL string and bindings together. Confirm data values use bindings and any dynamic identifier comes from a small allowlist. A raw query is not automatically vulnerable; safety depends on how its SQL text and inputs are constructed.

rg -n 'knex\.raw\(' -g '*.js' -g '*.ts' -g '*.mjs' -g '*.cjs' .

FAQ

Does knex.raw() prevent SQL injection by itself?

No. Raw SQL can safely handle untrusted values when you use bindings, such as knex.raw("... = ?", [value]). Interpolating a value into the SQL string makes it part of the SQL source. Review each raw call and its inputs.

Can I bind a column name with ?? and accept any user choice?

?? is Knex’s raw identifier placeholder; it quotes an identifier instead of binding a data value. Still map user choices to an allowlist so callers can select only the columns the application intends to expose.

References

See also SQL Injection (CWE-89): How It Works and How to Prevent It in Every Language — flawopen.com MyBatis ${} vs #{}: Prevent SQL Injection MySQLi vs PDO: Secure Prepared Statements Is NHibernate Safe from SQL Injection? MongoDB NoSQL Injection: Safe Query Filters Why String Formatting Risks Numeric ID Queries What Is a Parameterized Query?