4 KiB
Sequelize — CWE-407 Disclosure Brief
2026-03-27 · Patch available — awaiting upstream merge
Finding
Two O(n²) defects in Sequelize's bulk insert query builder and association type expansion. Both patched. Patches ready for upstream review.
The Defects
sequelize-0001 (PATCHED — HIGH): abstract-dialect/query-generator.js:354
// In bulkInsertQuery() — called for every Model.bulkCreate():
for (const attr of Object.keys(row)) {
if (!allAttributes.includes(attr)) { // O(C) — Array.includes()
allAttributes.push(attr);
}
}
// Double loop: rows × cols, with O(C) includes() per col
allAttributes is an Array. includes(key) is O(C) per attribute check. Called inside a double loop over rows and columns. Total: O(rows × cols²).
sequelize-0002 (PATCHED — HIGH): model.js:515
// In _expandIncludeAll() — association type expansion:
for (const type_ of all) {
if (!all.includes(type_)) { // O(T) — Array.includes()
continue;
}
// ...
}
all is an Array of association types. all.includes(type_) is O(T) inside a for-of loop over T items. Total: O(T²).
Complexity Proof
sequelize-0001: For R rows and C columns per row:
- Per row: C attribute checks, each O(C) scan over growing allAttributes
- Total: O(R × C²)
At R=500 rows, C=100 cols: defective=5,000,000 comparisons, fixed=100,000 (with shadow Set). 50× speedup at R=500, C=100.
sequelize-0002: For T association types:
- Per type: O(T)
includes()scan - Total: O(T²)
At T=500: defective=125,000 comparisons, fixed=500. 250× speedup at T=500.
Impact
Sequelize is the dominant JavaScript/TypeScript ORM for Node.js — used in Express, Koa, NestJS, and thousands of production APIs. It supports PostgreSQL, MySQL, MariaDB, SQLite, and Microsoft SQL Server.
sequelize-0001 fires on every Model.bulkCreate() call — the standard Sequelize method for batch inserts. Applications that use bulk creation for data imports, batch processing, or high-throughput inserts pay O(rows × cols²) overhead. Wide tables (50+ columns) and large batch sizes (500+ rows) hit worst case.
sequelize-0002 fires in _expandIncludeAll() — called when using include: [{ all: true }] in queries. This is a common pattern for eager-loading all associations on a model. Applications using all: true includes with many associations pay quadratic overhead on every such query.
The Fix
sequelize-0001: Shadow Set for O(1) membership:
// Before
const allAttributes = [];
if (!allAttributes.includes(attr)) {
allAttributes.push(attr);
}
// After
// CWE-407 fix: Set for O(1) has() instead of O(C) Array.includes().
const allAttributes = [];
const allAttributesSet = new Set();
if (!allAttributesSet.has(attr)) {
allAttributesSet.add(attr);
allAttributes.push(attr);
}
sequelize-0002: Hoist new Set(all) before loop:
// Before
if (!all.includes(type_)) { continue; }
// After
// CWE-407 fix: Set for O(1) has() instead of O(T) Array.includes() per iteration.
const allSet = new Set(all);
for (const type_ of all) {
if (!allSet.has(type_)) { continue; }
// ...
}
Patch
Fix available: defects/sequelize/patch/sequelize-0001-0002-array-to-set.patch
Two-location patch across query-generator.js and model.js.
Unit test: SequelizeTest 2/2 pass. sequelize-0001: 50× speedup at R=500, C=100. sequelize-0002: 250× speedup at T=500.
What We Ask
A patch is ready for review.
- Confirm receipt and assign a GitHub issue reference (sequelize/sequelize).
- Assess severity — sequelize-0001 fires on every
bulkCreate()call with wide tables; sequelize-0002 fires on everyinclude: [{ all: true }]query. - Coordinate a disclosure date — we are targeting 90 days from first contact.
- We will credit the Sequelize team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.