ZeroPath at Black Hat USA 2026

vm2 Sandbox Escape CVE-2026-47137: Quick Look at a CVSS 10.0 Patch Bypass Enabling Host RCE

A brief summary of CVE-2026-47137, a CVSS 10.0 sandbox escape in the vm2 Node.js library that bypasses a prior security patch through a strict equality flaw. Includes proof of concept, patch analysis, and affected version details.

CVE Analysis

10 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-06-12

vm2 Sandbox Escape CVE-2026-47137: Quick Look at a CVSS 10.0 Patch Bypass Enabling Host RCE
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 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:

  1. Create an outer NodeVM with the bypassed configuration. The attacker (or the application on behalf of the attacker) creates a NodeVM with { nesting: true } and does not specify require. The security guard is skipped because options.require is undefined, not false.

  2. Import vm2 inside the sandbox. Because nesting: true activates the NESTING_OVERRIDE map, the sandbox code can call require('vm2') to obtain the vm2 library itself, even though the effective requireOpts is false.

  3. Construct an unconstrained inner NodeVM. Using the imported vm2, the attacker creates a new inner NodeVM with an unrestricted require configuration, such as { builtin: ['child_process'] }.

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

  1. Truthy non true nesting values. The internal override gate uses truthiness (nesting && NESTING_OVERRIDE), not strict equality, so values like 1, 'yes', {}, or [] would still inject the override. The guard was widened from nesting === true to just nesting (truthy check).

  2. Truthy non object require values. Passing require: true, require: 1, or require: 'yes' would pass the !requireOpts check (they are truthy), but inside makeResolverFromLegacyOptions these primitives destructure to all undefined, 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:

  • NodeVM created with nesting: true
  • The require option either omitted entirely or set to any value that is not a genuine non null object or Resolver instance

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:

CVEGHSACVSSAffected VersionsPatched VersionDescription
CVE-2023-30547<= 3.9.163.9.17Sandbox escape
CVE-2023-37903GHSA-g644-9gfx-q4q49.8<= 3.9.193.9.20Sandbox escape via Node.js custom inspect function
CVE-2026-44007GHSA-8hg8-63c5-gwmx9.1<= 3.11.03.11.1nesting: true bypasses require: false via NESTING_OVERRIDE
CVE-2026-227099.8<= 3.10.13.11.0Sandbox escape via Promise callback bypass
CVE-2026-47137GHSA-m4wx-m65x-ghrr10.0<= 3.11.33.11.4Bypass 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

Detect & fix
what others miss

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