What Is Prototype Pollution and How to Prevent It
Prototype pollution is a critical JavaScript vulnerability that
occurs when an attacker manipulates the base
Object.prototype, injecting or modifying default properties
across an entire application runtime. This article explains the
mechanics behind prototype pollution, explores common attack vectors
such as unsafe object merging, and provides actionable defense
strategies—including prototype freezing, input sanitization, and the use
of prototype-less data structures—to secure JavaScript and Node.js
applications.
Understanding Prototype Pollution
JavaScript uses prototype-based inheritance. Almost all objects
inherit properties and methods from a prototype chain, which terminates
at Object.prototype. If an attacker successfully injects a
malicious property into Object.prototype, that property
becomes instantly accessible on every object created throughout the
application lifecycle.
Depending on how the application handles object properties, prototype
pollution can lead to serious consequences, including: * Denial
of Service (DoS): Overwriting built-in methods (like
toString or valueOf) to trigger uncaught
exceptions. * Authentication/Logic Bypass: Injecting
properties like isAdmin: true into checks that verify an
object property directly. * Remote Code Execution
(RCE): Overwriting sensitive system configurations or
parameters passed into functions like
child_process.exec.
Common Attack Vectors
Prototype pollution primarily happens when applications recursively merge, clone, or assign properties from unvalidated, user-controlled input (such as JSON payloads or URL query parameters).
A vulnerable recursive merge function allows keys like
__proto__ or constructor.prototype to traverse
up to the root prototype:
// Vulnerable merge implementation
function unsafeMerge(target, source) {
for (let key in source) {
if (typeof target[key] === 'object' && typeof source[key] === 'object') {
unsafeMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// Malicious input
const payload = JSON.parse('{"__proto__": {"polluted": true}}');
unsafeMerge({}, payload);
// The global prototype is now compromised
console.log({}.polluted); // trueHow to Guard Against Prototype Pollution
Securing an application against prototype pollution requires defense-in-depth, combining safe coding patterns, runtime defenses, and strict input validation.
1. Block Sensitive Object Keys
When implementing or choosing deep clone or merge functions,
explicitly reject dangerous keys such as __proto__,
constructor, and prototype.
function safeMerge(target, source) {
for (let key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // Skip dangerous keys
}
if (typeof target[key] === 'object' && typeof source[key] === 'object') {
safeMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}2. Use Objects Without Prototypes
For plain dictionaries or key-value storage from external input,
instantiate objects using Object.create(null) instead of
object literals ({}). These objects do not inherit from
Object.prototype and lack the __proto__ chain
entirely.
const safeDictionary = Object.create(null);
safeDictionary["anyKey"] = "value"; // Immune to Object.prototype inheritance3. Use Map
Instead of Plain Objects
When handling dynamic keys from untrusted user input, use native
JavaScript Map collections rather than plain objects.
Map instances do not map keys to object prototype
properties, isolating user-supplied data from prototype mechanics.
const userInputs = new Map();
userInputs.set('__proto__', 'safeValue'); // Does not pollute Object.prototype4. Freeze the Base Prototype
Freezing Object.prototype at application startup
prevents existing prototype methods and properties from being modified
or new properties from being added.
Object.freeze(Object.prototype);Note: In Node.js environments, you can also launch the runtime
with the --disable-proto=delete or
--disable-proto=throw flag to restrict access to
Object.prototype.__proto__ globally.
5. Validate Input Schemas
Use schema validation libraries (such as Zod, Joi, or Ajv) to strictly validate and sanitize payloads before processing them. Strip unrecognized properties and reject payloads that attempt to define reserved JavaScript property names.
6. Keep Dependencies Updated
Vulnerabilities often reside in third-party utilities that perform
object operations (such as outdated versions of lodash or minimist).
Regularly audit and patch project dependencies using tools like
npm audit or automated vulnerability scanners.