# vm2 Custom Module Resolver Can Bypass the External Package Allowlist by Loading a Colliding Host Package

vm2's external allowlist regex substring-matches package names, so sandboxed code loads evil-left-pad via a custom resolver and executes host code.

- Payout: Research
- Severity: critical
- Program: vm2
- Found by: XlabAITeam, keenanwgn, pkuGenuine, liangjs
- Disclosed: 2026-10-01
- CVE: CVE-2026-92951
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-c48m-32m9-vx93
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-c48m-32m9-vx93
- Tags: area:oss domain:oss impact:internal-access impact:rce kind:research platform:ghsa sev:critical tech:nodejs technique:source-review vuln:access-control vuln:rce vuln:sandbox-escape

**Target:** NodeVM require.external allowlist with custom require.resolve
**Root cause:** vm2 builds an unanchored regex from the external allowlist and substring-matches the requested package name, so any name containing an allowlisted name passes the pre-check before the custom resolver is invoked.
**Steps:**
1. Configure `new NodeVM({require:{external:['left-pad'], resolve: id => require.resolve(id, {paths:[customRoot]}), context:'host', builtin:[]}})`.
2. Place an unauthorized package `evil-left-pad` in a path resolvable by the custom resolver.
3. From sandboxed code call `require('evil-left-pad')`; the unanchored `/left\-pad/` check passes.
4. vm2 calls the custom resolver, adds the returned path to the loadable list, and loads it with host `require()`.
5. The package's top-level code runs `child_process.execFileSync()` in host context, printing `HOST_EXEC`.
**How found:** Noticed the allowlist pre-check regex was not anchored to full package-name boundaries, then tested a colliding name against a custom resolver in host context.
**Exploitation:** Attacker-controlled sandboxed JS requests a package whose name contains an allowlisted name; if the colliding package exists in a resolver-reachable path (plugin upload dir, synced private registry, dependency download workflow), its top-level code executes in the host Node.js process, enabling file/env/secret reads and host modification.
**Escalation:** Bypassing `builtin: []` by having the colliding package's top-level code require host `child_process`, turning a module-allowlist bypass into host RCE.
**Insight:** When a sandbox allowlist is enforced by regex/substring rather than exact equality, try prefix/suffix-colliding names (evil-<allowed>, <allowed>-backdoor) that a custom resolver can still resolve; also check whether the resolver's search path is attacker-influenceable.

