chore(deps): update dependency immutable@>=5.0.0 <5.1.5 to v5.1.8 [security] - #6388
Merged
renovate[bot] merged 1 commit intoJul 27, 2026
Merged
Conversation
|
✅ Meticulous spotted 0 visual differences across 289 screens tested: view results. Meticulous evaluated ~4 hours of user flows against your PR. Expected differences? Click here. Last updated for commit |
renovate
Bot
deleted the
renovate/npm-immutable-=5.0.0-5.1.5-vulnerability
branch
July 27, 2026 23:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
5.1.5→5.1.8](https://renovatebot.com/diffs/npm/immutable@>=5.0.0 <5.1.5/5.1.5/5.1.8)Immutable.js
List32-bit trie overflow → unrecoverable DoSCVE-2026-59879 / GHSA-v56q-mh7h-f735
More information
Details
Summary
List#set,List#setSize,List#setIn,List#updateIn(and the functionalset/setIn/updateIn) mishandle an index or size in the range[2 ** 30, 2 ** 31):Listthe operation enters an uncatchable infinite loop (a tight CPU spin; a surroundingtry/catchnever regains control). Only killing the worker recovers it.List(≥ 32 elements — i.e. any array of ≥ 32 items turned into aListbyfromJS) the loop allocates without bound → heap exhaustion → the process aborts (SIGABRT, exit134, or kernel OOM-kill137). 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:Impact
Availability only. A reachable configuration is any endpoint that routes untrusted input into a
Listindex or asetIn/updateInkey-path — which the extremely commonstate = 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
setSizebug can silently corrupt application state (wrong size) without crashing.Reproduction (immutable 5.1.7)
A remote 43-byte HTTP request (
{"path":["items","1073741824"],"value":"x"}) is sufficient to abort a worker that applies it viastate = 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
Liststores its values in a 32-wide trie (SHIFT = 5, so each level addresses 5 more bits) and uses signed 32-bit bitwise arithmetic throughoutsetListBounds()(src/List.js):relies on
1 << (newLevel + SHIFT). A JavaScript shift count is taken mod 32, so oncenewLevel + SHIFTreaches31the term goes negative (1 << 31 === -2147483648) and at32wraps to1(1 << 35 === 8). The comparison then staystrueforever and the loop never terminates. On a populatedList, each iteration retains a newVNode([newRoot]), so the heap fills and V8 aborts; on an emptyListit spins on CPU without allocating.setSizecorruption). Thebegin |= 0/end |= 0coercion (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 which1 << (newLevel + SHIFT)stays a valid positive 32-bit integer throughout the loops (newLevel + SHIFTstays ≤ 30).Remediation
The fix is contained to
setListBounds()insrc/List.js:Validate up front, before the lossy
| 0coercion. Compute the intended origin and capacity in full precision and throw a clear, catchableRangeErrorwhen they exceed the addressable range (MAX_LIST_SIZE = 2 ** 30).Infinity/NaNare left to the existing| 0 → 0behaviour (sosetSize(Infinity)stays0andslice(0, Infinity)still means "to the end").Stop the shift from wrapping. Replace
1 << expin the level-raising loops with a helper that uses the cheap bitwise shift while it is exact (exp ≤ 30, the common path including everypush/setSize/slice) and falls back to the non-wrapping2 ** exponly for the rare deep trees reached when a negative origin (unshift/ negative index) is normalized to a large positive capacity (expcan reach 35 there, where1 << 35would wrap to 8).This turns every hang, the misleading
"Maximum call stack size exceeded", the OOM/SIGABRT, and the silentsetSizetruncation into one descriptiveRangeError, preserves all behaviour for sizes< 2 ** 30, and keeps the hotpushpath on the fast bitwise shift (the2 ** expbranch is never reached by non-negative operations).Is the new limit a breaking change?
No working code is affected. A
Listcould never actually hold≥ 2 ** 30values 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 thatsetSize(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)
Listindex orsetIn/updateInkey-path segment against a sane maximum before passing it to immutable.≥ 2 ** 30.--max-old-space-size) so an abort is contained.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
immutable-js/immutable-js (immutable@>=5.0.0 <5.1.5)
v5.1.8Compare Source
v5.1.7Compare Source
v5.1.6Compare Source
reversedSequence.sizein__iteratorinstead of this #2196Configuration
📅 Schedule: (UTC)
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about these updates again.
This PR was generated by Mend Renovate. View the repository job log.