EncryptCodecencryptcodec
Blog/Web Security
Web SecurityMarch 29, 2026 · 7 min read

Prototype Pollution in JavaScript: How __proto__ Can Break Your App

In JavaScript, every object inherits from Object.prototype. If an attacker can modify that prototype, they can inject properties into every object in your application — including ones that control authentication.

What Prototype Pollution Is

JavaScript objects inherit properties from their prototype chain. Object.prototype sits at the top of this chain for most objects. If you can write to it, you affect every object:

// Polluting the prototype
const obj = {};
obj.__proto__.isAdmin = true;
 
// Now EVERY object has isAdmin = true
const user = {};
console.log(user.isAdmin); // true

This is not a theoretical concern. It is a real vulnerability class with CVEs in widely-used libraries.

How It Happens in Practice

Prototype pollution occurs when user-controlled input is used in a recursive merge, deep clone, or property assignment without checking for dangerous keys.

The Vulnerable Pattern

// A naive deep merge function
function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === "object" && source[key] !== null) {
      if (!target[key]) target[key] = {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}
 
// Attacker-controlled input
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
 
const config = {};
merge(config, malicious);
 
// Every object is now polluted
const user = {};
console.log(user.isAdmin); // true — authentication bypassed

The attacker sends {"__proto__": {"isAdmin": true}} as JSON. The merge function walks into __proto__ and writes to Object.prototype, polluting all objects.

Exploiting for Authentication Bypass

// Server-side code
function checkAccess(user) {
  if (user.role === "admin") {
    return true; // grant access
  }
  return false;
}
 
// Before pollution
const normalUser = { name: "Alice" };
checkAccess(normalUser); // false
 
// After pollution via __proto__.role = "admin"
checkAccess(normalUser); // true — normalUser.role resolves to "admin" via prototype

Exploiting for RCE (Remote Code Execution)

In server-side JavaScript, prototype pollution can lead to code execution through template engines or child process spawning:

// If Object.prototype is polluted with:
// { "shell": "/proc/self/exe", "env": { "NODE_OPTIONS": "--require /tmp/malicious.js" } }
 
// Then any child_process.spawn() call inherits these options
const { execSync } = require("child_process");
execSync("ls"); // executes with polluted env — runs attacker's code

Real-World CVEs

Lodash (CVE-2020-8203)

lodash.merge, lodash.defaultsDeep, and lodash.set were all vulnerable to prototype pollution. Lodash has over 25 million weekly npm downloads.

const _ = require("lodash");
 
// This polluted Object.prototype in lodash < 4.17.16
_.merge({}, JSON.parse('{"__proto__": {"polluted": true}}'));
 
console.log({}.polluted); // true

jQuery (CVE-2019-11358)

jQuery's $.extend was vulnerable when performing deep merges:

// jQuery < 3.4.0
$.extend(true, {}, JSON.parse('{"__proto__": {"polluted": true}}'));
 
console.log({}.polluted); // true

minimist (CVE-2020-7598)

The popular argument parser was vulnerable:

# This polluted Object.prototype
node app.js --__proto__.polluted=true

Defenses

1. Block dangerous keys

function safeMerge(target, source) {
  for (const key in source) {
    if (key === "__proto__" || key === "constructor" || key === "prototype") {
      continue; // skip dangerous keys
    }
    if (typeof source[key] === "object" && source[key] !== null) {
      if (!target[key]) target[key] = {};
      safeMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

2. Use Object.create(null)

Objects created with Object.create(null) have no prototype — they cannot be polluted:

// Normal object — has prototype
const obj = {};
console.log(obj.toString); // [Function: toString]
 
// Null-prototype object — no prototype chain
const safe = Object.create(null);
console.log(safe.toString); // undefined
// Cannot be affected by prototype pollution

Use this for configuration objects, lookup maps, and any object that stores user-influenced data.

3. Use Map instead of plain objects

// ❌ Plain object — vulnerable to prototype pollution
const cache = {};
cache[userInput] = value;
 
// ✅ Map — not affected by prototype pollution
const cache = new Map();
cache.set(userInput, value);

4. Freeze the prototype

// Prevent any modification to Object.prototype
Object.freeze(Object.prototype);
 
// Now pollution attempts throw in strict mode or silently fail
"use strict";
({}).__proto__.isAdmin = true; // TypeError: Cannot add property isAdmin

This is aggressive and may break some libraries, but it is effective for applications you fully control.

5. Validate input with schemas

// Using zod to validate input — rejects unexpected keys
import { z } from "zod";
 
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});
 
// Strips __proto__ and any other unexpected keys
const safeInput = UserSchema.parse(untrustedInput);

6. Use hasOwnProperty checks

// ❌ Checking property existence without hasOwnProperty
if (user.isAdmin) {
  // Vulnerable — isAdmin could come from polluted prototype
}
 
// ✅ Check own properties only
if (Object.hasOwn(user, "isAdmin") && user.isAdmin) {
  // Safe — only checks properties directly on the object
}

Detection

Scan your dependencies for known prototype pollution vulnerabilities:

npm audit
# Look for "Prototype Pollution" in the output
 
# Or use Snyk
npx snyk test

Conclusion

Prototype pollution is subtle and dangerous. It turns a simple property merge into authentication bypass or remote code execution. Block __proto__, constructor, and prototype keys in all merge operations. Use Object.create(null) or Map for data stores. Validate all input with schemas. And keep your dependencies updated — lodash, jQuery, and minimist all had prototype pollution CVEs that affected millions of applications.

Share this post

Try the Prototype Pollution Simulation

Related posts