# vm2: NodeVM node:-prefixed negative builtin deny bypass exposes child_process

vm2 NodeVM wildcard builtin deny list matched exact names, so `-node:child_process` failed to block `child_process`, letting sandboxed code reach host process-spawning APIs.

- Payout: Research
- Severity: critical
- Program: vm2
- Found by: nasaa0x, sangnigege, manus-use
- Disclosed: 2026-10-01
- CVE: CVE-2026-92957
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-8686-vhfx-7r3j
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8686-vhfx-7r3j
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:critical tech:nodejs technique:parser-differential technique:source-review vuln:access-control vuln:misconfiguration vuln:sandbox-escape

**Target:** NodeVM sandbox require() builtin policy
**Root cause:** `lib/setup-node-sandbox.js` strips the `node:` prefix before loading builtins, but `lib/builtin.js` compares negative wildcard entries by exact string against canonical `BUILTIN_MODULES` names, so `-node:child_process` never matches `child_process`.
**Steps:**
1. Configure `new NodeVM({ require: { builtin: ['*', '-node:child_process'] } })`.
2. Inside sandbox run `require('child_process')` and `require('node:child_process')`.
3. Both resolve; returned module exposes `execSync` and `spawn` as functions.
4. Control: `builtin: ['*', '-child_process']` correctly throws `VMError` ENOTFOUND for both spellings.
**How found:** Noticed the resolver strips `node:` before builtin load while the deny-list check in `lib/builtin.js` does exact string matching against canonical names, then probed both specifier spellings under both deny spellings.
**Exploitation:** Untrusted sandbox code calls `require('child_process')` under a policy the embedder believed denied it, obtaining host `execSync`/`spawn` references equivalent to command execution capability; the advisory's PoC only checks reachability and does not spawn a process.
**Insight:** When a runtime canonicalizes module specifiers (prefix stripping, aliasing) in one code path, audit every allow/deny comparison for the same normalization; deny-list entries written in the user-facing spelling silently no-op.

