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 extensible

2. 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.js

This 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: