Skip to content

Hardened JavaScript

What is Hardened JavaScript?

Hardened JavaScript is a standards track mode for the JavaScript language for safe plugin systems and supply chain attack resistance. Hardening JavaScript improves a program’s integrity in the face of adversarial code in the same process.

Mechanisms

Hardened JavaScript has three features: Lockdown, Compartments, and Harden.

  • Hardening an object (harden(object)) freezes it and every other object reachable by visiting prototypes and properties, making it safe to share with multiple parties. The object (or “capability”) is tamper-proof but not immutable or pure. That means none of the parties that hold the object can alter its methods to eavesdrop or interfere with other parties.

  • A Compartment (new Compartment()) is a sandbox with its own global object and evaluators (eval, Function, AsyncFunction, Compartment, and import). Unlike a same-origin iframe or V8 vm, all compartments have the same shared intrinsics like Array, Object, Date, and Math. Because these are the same for every compartment, Hardened JavaScript enjoys compatibility with the vast majority of JavaScript. Programs that rely on date instanceof Date work the same.

  • Lockdown patches up and hardens the shared intrinsics so they are safe to share with other parties and invulnerable to prototype pollution attacks. For example, after calling lockdown(), programs in any compartment can call new Function(code) to safely evaluate arbitrary code in the same compartment, but new Function.prototype.constructor(code) throws an error so it cannot evaluate code outside the compartment or access the true globalThis.

Examples

Lockdown

Calling Lockdown enters the Hardened JavaScript mode. Thereafter, the shared intrinsics are frozen.

lockdown();
console.log(Object.isFrozen([].__proto__));
// true

Lockdown does not erase any powerful objects from the initial global scope. Instead, Compartments give complete control over what powerful objects exist for client code.

Compartment

A compartment is a sandbox in which a program (a plugin or dependency) can execute but not escape. In the following example, we create a compartment endowed with a print() function on globalThis.

const c = new Compartment({
globals: harden({ print }),
});
c.evaluate(`print('Hello! Hello?');`);

Compatibility: XS and SES have not yet converged on the same signature for the Compartment constructor. XS supports the above pattern and until a future version, SES compartments accept the globals as their first argument.

import 'ses';
const c = new Compartment({
print: harden(console.log),
});
c.evaluate(`print('Hello! Hello?');`);

Harden

Harden gives all parties a foot to stand on to preserve the integrity of their objects and methods. Once hardened, an attacker can’t replace the methods of an object they share with another party.

lockdown();
let counter = 0;
const capability = harden({
inc() {
counter++;
},
});
console.log(Object.isFrozen(capability));
// true
console.log(Object.isFrozen(capability.inc));
// true

Although the surface of the object (capability) is frozen, the capability still closes over the mutable counter. Hardening an object graph makes the surface immutable, but does not guarantee that methods are free of side effects.

console.log(capability.inc()); // 0
console.log(capability.inc()); // 1
console.log(capability.inc()); // 2

Implementations

Applications

  • Agoric Logo Agoric uses Hardened JavaScript to confine smart contracts.

  • Moddable Logo Moddable uses Hardened JavaScript to confine programs on embedded devices.

  • LavaMoat Logo MetaMask uses Hardened JavaScript to defend its supply chain for its web extension, at build time and run time with LavaMoat.

  • MetaMask Logo MetaMask also uses Hardened JavaScript to confine its Snaps plugins.

Boundaries

Hardened JavaScript does not protect the availability of a program. Any party in the same realm, regardless of compartment isolation, can drop into an infinite loop and prevent all other parties from making progress. Hardened JavaScript combines well with carefully chosen process or worker boundaries.

Hardened JavaScript protects confidentiality by default by omitting timers and shared state between compartments. Each compartment’s global object has only certain hardened, shared intrinsics with other compartments, including Object, Array, Date, and Math, but lockdown ensures that new Date(), Date.now(), and Math.random() do not work. The compartment global object does not get any other properties from the host (web browser or Node.js) like performance. Without these features, a confined program can’t use timing side channels or observe that another party is drawing numbers from the Math pseudo-random number generator.

However, many confined programs will need timers and you (the host) can safely endow the compartments for a single party per process with timers, provided you keep no confidential information in the same process.