# @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

```bash
npm install @stevenleep/sandbox
```

## Basic Usage

```javascript
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)

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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

```javascript
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

```javascript
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
