Skip to main content
Vulnerability Database/CVE-2026-100721

CVE-2026-100721: vm2 Authorization Bypass Vulnerability

CVE-2026-100721 is an authorization bypass flaw in vm2 that enables sandbox escape and arbitrary code execution in the host context. This post covers technical details, affected versions, impact, and mitigation steps.

Published:

CVE-2026-100721 Overview

CVE-2026-100721 is an authorization bypass in the vm2 sandbox library affecting versions before 3.12.2. The flaw lives in the NodeVM external-module resolver, specifically in LegacyResolver.customResolve within lib/resolver-compat.js. When an embedder configures require.external with a custom resolver and context: 'host', the resolver records allowlisted module directories as regular expressions without enforcing a path separator or end-of-string boundary. Untrusted guest code can require a sibling package that merely shares the resolved prefix, triggering hostRequire and executing attacker-controlled code in the host context. The result is a sandbox escape and arbitrary code execution outside the vm2 boundary. The weakness is classified under CWE-863: Incorrect Authorization.

Critical Impact

Guest code inside a vm2 sandbox can escape to the host process and execute arbitrary JavaScript with the privileges of the embedding Node.js application.

Affected Products

  • vm2 npm package, all versions prior to 3.12.2
  • Node.js applications embedding vm2 with NodeVM and a custom require.external resolver
  • Server-side JavaScript platforms that use vm2 to isolate third-party or user-submitted code

Discovery Timeline

  • 2026-09-27 - CVE-2026-100721 published to NVD
  • 2026-09-28 - Last updated in NVD database

Technical Details for CVE-2026-100721

Vulnerability Analysis

The vm2 library sandboxes untrusted Node.js code and gates host-module access through an allowlist. In LegacyResolver.customResolve, the resolved directory of each allowlisted module is stored in this.externals using new RegExp('^' + escapeRegExp(resolvedPath)). The regular expression is anchored only at the start and has no trailing path separator or end-of-string boundary. A later check in isPathAllowedForModule uses path.startsWith(mod.path) with the same loose semantics.

An attacker running inside the sandbox first calls require('foo') to populate the external cache with the resolved directory of the allowlisted package. The attacker then calls require with the absolute path of a non-allowlisted sibling such as .../node_modules/foo2/index.js. The sibling path shares the foo prefix and passes the broken boundary check, so the resolver hands the path to hostRequire. Node.js loads the sibling module and executes its top-level code before vm.readonly wraps the exports, yielding arbitrary code execution in the host.

Root Cause

The root cause is a missing path-boundary assertion in the allowlist comparison. Prefix matching without a separator check conflates sibling directories such as foo and foo2, violating the authorization decision that isPathAllowed is meant to enforce.

Attack Vector

Exploitation requires only the ability to execute JavaScript inside a NodeVM instance that has been configured with a custom external resolver. No authentication is required, and no user interaction is needed on the host. The attacker places or identifies a sibling module whose directory name begins with an allowlisted package name, then triggers require with its absolute path from guest code.

javascript
// Patch from lib/resolver-compat.js — adds a path-boundary check
// Source: https://github.com/patriksimek/vm2/commit/6ac3916da84e060c403e407b6b6318fcc66b0e72
if (!super.isPathAllowed(path)) return false;
if (mod) {
    if (mod.allowTransitive) return true;
    // SECURITY (GHSA-7q3f-wx44-378m): require a path boundary after
    // `mod.path`, not a raw prefix. Without it a prefix-sharing sibling
    // (`.../node_modules/foo2/index.js` vs allowlisted
    // `.../node_modules/foo`) passed `startsWith` and loaded as if it
    // were `foo`.
    const len = mod.path.length;
    if (
        path.startsWith(mod.path) &&
        (path.length === len || (len > 0 && this.fs.isSeparator(mod.path[len - 1])) || this.fs.isSeparator(path[len]))
    ) {
        const rem = path.slice(len);
        if (!/(?:^|[\\/])node_modules(?:$|[\\/])/.test(rem)) return true;
    }
}

A related fix anchors the bare-specifier allowlist regular expression to prevent substring matches such as left-pad matching evil-left-pad:

javascript
// Source: https://github.com/patriksimek/vm2/commit/ab4ee7d803e8c80155e9eb3672226bddbca4aa9c
this.externalCache = externals.map(pattern =>
    new RegExp('^(?:' + makeExternalMatcherRegex(pattern) + ')(?:[\\/].*)?$')
);

Detect Identity Attacks the Moment Authentication Fails

Singularity™ Identity surfaces credential misuse, lateral movement, and privilege escalation in real time, so you can contain the breach before it spreads.

Stop Identity Attacks

Detection Methods for CVE-2026-100721

Indicators of Compromise

  • Guest-controlled require calls that pass absolute filesystem paths instead of bare module specifiers
  • Node.js child processes or outbound network connections originating from a process that embeds vm2 and should not spawn code
  • Unexpected loading of sibling packages whose names share a prefix with allowlisted externals, visible in Node.js module-load telemetry

Detection Strategies

  • Inventory applications that depend on vm2 and flag any version earlier than 3.12.2 using software composition analysis
  • Instrument hostRequire or Module._load in affected applications to log every module path resolved for sandboxed code
  • Alert on filesystem reads of node_modules subdirectories that follow an allowlisted package name with no separator boundary

Monitoring Recommendations

  • Track process-lineage anomalies where the embedding Node.js process spawns shells, network tools, or persistence utilities
  • Correlate inbound requests that supply user-controlled JavaScript with subsequent host-level file or network activity
  • Review build and dependency pipelines for packages whose names collide with existing allowlisted externals

How to Mitigate CVE-2026-100721

Immediate Actions Required

  • Upgrade vm2 to version 3.12.2 or later in every service that embeds the library
  • Audit NodeVM configurations and remove require.external entries that are not strictly required
  • Rotate secrets and credentials accessible to any host process that executed untrusted guest code on a vulnerable version

Patch Information

The fix is tracked in GitHub Security Advisory GHSA-5h3f-q97h-ccvc and the companion VulnCheck advisory. The resolver boundary check is applied in commits 6ac3916, 9103444, and ab4ee7d. Note that upstream vm2 has been deprecated in favor of isolated-vm; migration should be planned for long-term resilience.

Workarounds

  • Set require.external to false or restrict it to builtin modules only until the patch is applied
  • Run the embedding Node.js process under a restricted OS user with limited filesystem and network capabilities
  • Validate that no attacker-controllable sibling packages exist alongside allowlisted modules in node_modules
bash
# Update vm2 and verify the installed version
npm install vm2@^3.12.2
npm ls vm2

# Audit dependencies for vulnerable copies
npm audit --production

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.