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:
- Native ShadowRealm API (if available in the browser)
- ShadowRealm polyfill (automatically used if native implementation is not available)
- 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
- IMPORTANT: Always set
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