« All posts

Why Node.js Can't Be Hardened Against Prototype Pollution

Node.js security reports on prototype pollution gadgets are symptoms, not bugs. The real fix belongs at the application boundary, not in core.

Security reports about Node.js keep following the same pattern: a 'gadget' is found where code reads a property it expected to be absent and does something dangerous with it — in core or three dependencies deep. But every such report shares a hidden precondition: the application already has a prototype pollution. Once that's true, the gadget isn't the vulnerability, it's a symptom, and there are infinitely more behind it across core and every dependency. Hardening one closes nothing.

JavaScript reads through the prototype chain everywhere, not just via method calls but through destructuring, spread, for...of, and thenable resolution. Every object literal core creates still inherits from Object.prototype. Truly hardening core would mean null-prototyping every allocated object, avoiding every prototype-touching syntax, and forcing all dependencies to do the same — that's not hardening, it's a different language. Even primordials, which capture original built-ins to survive monkey-patching, don't solve this: they preserve Node's integrity, not your application's safety.

That's why Node.js does not accept prototype pollution in core as a security vulnerability. The real fix belongs at the boundary — the exact point untrusted bytes become objects. JSON.parse alone doesn't pollute anything; the damage happens when a recursive merge or Object.assign promotes a __proto__ key onto the real prototype chain. This is why Fastify runs secure-json-parse by default, rejecting __proto__ and constructor.prototype writes before they exist. If an attacker can write to your prototypes, the game is already over — your job is making sure they never get that write.