Act nowDisclosed

Advisory · CVE-2026-100721

vm2 custom resolver lets sandboxed code escape to the host

A prefix-match flaw in vm2's NodeVM external-module resolver lets untrusted guest code load non-allowlisted modules through the host require, escaping the sandbox.

Vendor
vm2
Product
vm2
Identifier / CWE
CVE-2026-100721
CWE-863
Action timing
Immediate
ELI5

Explain it like I’m five

vm2 keeps a guest list of modules the sandbox may use, but it checks the list with a fuzzy match. An uninvited module whose name starts like an invited one walks right in and runs its code on the host computer.

SIMPLIFIED_ATTACK_PATH05 STEPS
  1. 01Custom resolver configured

    An embedder sets require.external with a custom resolver and context set to host in NodeVM.

  2. 02Fuzzy allowlist check

    LegacyResolver.customResolve records the resolved module directory as a prefix regex with no path-separator or end boundary.

  3. 03Prefix passes the check

    Guest code requires the allowlisted module, then requires the absolute path of a non-allowlisted sibling whose path merely shares the resolved prefix.

  4. 04Sibling loads on host

    The sibling passes isPathAllowedForModule and loads through hostRequire, so its top-level code runs in the host process before any wrapping.

  5. 05Sandbox escape

    The attacker executes arbitrary code in the host context.

What happened

vm2 (CVE-2026-100721, CVSS 9.5) contains an authorization bypass in the NodeVM external-module resolver affecting releases before 3.12.2. When an embedder configures require.external with a custom resolver (and context: 'host'), LegacyResolver.customResolve in lib/resolver-compat.js records the resolved module directory in this.externals as a regular expression built from the resolved path, without requiring a path separator or end-of-string boundary.

Untrusted guest code can require the allowlisted module (e.g. foo) and then require the absolute path of a non-allowlisted sibling whose path merely shares the resolved prefix (e.g. .../node_modules/foo2/index.js). The sibling passes isPathAllowedForModule and is loaded through hostRequire, so its top-level code runs in the host process before the exports are wrapped with vm.readonly, resulting in a sandbox escape and arbitrary code execution in the host context.

This is the third sandbox-escape class in vm2 this month, following the child_process denylist and bridge issues fixed in 3.12.1.

What to do

  1. Upgrade vm2 to 3.12.2 or later.
  2. Audit NodeVM configurations that use custom resolvers with require.external; this flaw is specific to that combination.
  3. Review logs and process behavior for unexpected module loads or host-side execution from sandboxed workloads.
  4. Treat any instance that ran untrusted code under the affected configuration as potentially compromised and investigate the host.

Management note

vm2’s isolation boundary keeps failing in new ways, and this one is subtle: a string-matching detail in the module allowlist, not an obvious denylist gap. Patch immediately, and reconsider running untrusted code in the same process as the host.