SES: Hardened JavaScript for Secure Execution
SES (Secure EcmaScript) is a JavaScript shim providing a hardened environment for robust security and fearless cooperation. It implements Hardened JavaScript, a subset of JavaScript proposed to ECMA TC39, designed to prevent prototype pollution and other common vulnerabilities. The current stable version, 2.0.0, focuses on plugging side-channel attacks and refining security guarantees. SES operates by 'locking down' the global environment, freezing intrinsic objects, and providing the `Compartment` constructor for creating isolated execution contexts. Each `Compartment` has its own global object and module system but shares hardened, immutable primordials with other compartments. This approach ensures that mutually suspicious code can interact safely via object-capability (ocap) principles, where powers are explicitly granted. The package maintains an active release cadence, with frequent updates across the broader `@endo` ecosystem. Key differentiators include its comprehensive protection against tampering with built-in objects, enforcement of strict mode, and its utility in sandboxing third-party code for applications like blockchain smart contracts and browser extensions, notably used by Agoric and MetaMask.
Common errors
-
TypeError: Cannot assign to read only property 'prototype' of object '#<Object>' (or similar 'Cannot assign to read only property' errors)
cause `lockdown()` has frozen JavaScript intrinsics and objects to prevent prototype pollution and tampering with the global environment.fixDo not attempt to modify built-in objects or their prototypes after `lockdown()` has been called. If you need to introduce polyfills or shims, they must be applied *before* `lockdown()`. -
ReferenceError: fetch is not defined (or 'document is not defined', 'process is not defined')
cause Code running inside a `Compartment` does not have access to ambient authority (global browser/Node.js APIs) by default, adhering to POLA.fixTo grant access to specific host APIs, pass them explicitly into the `Compartment`'s `globals` option. For example: `new Compartment({ globals: { fetch: harden(globalThis.fetch) } })`. Ensure any granted capabilities are `harden()`ed. -
Error: Code evaluation is forbidden for `new Function(...)` (or `eval(...)`) after the compartment is locked down.
cause `lockdown()` replaces the global `Function` constructor and `eval` with versions that throw errors when used to evaluate source code directly, as a security measure.fixUse the `Compartment.prototype.evaluate(code)` method to securely run code within a specific `Compartment`'s scope. For dynamic module loading, explore `Compartment.prototype.import()` or `Compartment`'s `moduleMapHook`.
Warnings
- breaking Version 2.0.0 includes a major fix for a NaN side-channel vulnerability in JavaScript. This change prevents the leakage of NaN bit encodings via shared TypedArray views of a common ArrayBuffer, which could potentially expose sensitive information. Users should upgrade to prevent this subtle information leak.
- gotcha Calling `lockdown()` irreversibly modifies the global JavaScript environment. It freezes all intrinsic objects and functions, preventing any further prototype pollution or global tampering. It must be called once and as early as possible in the application's lifecycle, before any untrusted code or potentially harmful shims are loaded.
- gotcha After `lockdown()` is invoked, the `Function` constructor and similar evaluators (e.g., `eval`, `setTimeout(string)`) become 'tamed' in the global realm, meaning they will throw an error when attempting to evaluate arbitrary source code. This is a critical security feature to prevent direct arbitrary code execution. Within a `Compartment`, its own `Function` constructor evaluates code only within that compartment's global scope.
- gotcha Compartments operate under the Principle of Least Authority (POLA). By default, a `Compartment` receives no ambient authority, meaning it does not automatically have access to host-provided APIs like `fetch`, `localStorage`, `console`, or `process`.
Install
-
npm install ses -
yarn add ses -
pnpm add ses
Imports
- lockdown
const lockdown = require('ses').lockdown;import { lockdown } from 'ses'; - Compartment
const Compartment = require('ses').Compartment;import { Compartment } from 'ses'; - harden
const harden = require('ses').harden;import { harden } from 'ses'; - Global Lockdown
require('ses'); // Only imports, does not automatically call lockdownimport 'ses'; // Immediately hardens the global environment lockdown(); // Explicitly calls the global lockdown function
Quickstart
import { lockdown, Compartment, harden } from 'ses';
// Step 1: Lock down the global environment to prevent tampering.
// This should be done as early as possible in your application's lifecycle.
lockdown();
console.log('Global environment locked down.');
// Step 2: Create a new Compartment for secure execution of untrusted code.
// Compartments are isolated and by default have no ambient authority (e.g., no `fetch`).
const untrustedCompartment = new Compartment({
globals: {
// Grant specific global capabilities to the compartment.
log: harden(console.log),
greet: harden((name: string) => `Hello, ${name} from compartment!\n`),
},
});
// Step 3: Evaluate untrusted code within the compartment.
// The code only has access to its explicit globals and hardened intrinsics.
const untrustedCode = `
try {
log(greet('World'));
// Attempting to access unauthorized globals will fail.
// console.error('This should not be accessible.');
// new Function('return this')().alert('Attempted to access window!');
log('Attempting to modify Object.prototype...');
Object.prototype.evil = 'muahaha'; // This will fail due to lockdown
} catch (e: any) {
log('Caught expected error: ' + e.message);
}
const func = new Function('return 1 + 1;'); // This will throw after lockdown
`;
try {
untrustedCompartment.evaluate(untrustedCode);
} catch (e: any) {
console.error('Error evaluating untrusted code:', e.message);
}
// Verify global prototype pollution did not occur in the main realm
if (Object.prototype.hasOwnProperty('evil')) {
console.error('Prototype pollution detected in main realm!');
} else {
console.log('Main realm is still secure.');
}