Back to Blog
critical SEVERITY2 min read

Node.js Auth Query SQL Injection via Group Code Interpolation

A sign-in authorization check built its SQL query by mapping an `authorizedGroups` array into quoted string literals and joining them directly into a template literal, creating a classic SQL injection point in a critical authentication path. The fix replaces every interpolated value — including the previously "typed" parameter — with `?` placeholders bound through a value-builder helper, closing off the injection vector entirely.

O
By Orbis AppSec
•Published October 1, 2026•Reviewed October 1, 2026

Answer Summary

This is a first-party Node.js authentication module (no package/version applies). An attacker who can influence the group codes evaluated during sign-in could inject SQL into the group-membership `IN` clause and potentially bypass the authorization check that gates access by group. The fix replaces the string-interpolated group list and the id parameter with `?` placeholders bound via a value-builder (`_val.init().add().addAll()`); no package version is associated with this first-party fix. CWE was not formally assigned for this finding.

Vulnerability at a Glance

cweunknown
fixReplaced interpolated values with `?` placeholders bound through a value-builder helper
riskAuthorization bypass in the group-membership check during sign-in
languageJavaScript (Node.js)
root causeGroup codes mapped into quoted string literals and joined directly into a SQL template literal
vulnerabilitySQL Injection

Introduction

The authorization check that runs during sign-in validates whether a user's account belongs to one of a set of permitted groups. To build that check, the code took an authorizedGroups array and turned it into a SQL fragment with:

authorizedGroups.map((group) => `'${group}'`).join(", ")

That fragment was then spliced straight into a template-literal SQL string passed to _db.queryFirst(). Any group code that reaches this array without being escaped becomes part of the SQL text itself — not a bound parameter. This matters because the query in question isn't a reporting endpoint or an admin dashboard filter; it's the gate that decides whether a signed-in user is allowed to proceed, which makes it one of the highest-value targets in the whole authentication flow for an injection bug.

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see fix diff below
Ecosystem npm (Node.js)
CVE / GHSA not assigned
CWE unknown

This is first-party application code with no package version to track. The fix is scoped to the single authorization query inside the sign-in handler.

The Vulnerability Explained

Here's the relevant part of the original query construction:

WHERE 1 = 1
    AND people_id = ${dbPeople.getInt("id")}
    AND user_group_id IN (
        SELECT id FROM user_group WHERE code IN (
            ${authorizedGroups.map((group) => `'${group}'`).join(", ")}
        )
    )
    AND active = true

Two problems stack on top of each other here:

  1. dbPeople.getInt("id") is interpolated directly into the template literal rather than bound as a parameter.
  2. Each entry in authorizedGroups is wrapped in single quotes and joined with commas by hand — a manual, unescaped string-building step feeding straight into the IN (...) clause.

If any value that ends up in authorizedGroups can be influenced — for example through a group-management flow, a claim derived from an upstream token, or any code path that assembles this array from external input — a crafted group code containing a single quote can break out of the intended string literal. From there, an attacker can comment out the rest of the clause, inject an OR 1=1-style condition, or otherwise manipulate the truth value of the WHERE clause. Because this specific query is the authorization decision for sign-in, a successful injection doesn't just leak data — it can make isAuthorized resolve truthy for a user who shouldn't pass, defeating the group-based access control outright.

The Fix

The fix removes every hand-built string from the query and replaces it with positional ? placeholders, binding the actual values through a value-builder:

WHERE 1 = 1
    AND people_id = ?
    AND user_group_id IN (
        SELECT id FROM user_group WHERE code IN (?, ?)
    )
    AND active = true
`, _val.init()
  .add(dbPeople.getInt("id"))
  .addAll(authorizedGroups));

Two changes work together

Prevention and further reading

Frequently Asked Questions

Does the fix change how many parameters are bound to the group-authorization query?

Yes — the query now binds `dbPeople.getInt("id")` plus every value in `authorizedGroups` as positional `?` placeholders via `_val.init().add().addAll()`, instead of embedding them as literal SQL text.

Why was the `people_user_id` lookup changed from `_db.param("int")` to a plain `?` placeholder?

The fix standardizes both queries on the same positional-placeholder-plus-bound-value pattern, so the people lookup and the group-authorization check are no longer built with two different (and inconsistent) parameter-handling approaches.

Does the included regression test actually exercise the injected SQL payload?

It sends a request with a `sql_injection_in_group`-style payload in the Authorization header and asserts the endpoint returns 401/403, verifying the authentication gate rejects it rather than directly asserting on database behavior.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #10

Related Articles

critical

Toolforge Database Creation SQL Injection via Unicode Backtick Bypass

A critical SQL injection vulnerability in the database creation routine of a Node.js wiki library allowed attackers to inject arbitrary SQL by exploiting a weak validation check. The fix replaces a character-based block with a strict allowlist pattern, ensuring only valid database identifiers reach the SQL engine.

critical

heatmap.php SQL Injection: $_REQUEST Parameters in Unparameterized

A critical SQL injection vulnerability in the heatmap data retrieval endpoint allowed attackers to execute arbitrary database commands by manipulating coordinate bounds or time range parameters. The vulnerability affected all six user-controlled $_REQUEST parameters passed directly into query construction without parameterization.

critical

Actual Budget addTransaction.sh SQL Injection via Shell Variable

A critical SQL injection vulnerability in Actual Budget's transaction automation script allowed attackers to manipulate database records through shell variables interpolated directly into SQL strings. The fix introduces proper escaping functions and numeric validation to prevent injection through unquoted fields.

critical

SQL's Insert() and Update() Methods Used F-String Interpolation in u2share_batch_give_sugar

The SQL helper class in u2share_batch_give_sugar used Python f-strings to construct INSERT and UPDATE queries, creating SQL injection vulnerabilities even though values appeared to come from internal constants. The fix replaces all f-string query construction with sqlite3 parameterized queries using `?` placeholders, eliminating string interpolation entirely from the database path.

high

TrackOptionsManager DDL: Template-Literal SQL Injection Closed

The `TrackOptionsManager` service built its `CREATE TABLE` and `ALTER TABLE ... ADD COLUMN alias` statements by interpolating a `DEFAULT_ALIAS` constant directly inside single quotes in a JavaScript template literal, and its private `_query()` helper had no parameter channel at all. The fix routes the default value through `mysql.escape()` and gives `_query(q, params = [])` a real bound-parameter argument that is forwarded to `db.query()`. This removes an injection primitive on a schema-bootstra

critical

i18next-fs-backend 2.6.4 Prototype Pollution via Crafted Missing-Key

A critical prototype pollution vulnerability in i18next-fs-backend 2.6.4 allows attackers to modify Object.prototype through maliciously crafted translation key strings. The fix upgrades the package from 2.6.4 to 2.6.6, eliminating the unsafe key handling that permitted this attack vector.