ASVS 5.0 V15.3.6 requires JavaScript to be written so prototype pollution cannot happen, for example by using Set or Map instead of plain object literals as dictionaries. It is Level 2. The short version: never merge or assign untrusted keys into ordinary objects without filtering, and prefer structures that have no prototype chain to poison.
Why it matters
Every JavaScript object inherits from Object.prototype. If an attacker can write to a key named __proto__ or constructor.prototype, they change the default behavior of every object in the process. Effects range from flipped authorization checks (isAdmin suddenly present on everything) to denial of service and, in some template or option handling code, code execution.
The usual entry point is a deep merge, clone or "set by path" helper fed with parsed JSON or a query string.
How to implement it
Use a Map for key value data whose keys come from users:
const counts = new Map();
function hit(name) {
counts.set(name, (counts.get(name) ?? 0) + 1);
}
If you need an object, create it without a prototype:
const dict = Object.create(null);
dict[userKey] = value; // "__proto__" is just an ordinary key here
For merges, block dangerous keys and use own properties only:
const FORBIDDEN = new Set(["__proto__", "constructor", "prototype"]);
function safeMerge(target, source) {
for (const key of Object.keys(source)) {
if (FORBIDDEN.has(key)) continue;
const v = source[key];
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
target[key] = safeMerge(
Object.hasOwn(target, key) && typeof target[key] === "object" ? target[key] : {},
v
);
} else {
target[key] = v;
}
}
return target;
}
Two more defenses help. Run Node with --disable-proto=delete where compatible, and freeze the prototype at startup with Object.freeze(Object.prototype) after testing that your dependencies tolerate it. Keep merge, clone and path setting libraries updated, because most real world cases were vulnerabilities in those packages. A dependency scan, such as the free one at https://gold.safeguard.sh/scan, shows which versions you ship.
How to test it
- Find every deep merge, clone, extend and path assignment call that touches request data.
- Send a body like
{"__proto__": {"polluted": true}}and{"constructor": {"prototype": {"polluted": true}}}. - In a test, afterward check that
({}).pollutedis undefined. - Repeat with query strings, for example
a[__proto__][polluted]=1against parsers that build nested objects. - Add the check as an automated regression test.
Common mistakes
- Filtering only
__proto__and forgettingconstructor.prototype. - Fixing your own helper while a vulnerable library version still merges input.
- Testing only top level keys when the pollution works at depth.
- Checking
ininstead of own property tests, which sees inherited keys.
Quick check
Does JSON.parse create a prototype pollution problem by itself?
It creates an own property named __proto__ and does not pollute. The danger appears when a later merge copies that key onto another object.
Which level is this requirement?
Level 2.