CVE-2026-59879: Immutable.js `List` 32-bit trie overflow → unrecoverable DoS

Published Jul 8, 2026
·
Updated

Summary

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):

- 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. - 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.

The 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.

There is also a companion silent data-corruption issue in setSize:

js List([1, 2, 3]).setSize(2 31); // before fix => size 0 (silently cleared) List([1, 2, 3]).setSize(2 32 + 5); // before fix => size 5 (huge value wraps to 5)

Impact

Availability 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.).

No confidentiality or integrity impact, no RCE. The companion setSize bug can silently corrupt application state (wrong size) without crashing.

Reproduction (immutable 5.1.7)

ts import { fromJS, List } from 'immutable';

// 1) Populated List: OOM -> process abort (SIGABRT, exit 134) within ~2s fromJS({ items: new Array(64).fill(0) }).setIn(['items', '1073741824'], 'x');

// 2) Empty List: hangs forever, uncatchable List().set(2 30, 'x');

// 3) Silent truncation List([1, 2, 3]).setSize(2 31); // => size 0 List([1, 2, 3]).setSize(2 32 + 5); // => size 5

A 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).

Any 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.

Root cause

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):

1. Infinite loop (the hang / OOM). The level-raising loop

js while (newTailOffset >= 1 << (newLevel + SHIFT)) { newRoot = new VNode( newRoot && newRoot.array.length ? [newRoot] : [], owner ); newLevel += SHIFT; }

relies 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.

2. 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.

The 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).

Remediation

The fix is contained to setListBounds() in src/List.js:

1. 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 (MAXLISTSIZE = 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").

2. 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).

This 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).

Is the new limit a breaking change?

No 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.

Mitigations (for users who cannot upgrade immediately)

- Validate/clamp any externally supplied List index or setIn/updateIn key-path segment against a sane maximum before passing it to immutable. - Reject numeric path segments ≥ 2 30. - Run request handling in a worker that can be restarted, and cap the heap (--max-old-space-size) so an abort is contained.

Other sources

Immutable.js provides many Persistent Immutable data structures. Prior to 4.3.9 and 5.1.8, List#set, List#setSize, List#setIn, List#updateIn, and the functional set, setIn, and updateIn mishandle an index or size in the range 2 30 to 2 31 in setListBounds in src/List.js, causing an empty List to enter an uncatchable infinite loop, a populated List to allocate without bound until process abort, or setSize to silently wrap large values. This issue is fixed in versions 4.3.9 and 5.1.8.

MITRE

Affected Software

5 affected componentsFixes available
npm/immutable-js>4.3.8<=4.3.9, >5.1.7<=5.1.8
Immutable-js Immutable Node.js<4.3.9
Immutable-js Immutable Node.js>=5.0.0<5.1.8
npm/immutable>=5.0.0-beta.1<5.1.8
5.1.8
npm/immutable<4.3.9
4.3.9

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/immutable to a version that resolves this vulnerability.

    Fixed in 5.1.8
  2. Upgrade

    Upgrade npm/immutable to a version that resolves this vulnerability.

    Fixed in 4.3.9
  3. Upgrade

    Upgrade immutable.js to a version that resolves this vulnerability.

    Fixed in 4.3.9
  4. Upgrade

    Upgrade immutable.js to a version that resolves this vulnerability.

    Fixed in 5.1.8
  5. Configuration

    Validate/clamp any externally supplied List index or any setIn/updateIn key-path segment before passing it into immutable; reject numeric path segments >= 2 ** 30 to avoid the 32-bit trie overflow behavior in List#set/List#setSize/List#setIn/List#updateIn and functional set/setIn/updateIn.

    Immutable.js List inputs index/key-path segment validation = Reject numeric path segments >= 2 ** 30
  6. Compensating control

    For services that cannot upgrade immediately, run request handling in a worker that can be restarted, and cap the heap with --max-old-space-size so an abort/OOM is contained.

  7. Operational

    If an incident/attempt occurs before upgrading, only killing the worker recovers from the populated-list OOM/SIGABRT hang or the empty-list uncatchable infinite loop; restart the worker.

Event History

Jul 8, 2026
CVE Published
via MITRE·03:47 PM
Data Sourced
via MITRE·03:47 PM
DescriptionWeakness
Data Sourced
via NVD·05:17 PM
RemedyDescriptionSeverityWeaknessAffected Software
Jul 21, 2026
Advisory Published
via GitHub·06:36 PM
Data Sourced
via GitHub·06:36 PM
DescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-59879?

The severity of CVE-2026-59879 is rated high with a CVSS score of 8.7.

2

What vulnerability is associated with CVE-2026-59879?

CVE-2026-59879 is an integer overflow vulnerability in Immutable.js that can lead to an unrecoverable Denial of Service.

3

How do I fix CVE-2026-59879?

To fix CVE-2026-59879, update Immutable.js to versions 4.3.9 or 5.1.8 or later.

4

What software is affected by CVE-2026-59879?

The affected software includes Immutable.js versions prior to 4.3.9 and 5.1.8.

5

What is the impact of CVE-2026-59879?

The impact of CVE-2026-59879 is an empty List which can result in an unrecoverable Denial of Service condition.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203