Jul 31, 2026·5 min read·52 visits
A prototype pollution vulnerability in the recursive merging function of @phun-ky/defaults-deep versions prior to 2.0.5 allows remote attackers to execute arbitrary modification of Object.prototype attributes via crafted inputs.
CVE-2026-54737 is a high-severity Prototype Pollution vulnerability in the @phun-ky/defaults-deep npm library prior to version 2.0.5. Due to unsafe recursive object merging, unauthenticated attackers can supply structured payloads that modify the properties of Object.prototype, compromising the runtime process state.
The NPM package @phun-ky/defaults-deep is designed to apply recursive fallback values to input configurations, maintaining array structures without depending on the heavier Lodash library. The application surface area for this library includes configuration parsers, form processors, and API endpoints that ingest unstructured user input.
When applications process untrusted JSON objects and pass them to the library's defaultsDeep() or mergeWith() routines, they expose the runtime memory. The lack of key-level isolation during the recursive object merging phase exposes the environment to target-specific attribute manipulation.
This vulnerability is mapped to CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution'). Because all standard structures in JavaScript inherit from the top-level base object, any mutation occurring on the root prototype propagates directly through the entire active application memory space.
JavaScript objects resolve property requests using a prototype-based lookup mechanism. If a requested property is not explicitly defined on an instance, the engine traverses backward along the chain via the implicit __proto__ accessor until it hits the terminal root at Object.prototype. Alternatively, standard constructors expose their base class prototype via constructor.prototype.
The root cause of CVE-2026-54737 resides inside the deep merging module at src/utils/merge-with.ts. The primary recursive helper function baseMerge receives both target and source objects, evaluates properties using a custom ownKeys() iteration helper, and updates target properties without checking for reserved identifiers.
During processing, an input payload containing the key __proto__ maps to target['__proto__'], which immediately exposes the global Object.prototype. The subsequent recursive iteration steps directly into this reference and appends properties to it. Similarly, nested configurations using the keys constructor and prototype can traverse and corrupt properties on standard constructors.
An analysis of the source code before version 2.0.5 demonstrates that the recursive loop executes unchecked evaluation steps over source properties:
// Pre-2.0.5 vulnerable recursive merge processing
for (const key of ownKeys(source)) {
const srcValue = (source as any)[key];
const objValue = (target as any)[key];
if (isObjectLoose(srcValue)) {
if (!isObjectLoose(objValue)) {
(target as any)[key] = {};
}
baseMerge((target as any)[key], srcValue);
} else {
(target as any)[key] = srcValue;
}
}The maintainers resolved this issue in pull request 49 by introducing an immutable blocklist and integrating an early validation check inside the properties loop:
// Remediated key-validation block inside merge-with.ts
const UNSAFE_KEYS = new Set<string>(['__proto__', 'constructor', 'prototype']);
// ... within baseMerge
for (const key of ownKeys(source)) {
if (typeof key === 'string' && UNSAFE_KEYS.has(key)) {
continue;
}
const srcValue = (source as any)[key];
// ... process remaining properties safely
}The fix defines an explicit Set of system keys and implements a short-circuit branch that skips properties matching any banned strings. The constraint typeof key === 'string' preserves processing compatibility for symbol keys, which bypasses potential casting errors. This remediation effectively eliminates both direct and indirect lookup-path traversal vectors.
An attacker can exploit this flaw by passing a specifically structured JSON payload to an endpoint that deserializes the request and merges it with existing default state definitions.
To pollute the environment directly, the attacker structure utilizes the standard prototype accessor:
// Direct prototype pollution vector
import defaultsDeep from '@phun-ky/defaults-deep';
const payload = JSON.parse('{"__proto__": {"pollutedProperty": "compromised"}}');
defaultsDeep({}, payload);
const validationInstance = {};
console.log(validationInstance.pollutedProperty); // Output: "compromised"If basic sanitizers attempt to strip the __proto__ string, attackers can leverage constructor-based chaining to achieve the same result:
// Indirect constructor traversal vector
import { mergeWith } from '@phun-ky/defaults-deep/dist/utils/merge-with';
const target = {};
const payload = {
constructor: {
prototype: {
pollutedProperty: 'compromised'
}
}
};
mergeWith(target, payload);
console.log(({}).pollutedProperty); // Output: "compromised"The concrete impact of this vulnerability depends on how the host application utilizes objects subsequent to the merge operation. If down-stream logic evaluates administrative privilege checks by looking for uninitialized variables (e.g., checking if (user.isAdmin)), an attacker can escalate privileges globally by polluting Object.prototype.isAdmin with true.
In scenarios where the Node.js application uses template compilation libraries (like EJS, Pug, or Handlebars), attackers can exploit prototype pollution to perform Remote Code Execution (RCE). By corrupting global properties that dictate template output formatting or load internal helper modules, an attacker can hijack the shell context.
Furthermore, modifying standard object behavior can result in application-wide crash states. Corrupting basic operations like toString or valueOf raises unhandled runtime exceptions, inducing a persistent Denial of Service (DoS) across the active Node.js server container.
To remediate this vulnerability, immediate dependency upgrades must be performed. Ensure @phun-ky/defaults-deep is updated to version 2.0.5 or later:
npm install @phun-ky/defaults-deep@2.0.5If systems cannot be updated immediately, implement custom recursive payload sanitization filters to scrub dangerous keywords before passing inputs to the merge utility:
function sanitizeObject(input) {
if (typeof input !== 'object' || input === null) return input;
const unsafeProperties = ['__proto__', 'constructor', 'prototype'];
for (const property of Object.getOwnPropertyNames(input)) {
if (unsafeProperties.includes(property)) {
delete input[property];
} else {
sanitizeObject(input[property]);
}
}
return input;
}Additionally, defense-in-depth measures such as locking down the runtime base environment will help neutralize prototype modifications. Developers can enforce prototype freezing inside primary entry scripts to prevent modifications:
Object.freeze(Object.prototype);
Object.freeze(Array.prototype);CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
@phun-ky/defaults-deep @phun-ky | < 2.0.5 | 2.0.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1321 |
| Attack Vector | Network (AV:N) |
| CVSS Score | 7.3 (High) |
| EPSS Status | Not Registered |
| Impact | Prototype Pollution (Privilege Escalation, DoS, RCE) |
| Exploit Status | PoC Available |
| KEV Status | Not Listed |
The application receives input from an upstream component, but does not sanitize or incorrectly sanitizes the input before utilizing it to modify the attributes of an object prototype.
@tinacms/cli prior to version 3.0.0 dynamically constructs client source files using string interpolation without properly sanitizing runtime configuration variables. An attacker with permissions to create a branch or pull request can inject arbitrary JavaScript statements via a crafted Git ref name, leading to execution during automated build processes.
Nginx UI versions 2.2.0 through 2.5.10 fail to properly configure Gin framework trusted proxies when deployed behind a reverse proxy. This causes all incoming HTTP requests to be attributed to the loopback IP (127.0.0.1), enabling IP allowlist bypass and global authentication lockouts.
Nginx UI versions 2.5.0 through 2.5.10 contain an uncontrolled resource consumption vulnerability in the node authentication handler. Unauthenticated remote attackers can exhaust host disk storage and I/O resources by submitting large HTTP request bodies to node-signature endpoints prior to cryptographic signature validation.
Vikunja versions 2.3.0 through 2.6.0 contain an insufficient session expiration vulnerability (CWE-613) within the WebSocket authentication handler. Although Vikunja enforces server-side session tracking and revocation for REST API routes, the WebSocket handshake handler validates cryptographic JWT signatures without querying the database session state. Consequently, revoked JWT tokens can establish new real-time WebSocket connections, and existing connections persist after session revocation.
A cross-tenant boundary breach vulnerability in Vikunja allows an authenticated user to trigger global task position recalculations across all tenant instances by creating a saved filter with an empty filter string payload.
An access revocation flaw in Vikunja allows removed collaborators to retain outbound webhooks and link shares created prior to revocation, enabling persistent exfiltration of sensitive task data.