Jul 21, 2026·8 min read·28 visits
A design flaw in Immutable.js allows integer overflows during list resizing, enabling attackers to cause CPU exhaustion, out-of-memory crashes, or data corruption.
CVE-2026-59879 describes a critical integer overflow vulnerability in the Immutable.js library when handling indices near 32-bit boundaries. An attacker can leverage this flaw to cause a denial of service via CPU thread lockup or process crashes, as well as data corruption through silent size truncation.
Immutable.js is a widely used library for managing persistent, immutable data structures in JavaScript. Its List data structure utilizes a 32-way bitmapped vector trie to achieve efficient lookups and updates. The vulnerability, tracked as CVE-2026-59879, resides in the trie's internal resizing logic, which handles dynamic index and size boundaries.
An attacker can trigger this vulnerability by supplying index or size arguments in the range [2^30, 2^31) or larger to functions that modify the List bounds. This input causes an integer overflow during trie expansion, resulting in a thread-locking infinite loop or an Out-of-Memory (OOM) crash. The issue is classified under CWE-190 (Integer Overflow) and CWE-400 (Uncontrolled Resource Consumption).
The attack surface is exposed in any application that permits user-controlled indices or sizes to propagate into Immutable.js List mutation interfaces, such as set(), setSize(), setIn(), or updateIn(). Because JavaScript runtimes are typically single-threaded, a thread lockup completely disables processing of all concurrent requests on the affected event loop instance, leading to a severe denial of service.
The underlying issue stems from a combination of JavaScript bitwise operations and how Immutable.js dynamically scales its bitmapped vector trie. The library splits list indices into 5-bit chunks (SHIFT = 5) to navigate levels of the tree structure. When the engine processes a capacity change, it must raise the height of the trie if the new tail offset exceeds the maximum capacity representable at the current level.
In the vulnerable codebase, the loop responsible for raising trie levels was controlled by the condition newTailOffset >= 1 << (newLevel + SHIFT). Although standard JavaScript numbers are 64-bit floating-point values, JavaScript implements bitwise operations by coercing the operands to signed 32-bit integers. As newLevel increments in steps of 5, the term newLevel + SHIFT eventually reaches 31 and 32.
When newLevel + SHIFT equals 31, the bitwise expression 1 << 31 yields -2147483648. If newTailOffset is a positive number (such as 1073741792 from a requested size within [2^30, 2^31)), the comparison newTailOffset >= -2147483648 evaluates to true. In the next iteration, when newLevel + SHIFT equals 32, the shift count wraps around modulo 32, resulting in 1 << 0, which is 1. This causes the condition newTailOffset >= 1 to remain true indefinitely, establishing an infinite loop.
The runtime outcome of this loop depends on the initial state of the List object. For empty lists, the loop performs lightweight array allocations, leading to immediate CPU execution exhaustion. For populated lists, the loop continuously allocates wrapping VNode instances, consuming heap memory until the process terminates with an Out-of-Memory error.
Prior to the remediation in versions 4.3.9 and 5.1.8, the library did not validate input bounds before applying bitwise coercion. The list normalization step relied on lossy signed 32-bit coercion, while the level-raising mechanism allowed bit shifts to overflow into negative numbers and wrap around.
// Vulnerable Code Path in src/List.js
if (end !== undefined) {
end |= 0; // Lossy 32-bit integer coercion
}
// Level-raising logic
while (newTailOffset >= 1 << (newLevel + SHIFT)) {
newRoot = new VNode(
newRoot && newRoot.array.length ? [newRoot] : [],
owner
);
newLevel += SHIFT;
}The official fix implements pre-validation of the list limits using Number.isFinite and introduces MAX_LIST_SIZE capped at 2^30. This prevents any value exceeding safe limits from ever reaching the coercion or loop logic. Additionally, the bitwise shift operator in the comparison is replaced with a custom helper function, levelCapacity(exp), which avoids the overflow by switching to exponential representation when the exponent meets or exceeds 31.
// Patched Code Path in src/List.js
const MAX_LIST_SIZE = 2 ** 30;
function validateListBoundsRequest(list, begin, end) {
const requestedOrigin = list._origin + (begin === undefined ? 0 : begin);
const requestedCapacity = end === undefined
? list._capacity
: end < 0
? list._capacity + end
: list._origin + end;
if (
(Number.isFinite(requestedCapacity) && requestedCapacity > MAX_LIST_SIZE) ||
(Number.isFinite(requestedOrigin) && requestedOrigin < -MAX_LIST_SIZE)
) {
throw new RangeError('Invalid List size');
}
}
// Safe exponentiation helper replacing bitwise shifts for large values
function levelCapacity(exp) {
return exp < 31 ? 1 << exp : 2 ** exp;
}
// Patched level-raising logic
while (newTailOffset >= levelCapacity(newLevel + SHIFT)) {
newRoot = new VNode(
newRoot && newRoot.array.length ? [newRoot] : [],
owner
);
newLevel += SHIFT;
}An attacker can trigger the vulnerability remotely if the target application exposes endpoints that process user-controlled inputs as indices or array sizes in Immutable.js structures. No authentication is inherently required to exploit the core vulnerability itself; access depends entirely on the application's configuration. The payload consists of an integer payload designed to populate a set() or setSize() call.
To demonstrate a CPU Denial of Service, the attacker inputs a value within [2^30, 2^31), such as 1073741824. On an empty list, this triggers the infinite loop. Since the loop allocates negligible memory in this state, it runs indefinitely without crashing, keeping the CPU core bound at 100% utilization. This state blocks the execution of any other tasks on the Node.js main thread.
// Proof of Concept: CPU Thread Lockup (Empty List)
const { List } = require('immutable');
// Setting a value at 2^30 on an empty list causes a loop that never terminates
List().set(1073741824, "exploit_payload");To demonstrate a Process Out-of-Memory (OOM) crash, the target structure must contain at least one element. Operating on a populated list changes the execution path because the root node is initialized. During the infinite loop, the runtime continuously creates new VNode wraps, exhausting the process heap space within fractions of a second and forcing the environment to abort.
// Proof of Concept: Process OOM Crash (Populated List)
const { List } = require('immutable');
const activeList = List([1, 2, 3]);
// This triggers rapid heap exhaustion and a SIGABRT crash
activeList.set(1073741824, "exploit_payload");A deep-dive analysis of the remediation patch reveals secondary logical vulnerabilities. The validation function validateListBoundsRequest performs checks on raw input values, but the actual sizing parameters undergo a second evaluation and bitwise coercion within setListBounds (begin |= 0, end |= 0). This design creates a double-evaluation hazard analogous to a Time-of-Check to Time-of-Use (TOCTOU) bug.
An attacker can exploit this double-evaluation by passing a stateful JavaScript object equipped with a custom valueOf() hook. The first call to valueOf() occurs during validation and can return a safe integer to pass the checks. The second call occurs inside setListBounds during coercion, where the hook returns an out-of-bounds value. This bypasses the boundary checks completely, causing silent size truncation and state corruption.
// TOCTOU Double-Evaluation Bypass PoC
const { List } = require('immutable');
let evaluations = 0;
const statefulBound = {
valueOf: () => {
evaluations++;
return evaluations === 1 ? 3 : 2 ** 32 + 5;
}
};
const list = List([1, 2, 3]);
const mutatedList = list.setSize(statefulBound);
// Size is silently coerced and truncated, leading to data corruption
console.log(mutatedList.size); A second bypass involves negative value inputs. When validating a negative end parameter, the code computes the requested capacity as list._capacity + end. If an attacker passes a highly negative integer outside the 32-bit signed range (e.g., -2^31 - 5), the validation step accepts it because the resulting negative capacity is mathematically less than MAX_LIST_SIZE. However, during the subsequent coercion (end |= 0), the negative integer wraps to a large positive value, setting a massive capacity without triggering the validation error. This leads to internal index corruption.
To remediate CVE-2026-59879, developers should upgrade Immutable.js dependencies immediately. The vulnerability is resolved in versions 4.3.9 (for v4.x implementations) and 5.1.8 (for v5.x implementations). Software composition analysis tools should be configured to flag and block older versions.
However, due to the identified double-evaluation and negative overflow bypasses in the official validation logic, upgrading the package may not fully prevent data corruption or state inconsistency if the application processes complex object types or unchecked numeric values. Developers must sanitize and validate all inputs before passing them to Immutable.js APIs.
Implementing a defensive validation wrapper is highly recommended. The wrapper must ensure that the input is a primitive, non-negative integer below the safe limit of 2^30. Converting the input to a primitive number prior to validation blocks the stateful object bypass technique.
// Defensive input validation wrapper
function safeSetSize(list, targetSize) {
const sizePrimitive = Number(targetSize);
if (
!Number.isInteger(sizePrimitive) ||
sizePrimitive < 0 ||
sizePrimitive >= 1073741824
) {
throw new RangeError('Size must be a safe, positive primitive integer below 2^30');
}
return list.setSize(sizePrimitive);
}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| Product | Affected Versions | Fixed Version |
|---|---|---|
immutable immutable-js | < 4.3.9 | 4.3.9 |
immutable immutable-js | >= 5.0.0-beta.1, < 5.1.8 | 5.1.8 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-190, CWE-400, CWE-835, CWE-1284 |
| Attack Vector | Network |
| CVSS v4.0 | 8.7 (High) |
| EPSS Score | 0.0037 (Percentile: 29.38%) |
| Impact | Denial of Service, Data Corruption |
| Exploit Status | Proof-of-Concept |
| KEV Status | Not Listed |
The software performs an operation that can overflow the standard bounds of a data type, leading to resource consumption or logic flaws.
CVE-2026-12243 is a path traversal vulnerability in the Natural Language Toolkit (NLTK) version 3.9.4. The flaw exists because the input validation routine fails to account for percent-encoded directory traversal sequences like '..%2f' before passing them to urllib.request.url2pathname(), which decodes them into active traversal sequences.
CVE-2026-73654 is a high-severity prototype pollution vulnerability in Trigger.dev. The flaw occurs during the handling of run-metadata updates through the PUT /api/v1/runs/:runId/metadata endpoint. Because user-supplied keys are parsed directly by the @jsonhero/path library without sanitization, an authenticated attacker with low privileges can pollute the global Object.prototype. This causes database queries via Prisma ORM to fail validation and induces unhandled exceptions in the Prometheus metrics client, resulting in a process-wide denial of service.
CVE-2026-73559 is an uncontrolled resource consumption vulnerability in the vLLM engine, specifically within the /v1/completions API endpoint, allowing authenticated attackers to cause application-level denial of service via unbounded prompt arrays.
A critical security bypass vulnerability in Argo Workflows allows authenticated attackers with workflow submission privileges to bypass 'Strict' or 'Secure' template referencing restrictions. By injecting unvalidated fields into the nested ArtifactGC configuration, attackers can execute arbitrary pod patches, leading to host namespace escape and cluster-wide privilege escalation.
A Server-Side Request Forgery (SSRF) / Confused Deputy vulnerability has been identified in Pydantic AI UI Adapters (such as VercelAIAdapter). Under certain conditions, a malicious client can supply manipulated message history with provider metadata that forces the server to resolve files within privileged cloud environments (AWS S3, Google Cloud Storage) or model providers. This occurs because the adapters deserialize client-provided metadata structures directly into UploadedFile instances without validation, which are subsequently fetched using high-privilege server credentials.
A path traversal vulnerability in the optional dashboard server of atomic-agents-stack before version 1.1.0 allows unauthenticated remote attackers to read arbitrary files from the host filesystem.