Introduction
A sandbox that promises network isolation but quietly exposes the same network primitives through undocumented internal module names is worse than no sandbox at all: it gives embedders a false sense of security while leaving cloud metadata endpoints, localhost services, and internal networks fully reachable. That is precisely the situation with CVE-2026-47139 in vm2, the popular Node.js sandbox library.
vm2 is an open source sandbox for Node.js that allows developers to run untrusted JavaScript with controlled access to built in modules. With roughly 4,100 GitHub stars and broad adoption across code playgrounds, plugin systems, CI/CD pipelines, and increasingly AI agent frameworks, vm2 occupies a significant position in the Node.js ecosystem. Its relevance has grown as organizations deploy LLM powered agents that dynamically execute generated code inside sandboxed environments.
CVE-2026-47139 carries a CVSS 8.6 (High) score and is classified under CWE-693 (Protection Mechanism Failure). It allows sandboxed code within NodeVM to bypass explicit network module exclusions by requiring Node.js's underscored internal builtins (_http_client, _http_server, _tls_wrap, and others), enabling SSRF class attacks from within a supposedly network isolated sandbox. The vulnerability was patched in vm2 version 3.11.4, released May 18, 2026.
Technical Information
Root Cause: Incomplete Module Exclusion Logic
vm2's NodeVM provides a builtin configuration option that controls which Node.js built in modules are available to sandboxed code. A common pattern is to allow all builtins except network modules:
const vm = new NodeVM({ require: { builtin: ['*', '-http', '-https', '-net', '-dgram', '-tls', '-dns', '-dns/promises', '-http2'], external: false } });
The wildcard '*' expands to all modules listed in require('module').builtinModules, and the -name syntax removes specific modules from that expanded list. The exclusion logic performs exact name matching: -http removes the http module, -tls removes the tls module, and so on.
The problem is that Node.js exposes underscored internal implementation modules that are siblings of these public modules. These include:
_http_client_http_server_http_agent_http_common_http_incoming_http_outgoing_tls_common_tls_wrap
These underscored modules appear in require('module').builtinModules and provide the same underlying network primitives as their public counterparts. Because the exclusion directives only matched exact names (e.g., -http does not match _http_client), these internal modules passed through the filter and remained available to sandboxed code.
The vulnerable filter in lib/builtin.js was:
.filter(s => !s.startsWith('internal/') && !isDangerousBuiltin(s));
This excluded modules prefixed with internal/ and those in a dangerous builtins list, but it did not exclude modules prefixed with an underscore. The function responsible for expanding the wildcard into the allowed module list is makeBuiltinsFromLegacyOptions in lib/builtin.js.
Attack Flow
An attacker who can execute code within a NodeVM sandbox configured with network module exclusions can exploit this vulnerability through the following steps:
-
Identify the sandbox configuration: The attacker recognizes that the
NodeVMinstance uses the wildcard builtin pattern with network module exclusions (a common and documented configuration pattern). -
Require underscored internal modules: Instead of
require('http')(which is blocked), the attacker callsrequire('_http_client')to access theClientRequestconstructor, orrequire('_http_server')to access theServerconstructor. -
Make outbound HTTP requests: Using
_http_client.ClientRequest, the attacker crafts HTTP requests to arbitrary destinations. A particularly damaging target is the cloud metadata endpoint at169.254.169.254, which can expose IAM credentials, instance identity tokens, and other sensitive configuration data on AWS, GCP, and Azure. -
Open listening sockets: Using
_http_server.Server, the attacker can open a listening HTTP socket on the host, potentially exposing a service for data exfiltration or command and control communication. -
Establish encrypted connections: Using
_tls_wrap.TLSSocket, the attacker can create TLS wrapped connections that may evade network monitoring tools looking for plaintext HTTP traffic.
This vulnerability is categorized as Attack Category 34 in vm2's internal docs/ATTACKS.md documentation. While it does not provide full host remote code execution, the SSRF capability is particularly dangerous in cloud environments where metadata endpoints expose credentials, and it can be chained with other vm2 escape primitives from the 2026 CVE wave for full compromise.
The Fix
The patch in commit 436053e adds a single filter condition to the BUILTIN_MODULES definition in lib/builtin.js:
const BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives'))) .filter(s=>!s.startsWith('internal/') && !s.startsWith('_') && !isDangerousBuiltin(s));
The addition of !s.startsWith('_') ensures that all underscored internal modules are excluded from the wildcard expansion. A comprehensive security comment block was added above the filter documenting the tracked underscored siblings and the rationale for the fix. Importantly, addDefaultBuiltin is unchanged, so explicit opt ins via builtin: ['_http_client'] or configurations using mock/override with underscored names continue to function for embedders who intentionally want this behavior.
Proof of Concept
A public proof of concept exploit for CVE-2026-47139 is available directly from the maintainer's GitHub Security Advisory (GHSA-r9pm-gxmw-wv6p) and is corroborated by the fix commit.
The advisory includes a downloadable PoC file named internal-http-builtin-network-bypass.js, intended to be run from the vm2 repository root with node poc/internal-http-builtin-network-bypass.js. It was tested on vm2 3.11.2 and Node.js v25.9.0.
The canonical PoC from the fix commit's docs/ATTACKS.md demonstrates the exploit concisely:
// (advisory GHSA-r9pm-gxmw-wv6p) const vm = new NodeVM({ require: { builtin: ['*', '-http', '-https', '-net', '-dgram', '-tls', '-dns', '-dns/promises', '-http2'], external: false } }); vm.run(` const {ClientRequest} = require('_http_client'); const req = new ClientRequest({host: '169.254.169.254', port: 80, path: '/latest/meta-data/'}); req.on('response', r => r.on('data', d => module.exports = d.toString())); req.end(); `, 'poc.js');
This works because _http_client is present in require('module').builtinModules and exposes the same ClientRequest constructor as the public http module. The exclusion directive -http only removes the exact string http from the allowlist, leaving _http_client accessible.
The three primary bypass primitives are:
require('_http_client').ClientRequest(opts)makes outbound HTTP requests, bypassing the-http/-httpsexclusionrequire('_http_server').Server(handler).listen(0)opens a listening HTTP socket, bypassing the-netexclusionrequire('_tls_wrap').TLSSocketaccesses TLS primitives, bypassing the-tlsexclusion
The fix commit also includes a 202 line reproduction test suite at test/ghsa/GHSA-r9pm-gxmw-wv6p/repro.js that programmatically verifies the exploit is blocked post patch. The underscored modules load through the normal vm.readonly() bridge proxy, so this vulnerability provides SSRF class capability bypass rather than full RCE.
Detection Methods
Detecting CVE-2026-47139 requires a layered approach spanning dependency auditing, static code analysis, and runtime network monitoring. No formalized detection signatures (YARA, Sigma, Snort/Suricata, or Elastic detection rules) have been published for this vulnerability as of the publication date.
Version Based Detection (Software Composition Analysis)
The most straightforward detection method is identifying whether your project depends on a vulnerable version of the vm2 npm package. All versions from 0.1.0 through 3.11.3 are affected. Multiple SCA platforms, including GitLab Dependency Scanning, Veracode SCA, and Meterian, have indexed CVE-2026-47139 and will flag vulnerable vm2 installations. Any project whose package.json or lockfile pins vm2 at or below 3.11.3 should be treated as potentially exposed. The GHSA identifier to verify coverage in your tooling is GHSA-r9pm-gxmw-wv6p.
Static Analysis: Code Level Indicators of Exploitation
The fix commit's docs/ATTACKS.md (Category 34) explicitly documents detection rules for this vulnerability. The canonical exploit patterns to search for in any code that executes inside a NodeVM sandbox are require() calls targeting Node.js's undocumented underscored internal modules:
_http_client,_http_server,_http_agent,_http_common,_http_incoming,_http_outgoing_tls_common,_tls_wrap_stream_readable,_stream_writable,_stream_duplex,_stream_transform,_stream_wrap,_stream_passthrough
A grep or custom linting rule targeting require\(['"]_http_ and require\(['"]_tls_ across code submitted to sandboxed execution environments is an effective first pass detection. Also consider node: prefixed variants such as require('node:_http_client').
Configuration Auditing
Applications that instantiate NodeVM with the wildcard builtin pattern alongside network module exclusions (e.g., builtin: ['*', '-http', '-https', '-net', '-tls']) are the configurations directly at risk. Auditing your codebase for NodeVM instantiation patterns using wildcard builtins with network module exclusions will identify historically vulnerable configurations. The key vulnerable function is makeBuiltinsFromLegacyOptions in lib/builtin.js.
Network Based Detection
Since this vulnerability enables SSRF class access, network monitoring provides an important detection layer for sandboxed environments that are intended to be network isolated:
- Watch for unexpected outbound HTTP connections originating from the Node.js process hosting the
vm2sandbox, particularly to sensitive destinations like127.0.0.1,localhost, cloud metadata endpoints (169.254.169.254), or internal service addresses. - Monitor for unexpected listening sockets opened by the sandbox hosting process. The exploit can call
Server(...).listen(0)to bind to an ephemeral port, which would be anomalous for a sandboxed workload. - Process level network monitoring (tracking socket creation and connection events for the specific Node.js process running the sandbox) can surface exploitation attempts even without signature based detection.
Affected Systems and Versions
All versions of the vm2 npm package from 0.1.0 through 3.11.3 are affected. The fix ships in version 3.11.4.
The vulnerability specifically affects NodeVM instances configured with the wildcard builtin pattern (builtin: ['*']) combined with network module exclusions ('-http', '-https', '-net', '-dgram', '-tls', '-dns', '-dns/promises', '-http2'). Configurations that do not use the wildcard expansion or that do not rely on network module exclusions are not impacted by this specific bypass.
Version 3.11.5 (released May 18, 2026) is a drop in bugfix for v3.11.4 that resolves three regressions (util.inspect output, array iteration on frozen host arrays, and a .node extension key typo) without introducing API changes.
A community fork, @usebruno/vm2, exists at version 3.9.19 and may also be affected, though this has not been independently confirmed.
Vendor Security History
vm2 has a persistent pattern of sandbox escape vulnerabilities that reflects fundamental architectural limitations:
| Period | Events |
|---|---|
| 2023 | 8 security advisories published, 7 within a 4 month window |
| July 2023 | Project officially deprecated due to critical security issues |
| October 2025 | Maintenance resumed after deprecation period |
| January 2026 | CVE-2026-22709 (CVSS 9.8): Promise callback bypass |
| May 2026 | 13 CVEs disclosed in a single wave (CVSS 9.0 to 10.0) |
| May 2026 | v3.11.4 released closing 10 advisories simultaneously |
Notable prior CVEs include CVE-2023-30547 (exception sanitization bypass, patched in v3.9.17), CVE-2023-37466 (Promise handler sanitization bypass, patched in v3.9.19), and CVE-2026-22709 (Promise callback bypass, patched in v3.10.2). Each follows the same pattern: a gap in the proxy instrumentation allows sandboxed code to reach host level capabilities.
The 2026 wave includes critical RCE class vulnerabilities such as CVE-2026-24118 (__lookupGetter__ escape), CVE-2026-26956 (WASM/JSTag escape to host RCE), and CVE-2026-43999 (allowlist bypass to child_process RCE).
The root cause is architectural. Node.js inherently intercepts calls from within the sandbox, preventing arguments from being securely wrapped in proxies. Both the library maintainer and security researchers (including Semgrep) recommend migrating away from vm2 entirely to alternatives such as isolated-vm (which uses V8's native Isolate interface) or fully isolated environments such as Docker containers, gVisor, or Firecracker microVMs.
References
- CVE-2026-47139 Detail (NVD)
- GHSA-r9pm-gxmw-wv6p: GitHub Security Advisory
- GHSA-r9pm-gxmw-wv6p (OSV)
- Fix Commit: Exclude underscored builtins from NodeVM wildcard
- vm2 Release v3.11.4
- vm2 GitHub Repository
- vm2 npm Package
- Miggo Vulnerability Database: CVE-2026-47139
- GitLab Advisory: CVE-2026-47139
- Veracode SCA: NodeVM Network Builtin Exclusions Bypass
- Kodem Security: vm2 Sandbox Escape, AI Agent RCE Risk and IOCs
- Semgrep: New Sandbox Escape Affecting Popular Node.js Library
- EndorLabs: Critical Sandbox Escape in vm2 Enables RCE
- QuorumCyber: PoC Exploit Released for VM2 Vulnerability



