ZeroPath at Black Hat USA 2026

vm2 Sandbox Escape via Builtin Denylist Bypass: Overview of CVE-2026-47140 (CVSS 10.0)

A brief summary of CVE-2026-47140, a CVSS 10.0 sandbox escape in the vm2 Node.js library caused by incomplete denylist coverage of the process and inspector/promises builtins. Includes patch details, detection methods, and context on the broader 2026 vm2 CVE wave.

CVE Analysis

12 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-06-12

vm2 Sandbox Escape via Builtin Denylist Bypass: Overview of CVE-2026-47140 (CVSS 10.0)
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

The vm2 Node.js sandboxing library's builtin denylist has been bypassed again, this time through two overlooked modules that grant sandboxed code direct access to host process execution primitives. CVE-2026-47140 carries a CVSS 10.0 score and represents the fifth critical sandbox escape patched in the vm2 3.11.x release series during 2026, reinforcing a pattern that calls into question the viability of denylist based sandboxing for untrusted code execution.

vm2 is an open source sandbox library for Node.js, maintained by Patrik Simek, with approximately 4,100 GitHub stars and broad adoption across the Node.js ecosystem. It is used to run untrusted JavaScript with controlled access to Node.js built-in modules, positioning itself as a more secure alternative to the native vm module. Its relevance has grown significantly as AI agent frameworks have adopted it to execute model generated code in ostensibly isolated environments.

Technical Information

Root Cause: Exact Match Denylist with Incomplete Coverage

The vulnerability lives in lib/builtin.js, where NodeVM maintains a DANGEROUS_BUILTINS Set that determines which Node.js core modules are blocked from sandboxed code. Prior to the patch, this set contained: module, worker_threads, cluster, vm, repl, inspector, trace_events, and wasi. The enforcement logic used DANGEROUS_BUILTINS.has(key), a simple exact string match against the requested module name.

This design fails in two distinct ways:

Gap 1: process is entirely absent from the denylist. The process module was never added to DANGEROUS_BUILTINS. On Node.js 22 and later, the process object exposes getBuiltinModule(name), a function that can load any core module by name regardless of the embedder's allow or deny configuration. This means sandboxed code that can require("process") can then call process.getBuiltinModule("child_process") to obtain a reference to child_process, a module that would normally be blocked. From there, calling execFileSync, exec, or spawn provides arbitrary OS command execution on the host.

Beyond getBuiltinModule(), the process module also exposes binding(), dlopen(), and the host process.env, each of which provides additional avenues for sandbox escape or sensitive data exfiltration.

Gap 2: inspector/promises is not matched by the inspector denylist entry. While inspector is present in DANGEROUS_BUILTINS, the subpath builtin inspector/promises is a distinct module identifier. The exact match DANGEROUS_BUILTINS.has("inspector/promises") returns false because only the string "inspector" is in the Set. The inspector/promises module provides a Promise based API to the V8 Inspector protocol. An attacker can instantiate a Session, connect it, and issue a Runtime.evaluate call to execute arbitrary JavaScript in the host process context, completely outside the sandbox boundary.

Vulnerability Classification

CVE-2026-47140 is classified under CWE-693: Protection Mechanism Failure, defined by MITRE as occurring when "the product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product." The CVSS v3.1 vector is AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H, reflecting unauthenticated remote exploitation with low complexity, changed scope, and full impact across confidentiality, integrity, and availability. The CVSS v4.0 score from cybersecurity-help.cz is 9.3.

Attack Flow

The exploitation path requires that the target application's NodeVM instance has require enabled and its require.builtin configuration includes process, inspector/promises, or the wildcard "*". The default NodeVM configuration (where require is disabled or no affected builtins are allowed) is not vulnerable.

Path 1: process.getBuiltinModule pivot

  1. Attacker supplies malicious JavaScript to be executed inside the vm2 sandbox.
  2. The code calls require("process"), which succeeds because process is not in the denylist.
  3. The code calls .getBuiltinModule("child_process") on the returned process object, obtaining a reference to the host's child_process module.
  4. The code calls .execFileSync("hostname") (or any other command), executing it on the host operating system.
  5. The command output is returned to the sandboxed code, confirming full host command execution.

Path 2: inspector/promises Runtime.evaluate

  1. Attacker supplies malicious JavaScript to be executed inside the vm2 sandbox.
  2. The code calls require("inspector/promises"), which succeeds because the exact string does not match the inspector denylist entry.
  3. The code creates a new Session(), connects it, and calls .post("Runtime.evaluate", ...) with arbitrary JavaScript.
  4. The V8 Inspector evaluates the JavaScript in the host realm, outside the sandbox.

Both paths were validated in a proof of concept file (dangerous-builtin-denylist-rce.js) tested on vm2 3.11.2 and Node.js v25.9.0, as documented in the GitHub advisory.

The AI Agent Dimension

Microsoft Security Research framed the structural risk of vm2 sandbox escapes in May 2026 as "Prompts Become Shells." When AI agent frameworks use vm2 to execute code generated by language models, a prompt injection attack becomes a direct path to host RCE. The attacker does not need direct access to the application; they only need to influence the model's output to include one of the exploit paths above. Kodem Security's analysis specifically warns that the 2026 vm2 CVE wave turns AI agents into host RCE vectors.

Patch Information

A public patch for CVE-2026-47140 was committed by maintainer Patrik Simek on May 17, 2026 (commit a1ed47a) and shipped in vm2 version 3.11.4. The fix lives entirely within lib/builtin.js and addresses the root cause with two structural changes.

1. process added to the denylist. The DANGEROUS_BUILTINS Set now includes process alongside the previously blocked names (module, worker_threads, cluster, vm, repl, inspector, trace_events, wasi). This prevents sandboxed code from loading the raw host process module, which exposes getBuiltinModule(), binding(), dlopen(), and the host process.env.

2. Exact match replaced with family prefix matching. A new helper function isDangerousBuiltin(key) replaces every prior DANGEROUS_BUILTINS.has(key) call:

function isDangerousBuiltin(key) { if (typeof key !== 'string') return false; if (key.startsWith('node:')) key = key.slice(5); if (DANGEROUS_BUILTINS.has(key)) return true; const slash = key.indexOf('/'); if (slash > 0 && DANGEROUS_BUILTINS.has(key.slice(0, slash))) return true; return false; }

This is the heart of the fix. Rather than checking whether the exact string appears in the Set, the function first strips the optional node: URL style prefix (so node:process and node:inspector/promises cannot evade the check via an alternate spelling). It then checks the string for a / character; if one exists, it extracts everything before the slash (the "family name") and checks that against the denylist. This means inspector/promises, any future inspector/foo, or hypothetical process/bar subpath builtins are automatically blocked because their family name is in the dangerous set.

The function is enforced at two critical chokepoints inside lib/builtin.js:

At the BUILTIN_MODULES array construction, where Node's builtinModules list is filtered before the '*' wildcard can expand to them:

const BUILTIN_MODULES = (nmod.builtinModules || ...) .filter(s => !s.startsWith('internal/') && !isDangerousBuiltin(s));

And at the addDefaultBuiltin() function, the gate that handles explicit allowlist entries (e.g., builtin: ['process']) and the lower level makeBuiltins() API:

if (!special && isDangerousBuiltin(key)) return;

This dual enforcement ensures that dangerous builtins cannot leak into the sandbox regardless of whether the embedder uses the '*' wildcard expansion path or explicitly names a dangerous module. The SPECIAL_MODULES / mocks / overrides escape hatches are intentionally preserved so embedders who genuinely need a stub can register a sandbox safe replacement.

The commit also adds a comprehensive 175 line test suite (test/ghsa/GHSA-rp36-8xq3-r6c4/repro.js) that validates all blocked spellings (process, node:process, inspector/promises, node:inspector/promises), confirms the getBuiltinModule pivot to child_process is blocked, confirms the inspector/promises Session Runtime.evaluate path is blocked, verifies the explicit allowlist path is also rejected, and includes a regression guard ensuring safe subpath builtins like fs/promises, dns/promises, and stream/promises continue to load normally under '*'.

To upgrade:

npm install [email protected]

Version 3.11.5, published on May 18, 2026, is the latest available and includes additional fixes.

Detection Methods

Detecting exploitation or exposure to CVE-2026-47140 requires a layered approach combining dependency scanning, runtime behavioral monitoring, configuration auditing, and SIEM based threat detection. Because this vulnerability operates entirely within the Node.js runtime (sandboxed JavaScript escaping to the host process), there are no traditional network layer signatures or file based indicators. Detection efforts should focus on identifying vulnerable installations, spotting the specific bypass patterns, and catching post exploitation behaviors.

Dependency and Version Scanning

The most immediate detection method is identifying whether your environment runs a vulnerable version of the vm2 npm package. All versions through and including 3.11.3 are affected. Multiple SCA platforms have integrated this CVE:

  • Snyk tracks it as SNYK-JS-VM2-17111172, classifying it as "Incomplete List of Disallowed Inputs" with a CVSS v4.0 score of 9.4 and an EPSS probability of 0.13% (34th percentile).
  • GitHub Advisory Database catalogs it under GHSA-rp36-8xq3-r6c4, enabling automatic Dependabot alerts and pull requests for repositories with a direct or transitive vm2 dependency.
  • Endor Labs, Tenable, GitLab Advisory Database, and Vulners all independently index CVE-2026-47140.

A quick local check can be performed by running npm ls vm2 in project directories to determine whether any dependency tree includes a pre-3.11.4 release.

Configuration Level Detection

Not every vm2 installation is exploitable by this specific CVE. The advisory explicitly states this is "not reachable with the default NodeVM configuration where require is disabled or no affected builtins are allowed." Exploitation requires that the NodeVM instance's require.builtin option either includes the wildcard "*" or explicitly allows process or inspector/promises. Auditing your NodeVM initialization code for these configurations is a targeted detection step that can help prioritize remediation.

Runtime Behavioral Detection

Kodem Security's research on the 2026 vm2 sandbox escape wave provides actionable behavioral indicators that apply directly to CVE-2026-47140 exploitation:

  • Process indicators: Watch for node processes spawning unexpected child processes, particularly child_process.exec or child_process.spawn calls originating from contexts that should be sandboxed. Shell invocations (sh, bash, cmd.exe) parented to a Node.js runtime that should not be spawning them are strong indicators.
  • Filesystem indicators: Post escape code commonly targets sensitive credentials. Monitor for reads of ~/.aws/credentials, ~/.npmrc, ~/.docker/config.json, /proc/self/environ, and any environment variable enumeration from within an agent or sandbox runtime.
  • Network indicators: Outbound DNS lookups or HTTPS connections from a sandboxed agent runtime to domains or IPs not on an application allowlist are suspicious, especially in AI agent frameworks and CI/CD runners where vm2 isolates LLM generated code.

As Kodem notes, "no malware hashes, C2 domains, or named binaries are available for the 2026 vm2 CVE wave." These are vulnerability disclosures from researchers rather than observed campaign reports with traditional IOCs.

SIEM and Detection Platform Integration

The Clankerusecase threat led detection library has cataloged CVE-2026-47140 and produces continuously updated detection queries across six platforms: Splunk SPL, Microsoft Sentinel KQL, Microsoft Defender KQL, Sigma rules, Datadog Cloud SIEM, and CrowdStrike Falcon LogScale. Each detection is mapped to MITRE ATT&CK techniques and kill chain phases. SOC teams can pull these queries to operationalize detection in their existing SIEM environments.

Detection Gaps

A key insight from both Kodem and Snyk is that conventional SCA tools detect that vm2 is installed and flag the CVE, but they do not determine whether the runtime configuration actually exposes the vulnerable code path. An application using vm2 with restrictive require.builtin settings carries a fundamentally different risk profile than one using the wildcard "*". Runtime application detection and response (ADR) tooling, or manual configuration auditing, is required to close this gap and prioritize remediation accurately.

Affected Systems and Versions

All versions of the vm2 npm package up to and including 3.11.3 are affected. The vulnerability is fully patched in version 3.11.4.

The vulnerability is only exploitable when the NodeVM configuration meets one of these conditions:

  • require is enabled and require.builtin includes the wildcard "*"
  • require is enabled and require.builtin explicitly includes process
  • require is enabled and require.builtin explicitly includes inspector/promises

The default NodeVM configuration, where require is disabled or no affected builtins are allowed, is not vulnerable to this specific CVE.

The proof of concept was validated on vm2 3.11.2 running on Node.js v25.9.0. The process.getBuiltinModule() exploit path specifically requires Node.js 22 or later, where that API was introduced.

Vendor Security History

vm2 has a documented and recurring pattern of critical sandbox escape vulnerabilities, all sharing the CWE-693 classification:

YearCVECVSSBypass MechanismPatched Version
2023CVE-2023-32314CriticalSandbox escapeN/A
2026CVE-2026-2270910.0Promise callback bypass3.11.0
2026CVE-2026-2478110.0inspect function breakout3.11.0
2026CVE-2026-2695610.0Sandbox escape (confirmed on 3.10.4)3.11.x
2026CVE-2026-4400010.0Sandbox escape RCE3.11.x
2026CVE-2026-4714010.0process/inspector/promises denylist bypass3.11.4

Kodem Security reported a total of 13 vm2 sandbox escape vulnerabilities in the 2026 wave. The compounding nature of these findings, five CVSS 10.0 escapes patched across the 3.11.x series, suggests the denylist based sandbox model itself is the structural weakness rather than any individual implementation oversight.

The maintainer has been responsive, releasing patches as drop in replacements with no API changes. The commit for CVE-2026-47140 introduces a structural improvement (family prefix matching via isDangerousBuiltin()) rather than just adding individual entries, suggesting an attempt to address the root cause pattern. However, any future Node.js builtin or subpath not covered by the DANGEROUS_BUILTINS family set will create a new escape vector under the current architecture.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Jira
  • Linear
  • Slack
  • Security Compass
Security magnifying glass visualization