CVE-2026-47141 Overview
CVE-2026-47141 affects vm2, an open source virtual machine and sandbox library for Node.js. Versions prior to 3.11.4 allow sandboxed code to access process-wide observability builtins through require.builtin. The diagnostics_channel, async_hooks, and perf_hooks modules are not blocked by the dangerous builtin denylist. These modules expose state from the entire host process rather than the sandbox, allowing sandboxed code to observe host application data across the vm2 boundary. The maintainers patched the issue in version 3.11.4. This weakness is categorized as exposure of resource to wrong sphere [CWE-668].
Critical Impact
Sandboxed code can read host-side HTTP request data, authentication context, and performance marks, breaking the isolation guarantees of NodeVM.
Affected Products
- vm2 versions prior to 3.11.4
- Node.js applications embedding NodeVM with require.builtin enabled
- Downstream packages depending on vulnerable vm2 releases
Discovery Timeline
- 2026-06-12 - CVE-2026-47141 published to the National Vulnerability Database
- 2026-06-17 - Last updated in NVD database
Technical Details for CVE-2026-47141
Vulnerability Analysis
The vm2 library implements a sandbox that exposes a configurable list of allowed Node.js builtins to guest code through require.builtin. The library maintains a denylist intended to block builtins that can break out of or peek through the sandbox boundary. The denylist did not cover diagnostics_channel, async_hooks, and perf_hooks. These modules report on activity inside the host Node.js process itself, not just the sandbox context. Guest code obtaining a reference to any of these modules can subscribe to host activity in real time.
For example, calling dc.channel('http.server.request.start').subscribe(cb) delivers raw IncomingMessage objects from every request the embedding application receives, including Authorization and session cookie headers. The async_hooks module exposes executionAsyncResource(), which returns the host's current AsyncResource and any AsyncLocalStorage state pinned to it. The perf_hooks module surfaces every performance.mark() call made by the host, often containing request IDs, user IDs, or query strings.
Root Cause
The root cause is an incomplete denylist in lib/builtin.js. The vm2 boundary cannot contain modules whose data describes the embedder process. Even a read-only proxy that forwards calls to the underlying host module still functions as an exfiltration primitive.
Attack Vector
An attacker who can execute JavaScript inside a NodeVM instance requires any builtin permission that includes the missing modules. The attacker calls require('diagnostics_channel'), require('async_hooks'), or require('perf_hooks') and subscribes to host events. No additional privileges or user interaction are needed.
// Security patch in lib/builtin.js - fix(GHSA-9g8x-92q2-p28f)
// Source: https://github.com/patriksimek/vm2/commit/e1c48fce05189f48e71efbd32af0754efa4066bb
// `preopens: {}` exposes the host CWD when sandbox code constructs
// a WASI module. Embedders who genuinely need WASI can register a
// controlled wrapper via `mock`/`override`.
- 'wasi'
+ 'wasi',
+ // SECURITY (GHSA-9g8x-92q2-p28f): Process-wide observability builtins.
+ // Unlike most Node builtins, these expose state of the *entire host
+ // process* rather than sandbox-local state -- the vm2 boundary cannot
+ // usefully contain them because the data they surface (HTTP requests,
+ // async-context, perf marks, heap contents) belongs to the embedder.
+ // - diagnostics_channel : `dc.channel('http.server.request.start').subscribe(cb)`
+ // hands the sandbox raw host IncomingMessage
+ // objects -- including Authorization /
+ // session-token headers.
+ // - async_hooks : `executionAsyncResource()` returns the host's
+ // current AsyncResource pinned via AsyncLocalStorage.
+ // - perf_hooks : `performance.getEntriesByType('mark')` reads
+ // every host-side `performance.mark(name)`.
+ // - v8 : `v8.getHeapSnapshot()` serializes the entire
+ // host V8 heap.
The patch adds diagnostics_channel, async_hooks, perf_hooks, and v8 to the denylist enforced by NodeVM. See the vm2 GitHub Security Advisory GHSA-9g8x-92q2-p28f for the full disclosure.
Detection Methods for CVE-2026-47141
Indicators of Compromise
- Sandboxed scripts invoking require('diagnostics_channel'), require('async_hooks'), or require('perf_hooks')
- Unexpected subscribers registered on http.server.request.start or other diagnostics channels in production processes
- Outbound network connections from Node.js services that correlate with inbound HTTP requests carrying authorization headers
Detection Strategies
- Inventory all applications embedding vm2 and identify versions below 3.11.4 using software composition analysis
- Audit NodeVM configurations for require.builtin settings that permit observability modules
- Instrument the host process to log calls to diagnostics_channel.subscribe and executionAsyncResource() originating from sandbox contexts
Monitoring Recommendations
- Forward Node.js application logs to a centralized analytics platform and alert on sandbox require() calls that reference observability builtins
- Track dependency manifests in source control and trigger alerts on installations of vm2 versions less than 3.11.4
- Correlate process telemetry with inbound HTTP traffic to detect unusual cross-context data flow
How to Mitigate CVE-2026-47141
Immediate Actions Required
- Upgrade vm2 to version 3.11.4 or later in all affected applications
- Review every NodeVM instance and remove diagnostics_channel, async_hooks, perf_hooks, and v8 from any custom allowlist
- Rotate credentials and session tokens that may have been observable through the affected channels
Patch Information
The fix is available in vm2 release v3.11.4. The commit e1c48fce05189f48e71efbd32af0754efa4066bb adds the process-wide observability modules to the denylist in lib/builtin.js. Refer to the upstream patch commit for implementation detail.
Workarounds
- Migrate away from vm2, which is no longer recommended for untrusted code execution, toward isolated-process or VM-based sandboxes
- Explicitly restrict require.builtin to a minimal allowlist that excludes observability modules until upgrade is possible
- Run sandboxed workloads in dedicated Node.js processes with no access to host secrets or request data
# Configuration example: upgrade vm2 and pin the fixed version
npm install vm2@^3.11.4
npm ls vm2
# Verify the installed version is at least 3.11.4
node -e "console.log(require('vm2/package.json').version)"
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

