vm2 safely sandboxes untrusted JavaScript code running in Node.js. This is a security-critical project -- every change must be evaluated for sandbox escape potential.
Exported from lib/main.js via index.js:
VM-- isolated context for synchronous execution withoutrequire.NodeVM-- sandbox emulating Node's module loader with fine-grainedrequirecontrols.VMScript-- compilation, caching, and compiler selection wrapper.VMFileSystem-- controls sandbox filesystem access.VMError-- raised when sandbox policy is violated.
| File | Role |
|---|---|
lib/bridge.js |
Core proxy layer. Paired proxies for every value crossing host/sandbox. WeakMap caches for identity. Proxy trap handlers (BaseHandler, ProtectedHandler, ReadOnlyHandler). |
lib/setup-sandbox.js |
Sandbox bootstrap for VM. handleException, Promise wrapping, Symbol.for override, symbol filtering, Error.prepareStackTrace safe default, WebAssembly.JSTag deletion. |
lib/setup-node-sandbox.js |
Sandbox bootstrap for NodeVM. require, module resolution, console redirection. |
lib/transformer.js |
Acorn parser instrumenting catch blocks and with statements. Limitation: ecmaVersion: 2022 -- post-ES2022 syntax (e.g., using) is invisible. |
| File | Role |
|---|---|
lib/vm.js |
VM class. Compiles via vm.Script, applies transformer, enforces timeout/async. |
lib/nodevm.js |
NodeVM class. Module loader, external module access, console redirection. |
lib/script.js |
VMScript. Options, compilers, caching, async detection. |
lib/compiler.js |
Compiler resolution (JS passthrough, optional CoffeeScript/TypeScript). |
lib/filesystem.js |
VMFileSystem implementation. |
lib/resolver.js |
Module resolution for NodeVM. |
lib/events.js |
Sandbox-safe copy of Node's events. |
npx vm2 path/to/script.js # NodeVM with external modules, verbose loggingSee docs/ATTACKS.md for the full catalog of attack patterns, fundamentals of realm separation and V8 internals, and the defense table.
Key insight: V8 internal algorithms (ArraySpeciesCreate, FormatStackTrace, PromiseResolveThenableJob) operate on raw objects, bypassing proxy traps. Defenses must neutralize raw objects directly, not just intercept via proxies.
- Never expose host constructors or prototypes.
- Rebound every function crossing the bridge to the destination realm.
- Freeze or proxy shared mutable state.
- Filter property access via
Object.getOwnPropertyDescriptor. Neverfor...inor spread on host objects. - Treat new symbols and language features as suspect until vetted.
- Cache
Reflect.*and other critical references at init time.
- Does this expose any new return path for host objects?
- Can sandbox code call this method directly (not through a proxy)?
- Does this accept attacker-controlled parameters?
- Are all
Reflect.*calls using cached references? - Could V8 internal algorithms trigger this path (bypassing traps)?
- Does this handle host-realm errors that could be thrown?
- Are there new well-known symbols that need filtering?
npm test # Main suite (Mocha)
npm run test:compilers # Optional (needs typescript, coffee-script)
npm run lint # ESLinttest/vm.js-- Main sandbox tests. UsesmakeHelpers()for proxy boundary checks,it.cond(name, condition, fn)for version-gated tests.test/nodevm.js-- NodeVM module loading tests.test/escape-scanner.js-- Automated escape scanner. Serializable, runs insidevm.run().
Every security fix must include tests that reproduce the attack, verify the defense, and test bypass variants. Use it.cond() for Node version requirements.
Use the /hacker skill after making security-related changes to systematically red-team the sandbox and verify it still holds.
Every time the library is patched, update docs/ATTACKS.md:
- Add the attack to the appropriate tier/category (or create a new one).
- Document: attack flow, canonical example, why it works, mitigation, detection rules.
- Update the Compound Attack Patterns and "How The Bridge Defends" table.
- Add new APIs/features to "Considered Attack Surfaces" or "Future Risks".
- Keep changes small and focused. Security-sensitive code discourages large refactors.
- Include threat model reasoning and escape-prevention tests for boundary changes.
- Follow existing ESLint code style.
- Vulnerabilities: follow
SECURITY.md, not public issues.