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
Published August 26, 2026Reviewed 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

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

high

How SQL injection via template literals happens in Node.js SQLite and how to fix it

A SQL injection vulnerability in `src/lib/codex-state.mjs` allowed dynamic column names to reach SQL queries through JavaScript template literals. The fix implements defense-in-depth with strict identifier validation using `SAFE_IDENTIFIER` regex before query construction.

high

How Python SQLAlchemy Raw Query SQL Injection happens and how to fix it

A high-severity SQL injection vulnerability was fixed in the `skills/last30days/scripts/store.py` file where untrusted input was being concatenated directly into raw SQL queries. The fix replaces string concatenation with SQLAlchemy's TextualSQL prepared statements using named parameters, preventing attackers from manipulating database queries through malicious input.

critical

How SQL injection happens in Python DuckDB view creation and how to fix it

A critical SQL injection flaw in `python/src/idx/api.py:265` built five DuckDB `CREATE VIEW` statements with Python f-strings, interpolating a filesystem path directly into SQL text. The fix replaces the interpolated path with a bound parameter (`read_parquet(?)`) and moves the view names into a hardcoded, non-interpolated statement map — eliminating any path where filenames or directory values can alter SQL structure.

critical

How SQL Injection happens in PHP bulk email systems and how to fix it

A critical SQL injection vulnerability in `admin/utilities/bulkEmailSystem.php` allowed attackers to inject arbitrary SQL through unvalidated database names passed from user input. The fix implements strict input validation using regex pattern matching to ensure only safe database identifiers are processed, preventing exploitation of the bulk email functionality.