How to Freeze Object.prototype Against Pollution
Prototype pollution is a severe JavaScript vulnerability that occurs
when malicious actors inject or modify properties on the global base
object prototype, potentially leading to remote code execution, denial
of service, or logic bypasses. This guide outlines the key techniques
developers can use to freeze and protect Object.prototype
against tampering, highlighting direct API methods, runtime flags, and
complementary defensive coding practices.
1. Direct Freezing with
Object.freeze()
The most direct way to secure Object.prototype is by
calling Object.freeze() on it at the earliest entry point
of your application. Freezing prevents new properties from being added,
existing properties from being removed, and existing property values or
descriptors from being changed.
// Execute this at the very beginning of the application lifecycle
Object.freeze(Object.prototype);Once frozen, attempts to modify or assign new properties to the
prototype will fail silently in non-strict mode or throw a
TypeError in strict mode:
"use strict";
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
// Throws: TypeError: Cannot add property isAdmin, object is not extensible2. Deep Freezing Built-In Prototypes
Securing Object.prototype alone may leave other built-in
prototype chains exposed, such as Array.prototype or
Function.prototype. To ensure complete protection, freeze
all core built-in prototypes:
const corePrototypes = [
Object.prototype,
Array.prototype,
Function.prototype,
String.prototype,
Number.prototype,
Boolean.prototype,
Promise.prototype
];
corePrototypes.forEach(proto => Object.freeze(proto));3. Using Node.js
--frozen-intrinsics Flag
If you are running a Node.js application (version 12+), you can
leverage the built-in experimental runtime flag
--frozen-intrinsics. This flag automatically and
recursively freezes all built-in JavaScript objects and prototypes,
including Object.prototype, before executing your
application code.
Run your application using:
node --frozen-intrinsics index.jsThis removes the need to manually invoke Object.freeze()
across individual objects, ensuring comprehensive coverage from
startup.
4.
Restricting Modifications with
Object.preventExtensions()
If full freezing causes compatibility issues with libraries that
modify existing descriptors without adding new properties, you can
prevent additions using Object.preventExtensions():
Object.preventExtensions(Object.prototype);While Object.preventExtensions() prevents attackers from
injecting new properties (such as
Object.prototype.isAdmin = true), it does not prevent the
modification of existing writable properties like
Object.prototype.toString. Therefore,
Object.freeze() remains the preferred security option.
5. Complementary Defensive Patterns
Freezing Object.prototype can occasionally conflict with
third-party libraries that rely on prototype polyfills or
monkey-patching. In scenarios where freezing the global prototype is
impractical, apply alternative defensive patterns:
Prototype-less Objects: Create objects with no prototype inheritance using
Object.create(null).const safeMap = Object.create(null);Use
MapCollections: Use standardMaporSetinstances instead of plain objects when handling key-value data from user input.Key Sanitization: Explicitly validate or strip dangerous keys such as
__proto__,constructor, andprototypebefore performing recursive merge or clone operations.