Jul 21, 2026·8 min read·5 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-58426 is a critical security vulnerability in Gitea Actions where improper signature serialization allows an authenticated attacker to execute a canonicalization (boundary-shifting) attack. By rewriting query parameters while keeping the signature intact, the attacker can bypass access control checks to read private workflow artifacts or modify concurrent task upload states.
GitPython prior to version 3.1.51 contains a command injection vulnerability due to flaws in its option validation routine. By exploiting Git's native argument parser behavior, specifically long-option abbreviation resolution and short-option clustering, attackers can bypass the check_unsafe_options blocklist to execute arbitrary OS commands.
A critical security bypass and algorithm downgrade vulnerability in the PostgreSQL JDBC Driver (pgjdbc) allows Man-in-the-Middle (MITM) attackers to silently bypass channel binding requirements. When configured with `channelBinding=require`, the driver fails to assert that the negotiated SCRAM mechanism utilizes channel binding, allowing downgrade to plain SCRAM-SHA-256 when encountering unsupported server certificate signature algorithms (such as Ed25519 or Ed448).
CVE-2026-56170 is a high-severity Remote Denial of Service (DoS) vulnerability in Microsoft's ASP.NET Core framework. The vulnerability spans three separate resource-management vectors within the ASP.NET Core ecosystem, including SignalR Stateful Reconnect allocations, JSON Patch Type Confusion leading to stack exhaustion, and Kestrel HTTP/2 synchronization issues. An unauthenticated remote attacker can exploit these issues to cause process-terminating exceptions, rendering applications unavailable.
The Loofah Ruby gem version 2.25.0 and 2.25.1 contains an incomplete validation vulnerability in its public string-level utility Loofah::HTML5::Scrub.allowed_uri?. This helper fails to detect 'javascript:' URIs that are split by HTML5 named whitespace character references such as 	 and 
. Applications manually invoking this utility to validate links are vulnerable to stored Cross-Site Scripting (XSS), as browsers parse and remove these entities during rendering.
CVE-2026-50525 is a high-severity Denial of Service (DoS) vulnerability in the Microsoft .NET XML Cryptography stack. The vulnerability resides in the `System.Security.Cryptography.Xml` library, specifically within the `EncryptedXml` processing engine. Unauthenticated remote attackers can exploit this flaw by sending specifically crafted XML documents containing nested or recursive structures, or utilizing resource-intensive transforms. Processing such payloads leads to infinite CPU loops, stack exhaustion, or memory starvation, resulting in application termination.