JSPM

@stevenleep/sandbox

1.0.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 93
  • Score
    100M100P100Q7380F

A powerful JavaScript sandbox library that provides multiple sandbox implementation options for safely executing untrusted code in browser environments.

Package Exports

  • @stevenleep/sandbox
  • @stevenleep/sandbox/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@stevenleep/sandbox) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@stevenleep/sandbox

A powerful JavaScript sandbox library that provides multiple sandbox implementation options for safely executing untrusted code in browser environments. Perfect for micro-frontends and applications that need to run third-party code securely.

Features

  • Multiple sandbox implementations:

    • ProxySandbox: Based on Proxy to intercept global object operations
    • WithEvalSandbox: Based on with+eval to execute code, isolating global variables
    • ShadowRealmSandbox: Based on the new ShadowRealm API (with polyfill and fallback options)
    • SnapshotSandbox: Based on snapshots to save and restore global state
  • Enhanced Security:

    • Complete isolation from global objects
    • Strict mode execution by default
    • Prevention of prototype pollution attacks
    • Code validation to detect potentially dangerous patterns
    • Blacklisting of sensitive APIs
    • Control over network access
  • Resource Management:

    • Execution timeouts to prevent infinite loops
    • Memory usage monitoring
    • Performance measurement
    • Automatic cleanup of resources
  • Advanced Features:

    • Support for asynchronous code execution (async/await)
    • ES module loading support
    • Event listener management to prevent memory leaks
    • Enhanced error handling with detailed information
    • Symbol handling, including Symbol.unscopables
    • Tiered implementation approach with graceful fallback

Installation

npm install @stevenleep/sandbox

Basic Usage

import { createSandbox, SandboxType, getBestSandboxType } from '@stevenleep/sandbox';

// Auto-detect best sandbox implementation for current environment
const bestSandboxType = getBestSandboxType();
const sandbox = createSandbox(bestSandboxType, {
  name: 'my-sandbox'
});

// Or explicitly select a sandbox type
const proxySandbox = createSandbox(SandboxType.Proxy, {
  name: 'proxy-sandbox',
  strictMode: true
});

// Activate the sandbox
sandbox.activate();

// Execute code in the sandbox
const result = sandbox.execScript(`
  const message = 'Hello from sandbox!';
  console.log(message);
  
  // Won't affect the global environment
  window.foo = 'bar';
  
  // Return value
  message;
`);

console.log(result); // "Hello from sandbox!"
console.log(window.foo); // undefined - the property is contained within the sandbox

// When finished
sandbox.deactivate();
sandbox.destroy(); // Clean up any resources

ShadowRealm Implementation

The ShadowRealmSandbox provides the most secure isolation and uses a tiered implementation approach:

  1. Native ShadowRealm API (if available in the browser)
  2. ShadowRealm polyfill (automatically used if native implementation is not available)
  3. Iframe-based fallback (as last resort if polyfill fails)
import { ShadowRealmSandbox, isShadowRealmSupported } from '@stevenleep/sandbox';

// Check if ShadowRealm is supported (either native or via polyfill)
console.log(`ShadowRealm is ${isShadowRealmSupported() ? 'supported' : 'not supported'}`);

// Create a ShadowRealm sandbox
const sandbox = new ShadowRealmSandbox({
  name: 'shadowrealm-sandbox',
  initScript: 'console.log("Sandbox initialized")'
});

// The implementation will automatically choose the best available option:
// - Native ShadowRealm if supported by the browser
// - ShadowRealm polyfill if native is not available
// - Iframe fallback as last resort

// Execute code - this works the same regardless of which implementation is used
sandbox.execScript(`
  // This code runs in complete isolation
  const privateData = { secret: 'safe' };
  
  // Only explicitly exported values are accessible outside
  globalThis.exposedValue = 'exposed';
`);

// Access exported values
const value = await sandbox.execScriptAsync(`globalThis.exposedValue`);
console.log(value); // "exposed"

// Load an ES module
const module = await sandbox.loadModule('https://example.com/module.js');

Proxy Sandbox

The ProxySandbox provides a good balance of performance and complete isolation:

import { ProxySandbox } from '@stevenleep/sandbox';

const sandbox = new ProxySandbox({
  name: 'proxy-sandbox',
  strictMode: true, // IMPORTANT: Always set to true for proper isolation
  blacklist: ['localStorage', 'sessionStorage'],
  whitelist: ['console', 'setTimeout']
});

sandbox.activate();

// Variables defined in the sandbox stay in the sandbox
sandbox.execScript(`
  // This will be contained within the sandbox
  window.testValue = 'sandbox value';
  
  // This logs the sandboxed value
  console.log(window.testValue); // 'sandbox value'
  
  // This will log a warning and not be modified due to blacklist
  localStorage.setItem('test', 'value');
`);

// Variables defined in the sandbox are NOT accessible from outside
console.log(window.testValue); // undefined - completely isolated

sandbox.destroy();

WithEval Sandbox

The WithEvalSandbox provides simple global variable isolation:

import { WithEvalSandbox } from '@stevenleep/sandbox';

const sandbox = new WithEvalSandbox({
  name: 'eval-sandbox',
  globals: {
    // Custom globals to inject
    customAPI: {
      getData: () => 'Custom data'
    }
  }
});

sandbox.activate();

const result = sandbox.execScript(`
  // Access custom global
  const data = customAPI.getData();
  
  // Return the data
  data;
`);

console.log(result); // "Custom data"

sandbox.destroy();

ShadowRealm Sandbox

The ShadowRealmSandbox uses the new ShadowRealm API (with polyfill and iframe fallback) to provide complete isolation:

import { ShadowRealmSandbox } from '@stevenleep/sandbox';

const sandbox = new ShadowRealmSandbox({
  name: 'shadowrealm-sandbox',
  // Force using iframe fallback for compatibility
  forceIframe: true,
  // Set execution timeout
  timeLimit: 1000,
  // Security configuration
  security: {
    preventSensitiveAPIs: true,
    allowNetwork: false
  },
  // Module options if needed
  moduleOptions: {
    baseURL: 'https://example.com/',
    trustedSources: ['https://trusted-cdn.com/']
  }
});

sandbox.activate();

// Execute code in complete isolation
sandbox.execScript(`
  // Code runs in completely separate environment
  const secretData = 'This is isolated';
  
  // Define a function we'll export
  function calculateResult(input) {
    return input * 2;
  }
`);

// Export a function from main environment to sandbox
sandbox.exportFunction('multiplyByThree', (value) => value * 3);

// Use the exported function in sandbox
const result = sandbox.execScript(`
  // Use the function exported from main environment
  const result = multiplyByThree(10);
  return result;
`);
console.log(result); // 30

// Import a function from sandbox
const calculate = sandbox.importFunction('calculateResult');
console.log(calculate(5)); // 10

// Async execution with timeout
const asyncResult = await sandbox.execScriptAsync(`
  return new Promise(resolve => {
    setTimeout(() => resolve('Completed'), 100);
  });
`, 500);

sandbox.destroy();

Snapshot Sandbox

The SnapshotSandbox captures and restores window state:

import { SnapshotSandbox } from '@stevenleep/sandbox';

const sandbox = new SnapshotSandbox({
  name: 'snapshot-sandbox'
});

// Save current window state
sandbox.activate();

// Run code that modifies window
sandbox.execScript(`
  window.modifiedValue = 'modified';
`);

console.log(window.modifiedValue); // "modified"

// Restore original window state
sandbox.deactivate();

console.log(window.modifiedValue); // undefined - window restored

Security Considerations

  • ShadowRealmSandbox: Provides the strongest isolation

    • Native implementation offers true realm-based isolation
    • Polyfill provides excellent compatibility across browsers
    • Even the iframe fallback gives strong isolation guarantees
    • Best choice for executing untrusted code
  • ProxySandbox: Offers a good balance between isolation and compatibility

    • IMPORTANT: Always set strictMode: true for complete isolation
    • With proper configuration, provides complete isolation of window modifications
    • Sandbox variables remain in the sandbox and do not leak to global scope
    • Uses an isolated object model separate from the global window
    • Good choice for semi-trusted code when ShadowRealm is not available
  • WithEvalSandbox: Provides limited isolation

    • Mainly isolates global variables, not full security boundary
    • Good for code organization more than security
    • Not recommended for untrusted code
  • SnapshotSandbox: Focuses on state restoration rather than isolation

    • Useful for temporarily modifying window state
    • Not a security boundary for untrusted code
    • Good for isolation between trusted components
  • General Security Tips:

    • Always sanitize inputs before passing to any sandbox
    • Validate outputs returned from sandbox execution
    • Apply Content Security Policy (CSP) restrictions
    • Consider using a web worker for additional isolation
    • Limit execution time with timeouts
    • Restrict access to sensitive APIs through blacklists

Ensuring Complete Isolation

For maximum security, use the following patterns:

import { createSandbox, getBestSandboxType } from '@stevenleep/sandbox';

// Always use the best available sandbox type with strictMode enabled
const sandbox = createSandbox(getBestSandboxType(), {
  name: 'secure-sandbox',
  strictMode: true,  // Critical for isolation
  blacklist: [
    // Restrict access to sensitive APIs
    'localStorage', 'sessionStorage', 'indexedDB', 
    'WebSocket', 'XMLHttpRequest', 'fetch',
    // Prevent DOM access
    'document', 'navigator', 'location'
  ]
});

sandbox.activate();

// Variables defined inside stay inside
sandbox.execScript(`
  window.secretData = "This stays in the sandbox";
`);

// Global window is completely unmodified
console.log(window.secretData); // undefined

sandbox.destroy();

Advanced Usage

Performance Measurement

import { performanceMeasure } from '@stevenleep/sandbox';

const { start, end, measure } = performanceMeasure('code-execution');

start();
// Code to measure
const result = heavyComputation();
const duration = end();

console.log(`Execution took ${duration}ms`);

Error Handling

import { wrapExecution, formatError } from '@stevenleep/sandbox';

const result = wrapExecution(() => {
  // Potentially dangerous code
  return riskyOperation();
});

if (result.error) {
  console.error('Error occurred:', formatError(result.error));
} else {
  console.log('Success:', result.value);
}

License

MIT