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.


Prevention & Best Practices

1. Keep ORM Dependencies Updated

ORMs like Sequelize are large, complex libraries with their own SQL-generation logic. Vulnerabilities in the ORM layer can affect every query in your application, regardless of how carefully individual queries are written. Subscribe to security advisories for your ORM and treat critical upgrades as urgent.

2. Use Explicit Raw SQL Sparingly

When you do need raw SQL in Sequelize, always use Sequelize.literal() explicitly, and never pass user-controlled data into it without parameterization:

// ❌ Dangerous — never do this
Model.findAll({
  where: Sequelize.literal(`status = '${userInput}'`)
});

// ✅ Safe — use parameterized replacements
Model.findAll({
  where: Sequelize.literal('status = :status'),
  replacements: { status: userInput }
});

3. Use Sequelize Operators for Complex Conditions

Instead of constructing complex WHERE conditions with raw strings, use Sequelize's built-in Op operators:

const { Op } = require('sequelize');

// ✅ Safe — Sequelize handles escaping
Model.findAll({
  where: {
    age: { [Op.gt]: userInput },
    name: { [Op.like]: `%${sanitizedInput}%` }
  }
});

4. Scan Dependencies in CI/CD

The vulnerability was caught by Trivy scanning package-lock.json. Integrate dependency scanning into your CI/CD pipeline:

# Example: Trivy scan in CI
trivy fs --security-checks vuln ./server/package-lock.json

Tools to consider:
- Trivy — container and filesystem vulnerability scanner
- npm audit — built-in Node.js dependency auditing
- Snyk — developer-first security scanning
- OWASP Dependency-Check — open-source dependency auditing

5. Apply the Principle of Least Privilege to Database Users

Even if SQL injection occurs, limiting the database user's permissions reduces the blast radius. The application's database user should only have SELECT, INSERT, UPDATE, and DELETE on the specific tables it needs — never DROP, CREATE, or cross-database access.

Security Standards Reference

  • OWASP Top 10: A03:2021 – Injection
  • CWE-89: Improper Neutralization of Special Elements used in an SQL Command
  • OWASP SQL Injection Prevention Cheat Sheet: Use parameterized queries and prepared statements as the primary defense

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.


References

Frequently Asked Questions

What is the unsafe fall-through vulnerability in Sequelize's getWhereConditions?

It's a flaw where Sequelize's query-building logic would fall through to treating user-supplied parenthesized expressions as raw SQL, bypassing normal sanitization and enabling SQL injection.

How do you prevent raw attribute injection in Sequelize (Node.js)?

Upgrade to Sequelize 6.29.0 or later, avoid passing user-controlled input directly to query attributes, and always use parameterized queries or Sequelize's built-in operators.

What CWE is the Sequelize getWhereConditions vulnerability?

CWE-89: Improper Neutralization of Special Elements used in an SQL Command (SQL Injection).

Is input validation alone enough to prevent this Sequelize vulnerability?

No. While input validation helps, the root cause was inside Sequelize's own query-building logic. The definitive fix is upgrading to 6.29.0 where the unsafe fall-through is removed at the framework level.

Can static analysis detect this Sequelize raw attribute injection?

Yes. Trivy's dependency scanner flagged this vulnerability (CVE-2023-22579) in the package-lock.json, and tools like Semgrep can detect unsafe Sequelize query patterns at the code level.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2784

Related Articles

high

How SQL Injection happens in Python BigQuery connectors and how to fix it

A high-severity SQL injection vulnerability was discovered in a BigQuery connector's query-building logic, where Python f-strings interpolated user-controlled identifiers—project_id, dataset_id, table_id, and timestamp_column—directly into SQL without validation. An attacker with control over connector configuration could inject arbitrary BigQuery SQL, including destructive statements. The fix introduces strict allowlist-based identifier validation using compiled regular expressions before any S

critical

How SQL Injection happens in PHP PDO queries and how to fix it

A critical SQL injection vulnerability was discovered in the `getOfficialContests()` method of ContestRepository.php, where the `$site_id` parameter was directly interpolated into a SQL query string instead of using prepared statements. This vulnerability allowed attackers to inject arbitrary SQL commands and potentially access or manipulate the entire contest database. The fix replaced `pdo->query()` with `pdo->prepare()` and proper parameter binding.

critical

How SQL Injection Happens in CSV-to-SQL Converters and How to Fix It

A critical SQL injection vulnerability was discovered in the `csv2sql()` function in `src/data/converter/csv.js`, where CSV data and table names were directly interpolated into SQL INSERT statements without sanitization. The fix implements input validation through identifier sanitization and proper value escaping, eliminating the attack surface while preserving legitimate functionality.

critical

How SQL injection happens in Node.js string interpolation and how to fix it

A critical SQL injection vulnerability was discovered in the `getScript()` method of `src/core/statistics.js`, where the `metadata_id` variable was directly interpolated into DELETE and UPDATE SQL statements without any validation. An attacker controlling this parameter could inject malicious SQL payloads to delete entire tables or exfiltrate sensitive data. The fix implements strict input validation using `parseInt()` and regex patterns to ensure only safe values reach the database queries.

critical

How SQL Injection happens in Node.js MySQL queries and how to fix it

A critical SQL injection vulnerability was discovered in `divisible_asset.js` where `message_index` and `output_index` values from external payment data were directly interpolated into SQL queries without proper escaping. This fix applies `conn.escape()` to these parameters, preventing attackers from manipulating database queries through crafted payment elements.

critical

How Heap Buffer Overflows Happen in C++ ZIP Extraction and How to Fix Them

A critical heap buffer overflow vulnerability was discovered in `TKLiveSync/unzip.cpp`, where ZIP archive entry names were copied into a `PATH_MAX`-sized heap buffer using `strcpy()` without any length validation. Since the ZIP specification allows entry names up to 65,535 bytes — far exceeding typical `PATH_MAX` values of 1,024 to 4,096 bytes — a crafted archive could overflow the buffer and corrupt heap memory. The fix replaces the unsafe `strcpy`/`dirname` pattern with `std::string` operation