In this lesson, you will learn how to defend your Node.js applications against malicious JSON payloads designed to inject properties into the Object.prototype and hijack application behavior.
Prototype pollution occurs when an attacker provides a JSON object containing keys like __proto__, constructor, or prototype. If your code performs a recursive merge or a "deep clone" operation on this user-controlled input, these keys can overwrite the base prototype shared by all objects in your runtime.
Consider this classic vulnerability pattern:
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
merge(target[key], source[key]); // Dangerous recursion
} else {
target[key] = source[key];
}
}
}
If an attacker sends { "__proto__": { "isAdmin": true } }, the merge function will inadvertently set isAdmin on Object.prototype. Suddenly, every object in your application—including ones that should be unprivileged—will report true for isAdmin.
The Defense: Structural Validation
To prevent this, you must treat incoming object keys as untrusted input. Before processing, you should enforce a strict "allow-list" approach or explicitly block dangerous keys. The most effective way to harden your parsing logic is to intercept these keys before they reach your merge or assignment functions:
const BANNED_KEYS = ['__proto__', 'constructor', 'prototype'];
function isSafeKey(key) {
return !BANNED_KEYS.includes(key);
}
function safeMerge(target, source) {
for (let key in source) {
if (!isSafeKey(key)) continue; // Drop the malicious key
if (typeof source[key] === 'object' && source[key] !== null) {
safeMerge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
}
By filtering the keys, you ensure that the internal prototype chain remains immutable regardless of what the external request contains. Additionally, using Object.create(null) for objects that act as dictionaries (where you don't need the default prototype methods) serves as a robust architectural defense, as these objects do not inherit from Object.prototype in the first place.
Key Takeaway: Never blindly merge user-supplied JSON into existing objects; always sanitize your input by stripping keys like __proto__, constructor, and prototype before processing.