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.
-
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
npm install @stevenleep/sandbox
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
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');
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();
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();
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();
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
-
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
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();
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`);
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);
}
MIT