Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
HIGH severity

Immutable.js `List` 32-bit trie overflow → unrecoverable DoSGHSA-v56q-mh7h-f735

HIGHFix: immutable-js/immutable-js@a1a1ee4

GHSA-v56q-mh7h-f735 is a high-severity (CVSS 7.5) CWE-190 vulnerability in immutable. A fix is available for immutable — see the affected versions and patch details below.

Also known asCVE-2026-59879
Published
Updated
Affected
3 pkgs
Patched
3 / 3
Exploits
None indexed
Exploitation data as of Oct 7, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for GHSA-v56q-mh7h-f735.

EPSS Exploitation Probability

via FIRST.org ↗
0.7%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs50th percentile — riskier than 50% of all scored CVEsHighest risk
0.00%0.39%0.78%1.17%0.4%0.7%0.7%Aug 26Sep 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

GHSA-v56q-mh7h-f735 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 384,534 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

3 pkgs affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

8Kother npm packages depend on this — each one inherits the vulnerability until it's patched upstream
immutablenpm
51.2Mdownloads / week

Description

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:

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)

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

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

  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.

Affected Packages

3 total 3 fixed
EcosystemPackageVulnerable rangeFix
📦npmimmutable≥ 4.0.0-rc.1&&< 4.3.94.3.9npm install immutable@4.3.9
📦npmimmutable≥ 5.0.0-beta.1&&< 5.1.85.1.8npm install immutable@5.1.8
📦npmimmutableall versions3.8.4npm install immutable@3.8.4

Affected Products

1 product · 2 configurations
Application
immutableimmutable-js
≥ 5.0.0 && < 5.1.8
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for immutable, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update immutable to 4.3.9 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-v56q-mh7h-f735 is resolved across your whole dependency graph.

  3. Workarounds

    Cap what an attacker can consume: apply request size, rate and timeout limits in front of the affected component, and run it with memory and CPU limits so exhaustion degrades one worker rather than the whole service.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatModerate

This Moderate impact denial of service vulnerability in Immutable.js affects Red Hat products that process untrusted input through List operations. An attacker can trigger an infinite loop or unbounded memory allocation by providing specially crafted large index values, leading to a service disruption. Additionally,…

Workaround published by Red Hat
To mitigate this issue, applications should implement input validation to reject or clamp any externally supplied List index or key-path segment that exceeds a sane maximum, specifically values greater than or equal to 2^30. Additionally, running request handling in isolated worker processes with capped heap sizes (e.g., using `--max-old-space-size`) can contain the impact of a potential process abort.
Source: Red Hat security advisory for GHSA-v56q-mh7h-f735 (CC BY 4.0)
ProductFixed inAdvisory
Red Hat Satellite 6.19 for RHEL 9foreman-0:3.18.0.14-1.el9satRHSA-2026:74503
multicluster engine for Kubernetes 2.6multicluster-engine/console-mce-rhel9:1790117683RHSA-2026:72850
multicluster engine for Kubernetes 2.8multicluster-engine/console-mce-rhel9:1790121310RHSA-2026:72854
multicluster engine for Kubernetes 2.9multicluster-engine/console-mce-rhel9:1789583887RHSA-2026:72851
Red Hat Advanced Cluster Management for Kubernetes 2.14rhacm2/console-rhel9:1788563615RHSA-2026:67539
Red Hat Ansible Automation Platform 2.1ansible-automation-platform/automation-portal:1790254963RHSA-2026:72712
Red Hat Discovery 2discovery/discovery-ui-rhel9:1789677459RHSA-2026:69289
Red Hat OpenShift Container Platform 4.21openshift4/nmstate-console-plugin-rhel9:1790778086RHSA-2026:74384

Frequently Asked Questions

## 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`
O3 Security · Impact-Aware SCA

Is GHSA-v56q-mh7h-f735 in your dependencies?

Find it across npm, including transitive dependencies.

Immutable.js `List` 32-bit trie overflow → unrecoverable DoS