How to Prevent Prototype Pollution in Node.js
Prototype pollution is a critical vulnerability in Node.js
applications that allows attackers to inject malicious properties into
the global Object.prototype. This can lead to application
crashes, bypass of security controls, or even remote code execution.
This article provides a direct, actionable guide on how to secure your
Node.js applications against prototype pollution using secure coding
practices, input validation, and defensive configurations.
Use Map Instead of Plain Objects
The simplest way to avoid prototype pollution is to avoid using plain
JavaScript objects ({}) as key-value stores for
user-controlled input. Instead, use the ES6 Map class.
Map does not inherit from Object.prototype,
making it completely immune to prototype-based attacks.
// Vulnerable to prototype pollution
const userRoles = {};
userRoles[userInputKey] = userInputValue;
// Secure against prototype pollution
const userRoles = new Map();
userRoles.set(userInputKey, userInputValue);If you must use a plain object, create it without a prototype using
Object.create(null). This ensures the object has no
inheritance chain, meaning __proto__ and
constructor properties do not exist on it.
const safeObject = Object.create(null);
safeObject[userInputKey] = userInputValue; // SafeFreeze the Object Prototype
You can prevent modifications to JavaScript’s default prototypes by
freezing them when your application starts. Placing
Object.freeze(Object.prototype) at the entry point of your
Node.js application stops attackers from injecting new properties or
altering existing ones globally.
// Run this at the very beginning of your entry file (e.g., app.js or index.js)
Object.freeze(Object.prototype);
Object.freeze(Array.prototype);While highly effective, test your application thoroughly after applying this, as some third-party libraries may rely on modifying default prototypes and could break.
Implement Strict Input Validation
Prototype pollution typically occurs when an application recursively merges or deep-clones user-provided JSON payloads without validation. You should sanitize and validate all incoming request bodies using schema validators like Ajv or Joi.
Configure your validators to reject any payloads containing forbidden
keys, specifically: * __proto__ * constructor
* prototype
Example using Joi:
const Joi = require('joi');
const schema = Joi.object({
// Define expected fields explicitly and disallow unknown properties
}).unknown(false); Use Secure Libraries for Merging and Cloning
Custom implementation of deep-merge or cloning functions is a common source of prototype pollution. Avoid writing custom recursive merge functions. Instead, use established libraries and keep them updated.
If you use utilities like lodash, ensure you are using
the latest version, as older versions had known prototype pollution
vulnerabilities in functions like _.defaultsDeep,
_.merge, and _.set.
If you must write a custom merge function, explicitly block the dangerous keys:
function safeMerge(target, source) {
for (let key in source) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // Skip dangerous keys
}
// Perform merge logic
}
return target;
}Keep Node.js and Dependencies Updated
Vulnerabilities are frequently discovered in popular npm packages. Regularly run vulnerability scanners to detect and patch prototype pollution risks in your dependency tree. Use the built-in npm tool:
npm auditFor automated continuous monitoring, integrate tools like Snyk or GitHub Dependabot into your CI/CD pipeline to catch vulnerable packages before they reach production.