Lodash _.merge Prototype Pollution in Recent Versions

This article examines how the Lodash JavaScript library handles prototype pollution vulnerabilities within its deep-merging utility, _.merge. Historically a frequent target for prototype pollution attacks, _.merge has undergone significant patching in modern releases. The following breakdown explains the nature of the original vulnerability, the mitigation mechanisms introduced in recent Lodash versions, and the security implications for modern JavaScript applications.

The Historic Prototype Pollution Flaw

Prototype pollution occurs when an attacker can manipulate object properties to inject or modify attributes on JavaScript's base Object.prototype. Because almost all objects inherit from Object.prototype, any property added there becomes globally accessible across the runtime environment.

In older versions of Lodash (prior to version 4.17.12), _.merge recursively copied properties from a source object to a target object without verifying property names. An attacker supplying untrusted JSON data could pass objects containing sensitive keys such as __proto__ or constructor.prototype:

const maliciousPayload = JSON.parse('{"__proto__": {"isAdmin": true}}');
_.merge({}, maliciousPayload);

// In vulnerable versions:
console.log({}.isAdmin); // true

This allowed attackers to bypass authorization checks, alter application logic, or cause denial of service (DoS) crashes.

How Recent Versions Neutralize the Threat

In recent versions of Lodash (starting broadly from version 4.17.15 and finalized in 4.17.21), prototype pollution via _.merge is neutralized through defensive checks implemented in internal merge routines, specifically baseMergeDeep.

Key Filtering

When traversing object keys during a merge operation, Lodash now explicitly checks for dangerous property names. If an object property key is __proto__, constructor, or prototype, the merge function skips the operation for that key.

Prototype Shielding

Because modern Lodash intercepts __proto__ and constructor.prototype keys, it will not assign nested values to Object.prototype. Merging a malicious payload now produces safe, contained behavior:

const _ = require('lodash');

const payload = JSON.parse('{"__proto__": {"polluted": "yes"}}');
const target = {};

_.merge(target, payload);

// In modern versions:
console.log(target.polluted); // undefined
console.log({}.polluted);     // undefined

The payload's attributes are effectively dropped or treated as own properties without modifying the prototype chain.

Current Implications and Best Practices

While recent versions of Lodash actively defend against prototype pollution within _.merge, developers should still observe standard defensive programming practices: