ZeroPath at Black Hat USA 2026

vm2 CVE-2026-47139: Underscored Builtin Network Bypass Enables SSRF from Node.js Sandbox — Quick Look with PoC and Detection Guidance

A brief summary of CVE-2026-47139, a high severity sandbox bypass in vm2 for Node.js that allows sandboxed code to make network requests via underscored internal builtins despite explicit network module exclusions. Includes proof of concept details and detection strategies.

CVE Analysis

12 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-06-12

vm2 CVE-2026-47139: Underscored Builtin Network Bypass Enables SSRF from Node.js Sandbox — Quick Look with PoC and Detection Guidance
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

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:

  1. Identify the sandbox configuration: The attacker recognizes that the NodeVM instance uses the wildcard builtin pattern with network module exclusions (a common and documented configuration pattern).

  2. Require underscored internal modules: Instead of require('http') (which is blocked), the attacker calls require('_http_client') to access the ClientRequest constructor, or require('_http_server') to access the Server constructor.

  3. 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 at 169.254.169.254, which can expose IAM credentials, instance identity tokens, and other sensitive configuration data on AWS, GCP, and Azure.

  4. 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.

  5. 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/-https exclusion
  • require('_http_server').Server(handler).listen(0) opens a listening HTTP socket, bypassing the -net exclusion
  • require('_tls_wrap').TLSSocket accesses TLS primitives, bypassing the -tls exclusion

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 vm2 sandbox, particularly to sensitive destinations like 127.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:

PeriodEvents
20238 security advisories published, 7 within a 4 month window
July 2023Project officially deprecated due to critical security issues
October 2025Maintenance resumed after deprecation period
January 2026CVE-2026-22709 (CVSS 9.8): Promise callback bypass
May 202613 CVEs disclosed in a single wave (CVSS 9.0 to 10.0)
May 2026v3.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

Detect & fix
what others miss

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