@stevenleep/sandbox
TypeScript icon, indicating that this package has built-in type declarations

1.0.1 • Public • Published

@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

Package Sidebar

Install

npm i @stevenleep/sandbox

Weekly Downloads

98

Version

1.0.1

License

none

Unpacked Size

185 kB

Total Files

11

Last publish

Collaborators

  • stevenleep