Back to Blog
critical SEVERITY8 min read

How Unsafe Fall-Through in getWhereConditions Happens in Sequelize and How to Fix It

A critical vulnerability in Sequelize (CVE-2023-22579) allowed attackers to inject raw SQL through an unsafe fall-through in the `getWhereConditions` function when parentheses were used in query attributes. Upgrading from version 6.26.0 to 6.29.0 closes this attack vector by tightening how raw attributes are handled. Any Node.js application using Sequelize for database queries should treat this upgrade as an urgent security priority.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 26, 2026•Reviewed August 26, 2026

Answer Summary

CVE-2023-22579 is a critical SQL injection vulnerability in Sequelize (Node.js ORM), classified under CWE-89 (Improper Neutralization of Special Elements in SQL Commands). The flaw stems from an unsafe fall-through in Sequelize's `getWhereConditions` function that inadvertently enabled "raw attributes" — allowing user-controlled input containing parentheses to bypass query sanitization and reach the database as raw SQL. The fix is to upgrade Sequelize from version 6.26.0 to 6.29.0, which also updates the `retry-as-promised` dependency from 6.1.0 to 7.1.1 as part of the hardened release.

Vulnerability at a Glance

cweCWE-89
fixUpgrade Sequelize from 6.26.0 to 6.29.0, which removes default raw attribute support in parenthesized expressions
riskAttackers can inject arbitrary SQL into WHERE clauses, potentially reading, modifying, or deleting database data
languageJavaScript / Node.js
root causeSequelize's getWhereConditions function had an unsafe fall-through that treated parenthesized expressions as raw SQL attributes by default
vulnerabilitySQL Injection via unsafe fall-through in getWhereConditions

How Unsafe Fall-Through in getWhereConditions Happens in Sequelize and How to Fix It


Introduction

The server/package-lock.json file in this application pins the version of Sequelize — one of the most widely used Node.js ORMs — and that single version number turned out to be the difference between a safe database layer and one vulnerable to SQL injection. Trivy's dependency scanner flagged sequelize@6.26.0 for CVE-2023-22579, a critical-severity vulnerability rooted in an unsafe fall-through inside Sequelize's internal getWhereConditions function.

What makes this vulnerability particularly insidious is that it doesn't require a developer to write obviously unsafe code. The flaw lives inside Sequelize itself: when building WHERE clauses, the library's query-construction logic would fall through to treating certain expressions — specifically those containing parentheses — as raw SQL attributes by default. That means user-controlled data that happened to include parentheses could slip past Sequelize's normal sanitization and land directly in a SQL query.

For developers building applications on top of Sequelize, this is a sobering reminder that ORM frameworks are not a silver bullet against SQL injection. The ORM itself can be the vulnerable component.


The Vulnerability Explained

What Is "Default Raw Attribute" Support?

Sequelize's query builder constructs SQL WHERE clauses by inspecting the attributes passed to methods like findAll, findOne, update, and destroy. Internally, getWhereConditions processes these attributes and decides how to serialize them into SQL.

The vulnerability — tracked as both CVE-2023-22579 and the related CVE-2023-22578 — stems from the fact that when getWhereConditions encountered an attribute value wrapped in parentheses, it would fall through to a code path that treated the value as a raw SQL fragment, without escaping or parameterizing it.

In simplified terms, the vulnerable behavior looked like this:

// Vulnerable Sequelize 6.26.0 behavior
// If a user-controlled value arrives as: "(1=1 OR 1=1)"
// getWhereConditions falls through and emits it verbatim into the SQL:

Model.findAll({
  where: {
    username: userInput  // if userInput = "(admin' OR '1'='1')"
  }
});

// Generated SQL (vulnerable):
// SELECT * FROM users WHERE username = (admin' OR '1'='1')
// The parenthesized expression bypasses normal quoting

The "fall-through" means there was a conditional check in getWhereConditions that, when it failed to match a known safe pattern, did not reject the input — it continued down to a code path that emitted the value as raw SQL. This is a classic unsafe default: rather than failing closed (rejecting unrecognized input), the function failed open (passing it through).

How Could This Be Exploited?

Consider an application endpoint that accepts a filter parameter from an HTTP request and passes it into a Sequelize query:

// Example vulnerable application code
app.get('/api/users', async (req, res) => {
  const users = await User.findAll({
    where: {
      role: req.query.role  // User-controlled input
    }
  });
  res.json(users);
});

With Sequelize 6.26.0, an attacker could craft a request like:

GET /api/users?role=(admin' UNION SELECT password,username,null FROM users--)

Because the parenthesized expression triggers the unsafe fall-through in getWhereConditions, this value could be emitted into the SQL query as a raw fragment, potentially allowing:

  • Data exfiltration via UNION SELECT attacks
  • Authentication bypass via tautology injections (OR 1=1)
  • Data destruction if the application has write permissions

The real-world impact depends on the database permissions granted to the application's database user, but in the worst case, an attacker with access to this endpoint could read every row in every table the application has access to.

The Vulnerable Dependency Version

The lock file pinned the vulnerable version explicitly:

// server/package-lock.json (BEFORE)
"node_modules/sequelize": {
  "version": "6.26.0",
  "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.26.0.tgz",
  "integrity": "sha512-Xv82z1FdSn/qwB1IObSxIHV519cFk/vSD28vWs8Y0VucQLn7pK2x2jYjf2Qg/rBUQbCVprDdU7RPf+55rrkc0A=="
}

And the package.json range ^6.26.0 would not automatically resolve to a patched version without an explicit update, since the lock file overrides the semver range.


The Fix

The fix is a targeted dependency upgrade: Sequelize is bumped from 6.26.0 to 6.29.0. This version contains Sequelize's own internal patch to getWhereConditions that removes the unsafe default fall-through for raw attribute handling when parentheses are present.

Before and After: package-lock.json

// server/package-lock.json
"node_modules/sequelize": {
-  "version": "6.26.0",
-  "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.26.0.tgz",
-  "integrity": "sha512-Xv82z1FdSn/qwB1IObSxIHV519cFk/..."
+  "version": "6.29.0",
+  "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.29.0.tgz",
+  "integrity": "sha512-m8Wi90rs3NZP9coXE52c7PL4Q078nwYZXqt1IxPvgki7nOFn0p/F0eKsYDBXCPw9G8/BCEa6zZNk0DQUAT4ypA==",
+  "license": "MIT"
}

Before and After: package.json

// server/package.json
-"sequelize": "^6.26.0",
+"sequelize": "^6.29.0",

Both files must be updated together. The package.json change updates the declared minimum version so future npm install runs won't resolve back to a vulnerable version. The package-lock.json change ensures the exact resolved version is pinned to the patched release immediately.

The retry-as-promised Transitive Dependency

The fix also updates a transitive dependency, retry-as-promised, from 6.1.0 to 7.1.1:

"node_modules/retry-as-promised": {
-  "version": "6.1.0",
-  "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-6.1.0.tgz",
-  "integrity": "sha512-Hj/jY+wFC+SB9SDlIIFWiGOHnNG0swYbGYsOj2BJ8u2HKUaobNKab0OIC0zOLYzDy0mb7A4xA5BMo4LMz5YtEA=="
+  "version": "7.1.1",
+  "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz",
+  "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==",
+  "license": "MIT"
}

Sequelize 6.29.0 requires the updated retry-as-promised 7.x as part of its own dependency graph. This is a normal transitive update that comes along with the Sequelize upgrade and does not introduce any breaking changes for application code.

Why This Fix Works

In Sequelize 6.29.0, the internal getWhereConditions function was patched to not default to raw attribute mode when it encounters parenthesized expressions. Instead, the function now requires an explicit opt-in to raw SQL — developers must use Sequelize.literal() to pass raw SQL fragments, making the intent explicit and auditable. Implicit raw attribute support through parentheses is disabled, closing the fall-through path entirely.


Key Takeaways

  • The getWhereConditions fall-through in Sequelize 6.26.0 meant that parenthesized user input could bypass ORM-level SQL sanitization entirely — this was not a developer mistake but a flaw in the library itself.
  • Pinning sequelize to ^6.26.0 in package.json was not sufficient protection — the lock file held the exact vulnerable version and would not auto-update without an explicit intervention.
  • Upgrading both package.json and package-lock.json is required — updating only one file leaves the other out of sync and may not actually change the installed version.
  • The transitive dependency retry-as-promised also needed updating — Sequelize 6.29.0 requires retry-as-promised@7.x, illustrating that security upgrades sometimes carry necessary transitive dependency changes.
  • ORM frameworks are not immune to SQL injection vulnerabilities — always treat ORM library updates with the same urgency as application code security patches.

How Orbis AppSec Detected This

  • Source: The sequelize package version 6.26.0 declared in server/package-lock.json, which is consumed by any application code passing user-influenced data to Sequelize query methods (e.g., findAll, findOne, update).
  • Sink: Sequelize's internal getWhereConditions function, which constructs SQL WHERE clauses from query attributes — the unsafe fall-through in this function allows parenthesized expressions to reach the SQL engine as raw fragments.
  • Missing control: Sequelize 6.26.0 lacked an explicit gate in getWhereConditions to reject or require opt-in for raw attribute expressions containing parentheses, defaulting to unsafe pass-through behavior instead of failing closed.
  • CWE: CWE-89 — Improper Neutralization of Special Elements used in an SQL Command (SQL Injection)
  • Fix: Upgraded sequelize from 6.26.0 to 6.29.0 in both server/package.json and server/package-lock.json, removing the unsafe default raw attribute fall-through at the framework level.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CVE-2023-22579 is a stark reminder that SQL injection threats don't always originate from application code — they can live inside the very frameworks developers trust to protect them. The unsafe fall-through in Sequelize's getWhereConditions function turned a routine ORM query into a potential SQL injection vector, simply because the library defaulted to treating parenthesized expressions as raw SQL.

The fix is straightforward — upgrade Sequelize from 6.26.0 to 6.29.0 — but the lesson is broader: dependency security is application security. Regularly audit your package-lock.json, integrate vulnerability scanning into your CI/CD pipeline, and treat critical ORM upgrades with the same urgency as patching your own code.

When your ORM is the vulnerability, no amount of careful query writing will save you. Stay current, scan continuously, and let automated tools catch what manual review misses.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2784

Related Articles

critical

Go ArticleRemove() SQL Injection via fmt.Sprintf IN Clause

The Go backend's `ArticleRemove` function built a SQL `DELETE` statement by joining a caller-supplied slice of article IDs directly into the query string with `fmt.Sprintf`. Because the `aids` values came straight from the frontend with no validation or escaping, an attacker could inject arbitrary SQL into the `IN(...)` clause. The fix replaces string concatenation with a parameterized query using placeholders and a `DB.Exec` argument list.

critical

JdbcSinkFunction.invoke() SQL Injection via Unvalidated Identifiers

The `JdbcSinkFunction.invoke()` method in Lacus's RTC engine built SQL INSERT statements with `String.format()`, placing database name, table name, and column names directly into the query string without validation. Although the actual row values used parameterized `?` placeholders, the identifiers themselves were injectable, letting a crafted sink configuration break out of the intended INSERT statement. The fix adds a strict allowlist regex that rejects any identifier containing characters out

critical

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.

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.