CVE-2026-59879: Immutable Denial of Service Vulnerability
HIGHCVE-2026-59879 is a HIGH (CVSS 7.5) denial of service vulnerability (CWE-190: Integer Overflow or Wraparound) affecting Immutable libraries in the npm ecosystem. The issue involves immutable.js `list` 32-bit trie overflow → unrecoverable dos. Remediation is available by upgrading affected packages to version 4.3.9, 5.1.8, 3.8.4.
CVE-2026-59879 at a Glance
Security Impact
CVSS: 7.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).
How to Fix CVE-2026-59879
To remediate this vulnerability, upgrade all affected Immutable dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install immutable@4.3.9
npm install immutable@5.1.8
npm install immutable@3.8.4
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls immutable
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-59879, inspect your project dependencies using npm:
npm ls immutable
Analyze the terminal output to locate any version of Immutable in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.3.9), your application is affected.
Note: The npm ls immutable command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-190: Integer Overflow or Wraparound, where the product performs a calculation that can produce an integer overflow or wraparound when the logic assumes that the resulting value will always be larger than the original value. this occurs when an integer value is incremented to a value that is too large to store in the associated representation. when this occurs, the value may become a very small or negative number. In Immutable, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-59879 Affected Immutable Versions
3 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
immutable
|
npm |
≥ 4.0.0-rc.1
< 4.3.9
|
4.3.9
|
|
immutable
|
npm |
≥ 5.0.0-beta.1
< 5.1.8
|
5.1.8
|
|
immutable
|
npm |
< 3.8.4
|
3.8.4
|
References
Frequently Asked Questions
What is CVE-2026-59879?
Which Immutable versions are affected?
What version fixes CVE-2026-59879?
Is immutable affected?
What is the CVSS score of CVE-2026-59879?
What CWE is associated with CVE-2026-59879?
How can I check npm dependencies for CVE-2026-59879?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-v56q-mh7h-f735",
"modified" : "2026-09-16T18:47:10Z",
"published" : "2026-07-21T18:36:27Z",
"aliases" : [ "CVE-2026-59879" ],
"summary" : "Immutable.js `List` 32-bit trie overflow → unrecoverable DoS",
"details" : "## Summary\n\n`List#set`, `List#setSize`, `List#setIn`, `List#updateIn` (and the functional `set` / `setIn` / `updateIn`) mishandle an index or size in the range `[2 ** 30, 2 ** 31)`:\n\n- On an **empty** `List` the operation enters an **uncatchable infinite loop** (a tight CPU spin; a surrounding `try/catch` never regains control). Only killing the worker recovers it.\n- On a **populated** `List` (≥ 32 elements — i.e. any array of ≥ 32 items turned into a `List` by `fromJS`) the loop allocates without bound → heap exhaustion → the **process aborts** (`SIGABRT`, exit `134`, or kernel OOM-kill `137`). A real crash, not a recoverable error.\n\nThe index may be a **numeric string**, so it can come straight from a request body, URL, or key-path. A single small unauthenticated request is enough.\n\nThere is also a companion **silent data-corruption** issue in `setSize`:\n\n```js\nList([1, 2, 3]).setSize(2 ** 31); // before fix => size 0 (silently cleared)\nList([1, 2, 3]).setSize(2 ** 32 + 5); // before fix => size 5 (huge value wraps to 5)\n```\n\n## Impact\n\nAvailability only. A reachable configuration is any endpoint that routes untrusted input into a `List` index or a `setIn`/`updateIn` key-path — which the extremely common `state = fromJS(body); state.setIn(userPath, value)` pattern does (config stores, document/collection editors, redux-immutable reducers, JSON-Patch endpoints, etc.).\n\nNo confidentiality or integrity impact, no RCE. The companion `setSize` bug can silently corrupt application state (wrong size) without crashing.\n\n## Reproduction (immutable 5.1.7)\n\n```ts\nimport { fromJS, List } from 'immutable';\n\n// 1) Populated List: OOM -> process abort (SIGABRT, exit 134) within ~2s\nfromJS({ items: new Array(64).fill(0) }).setIn(['items', '1073741824'], 'x');\n\n// 2) Empty List: hangs forever, uncatchable\nList().set(2 ** 30, 'x');\n\n// 3) Silent truncation\nList([1, 2, 3]).setSize(2 ** 31); // => size 0\nList([1, 2, 3]).setSize(2 ** 32 + 5); // => size 5\n```\n\nA remote 43-byte HTTP request (`{\"path\":[\"items\",\"1073741824\"],\"value\":\"x\"}`) is sufficient to abort a worker that applies it via `state = state.setIn(path, value)`.\n\nAny index in `[2 ** 30, 2 ** 31)` works (`1073741824`, `2000000000`, …). An index in `[2 ** 31, 2 ** 32)` does not crash — it silently wraps (clearing the List) via the same root cause.\n\n## Root cause\n\n`List` stores its values in a 32-wide trie (`SHIFT = 5`, so each level addresses 5 more bits) and uses **signed 32-bit bitwise arithmetic** throughout `setListBounds()` (`src/List.js`):\n\n1. **Infinite loop (the hang / OOM).** The level-raising loop\n\n```js\nwhile (newTailOffset >= 1 << (newLevel + SHIFT)) {\n newRoot = new VNode(\n newRoot && newRoot.array.length ? [newRoot] : [],\n owner\n );\n newLevel += SHIFT;\n}\n```\n\nrelies on `1 << (newLevel + SHIFT)`. A JavaScript shift count is taken **mod 32**, so once `newLevel + SHIFT` reaches `31` the term goes **negative** (`1 << 31 === -2147483648`) and at `32` wraps to `1` (`1 << 35 === 8`). The comparison then stays `true` forever and the loop never terminates. On a populated `List`, each iteration retains a new `VNode` (`[newRoot]`), so the heap fills and V8 aborts; on an empty `List` it spins on CPU without allocating.\n\n2. **Silent wraparound (the `setSize` corruption).** The `begin |= 0` / `end |= 0` coercion (`ToInt32`) silently wraps large finite values (`(2 ** 31) | 0 === -2147483648`, `(2 ** 32 + 5) | 0 === 5`), producing a wrong resulting size instead of an error.\n\nThe threshold is `2 ** 30`: that is the largest size for which `1 << (newLevel + SHIFT)` stays a valid positive 32-bit integer throughout the loops (`newLevel + SHIFT` stays ≤ 30).\n\n## Remediation\n\nThe fix is contained to `setListBounds()` in `src/List.js`:\n\n1. **Validate up front, before the lossy `| 0` coercion.** Compute the intended origin and capacity in full precision and throw a clear, catchable `RangeError` when they exceed the addressable range (`MAX_LIST_SIZE = 2 ** 30`). `Infinity`/`NaN` are left to the existing `| 0 → 0` behaviour (so `setSize(Infinity)` stays `0` and `slice(0, Infinity)` still means \"to the end\").\n\n2. **Stop the shift from wrapping.** Replace `1 << exp` in the level-raising loops with a helper that uses the cheap bitwise shift while it is exact (`exp ≤ 30`, the common path including every `push`/`setSize`/`slice`) and falls back to the non-wrapping `2 ** exp` only for the rare deep trees reached when a negative origin (`unshift` / negative index) is normalized to a large positive capacity (`exp` can reach 35 there, where `1 << 35` would wrap to 8).\n\nThis turns every hang, the misleading `\"Maximum call stack size exceeded\"`, the OOM/`SIGABRT`, and the silent `setSize` truncation into one descriptive `RangeError`, preserves all behaviour for sizes `< 2 ** 30`, and keeps the hot `push` path on the fast bitwise shift (the `2 ** exp` branch is never reached by non-negative operations).\n\n### Is the new limit a breaking change?\n\nNo working code is affected. A `List` could never actually hold `≥ 2 ** 30` values before — the attempt hung, crashed, or silently corrupted the size. The limit was already implicit in the 32-bit trie; the fix only makes it explicit and catchable, mirroring native JS arrays (`new Array(2 ** 32)` → `RangeError: Invalid array length`). The single observable behaviour change is that `setSize(hugeValue)`, which used to return a silently wrong size, now throws. `2 ** 30` ≈ 1.07 billion entries (~8 GB of pointers alone), far beyond any practical use.\n\n## Mitigations (for users who cannot upgrade immediately)\n\n- Validate/clamp any externally supplied `List` index or `setIn`/`updateIn` key-path segment against a sane maximum before passing it to immutable.\n- Reject numeric path segments `≥ 2 ** 30`.\n- Run request handling in a worker that can be restarted, and cap the heap (`--max-old-space-size`) so an abort is contained.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
}, {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "immutable"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "4.0.0-rc.1"
}, {
"fixed" : "4.3.9"
} ]
} ]
}, {
"package" : {
"ecosystem" : "npm",
"name" : "immutable"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "5.0.0-beta.1"
}, {
"fixed" : "5.1.8"
} ]
} ]
}, {
"package" : {
"ecosystem" : "npm",
"name" : "immutable"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.8.4"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/immutable-js/immutable-js/security/advisories/GHSA-v56q-mh7h-f735"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-59879"
}, {
"type" : "WEB",
"url" : "https://github.com/immutable-js/immutable-js/commit/a1a1ee412dcaa380ab325196283d06594ffe4b84"
}, {
"type" : "WEB",
"url" : "https://github.com/immutable-js/immutable-js/commit/f0bc997d8eb9886aff2236635aa210a95a04304a"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/immutable-js/immutable-js"
}, {
"type" : "WEB",
"url" : "https://github.com/immutable-js/immutable-js/releases/tag/v3.8.4"
}, {
"type" : "WEB",
"url" : "https://github.com/immutable-js/immutable-js/releases/tag/v4.3.9"
}, {
"type" : "WEB",
"url" : "https://github.com/immutable-js/immutable-js/releases/tag/v5.1.8"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-1284", "CWE-190", "CWE-400", "CWE-835" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-21T18:36:27Z",
"nvd_published_at" : "2026-07-08T17:17:26Z"
}
}