Skip to main content

vm2 EUVDEUVD-2026-36440

| CVE-2026-47209 HIGH
Protection Mechanism Failure (CWE-693)
2026-05-29 https://github.com/patriksimek/vm2 GHSA-c4cf-2hgv-2qv6
8.6
CVSS 3.1 · Vendor: https://github.com/patriksimek/vm2
View

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain

  1. Recon
    Submit untrusted JS to vm2 host
  2. Delivery
    Obtain reference to bridged host object
  3. Exploit
    Create child via Object.create(hostObj)
  4. Install
    Assign Symbol.for('nodejs.util.promisify.custom') on child
  5. C2
    Property leaks through BaseHandler.set to host
  6. Execute
    Host calls util.promisify(hostObj)()
  7. Impact
    Attacker 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 VM.setGlobal, the sandbox option, or any returned host reference reachable through the bridge (intrinsics protected by ProtectedHandler are out of scope, but ordinary functions/objects are not), and (3) the host realm subsequently reads, enumerates, or dispatches through that polluted object - the canonical sharpest path is the host invoking util.promisify(hostFn) after the sandbox writes Symbol.for('nodejs.util.promisify.custom'), but any post-sandbox use of a tainted slot suffices. No authentication, network position, or user interaction at the vm2 layer is required (CVSS PR:N/UI:N); attacker capability is 'can submit JavaScript that vm2 will run'. Limiting factor: if the host never re-touches sandbox-exposed objects after sandbox execution, the polluted property remains inert - practical impact scales with how tightly host code is coupled to objects shared with the sandbox.

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., util.promisify(hostFn)), so impact is contingent on the host application's post-sandbox usage pattern. The vulnerability is tagged 'RCE' because polluting Symbol.for('nodejs.util.promisify.custom') yields attacker-controlled callables invoked in the host. The advisory contains a working reproduction snippet, which raises practical exploitability above the typical theoretical sandbox-escape bar, but no public weaponized exploit, EPSS score, or KEV listing is present in the provided data. Confidentiality (C:N) and availability (A:N) being zero is conservative - once host RCE is reached via promisify hijack, downstream C/A impact follows, so the published score may understate end-state risk for embedders. For any service evaluating untrusted code in vm2 ≤ 3.11.3 this is a hard priority-one upgrade.

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 hostFn into the sandbox. Sandbox code calls Object.create(hostFn) and assigns Symbol.for('nodejs.util.promisify.custom') to an attacker-controlled function; the receiver-ignoring BaseHandler.set writes the symbol property onto the host-realm hostFn. Later the host invokes util.promisify(hostFn)(...) and dispatches into attacker-controlled code in the host realm, yielding sandbox escape and effective host-side code execution; the advisory ships a working reproduction snippet.

This Week Schedule remediation this week. Why: HIGH severity (CVSS 7.0-8.9).
Patch available 0.0% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – 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

vm2 < 3.11.4

Affected Packages

npm vm2 fix: 3.11.4

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

Vendor (https://github.com/patriksimek/vm2) PRIMARY 8.6 HIGH
AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N
Red Hat 8.6 MEDIUM

Primary rating from Vendor (https://github.com/patriksimek/vm2).

CVSS VectorVendor: https://github.com/patriksimek/vm2

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

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

javascript
// 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:

javascript
// Sandbox code
const child = Object.create(hostObj);
child.injectedProp = 'attacker-value';
// hostObj now has 'injectedProp' on the HOST side

Combined with the Symbol.for coverage gap, this enables semantic confusion attacks:

javascript
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

javascript
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-BUG

Suggested Fix

javascript
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
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 5 npm packages depend on vm2 (4 direct, 1 indirect)

Ecosystem-wide dependent count for version 3.11.4.

Executive ViewAI

Why It Matters

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.

Business Impact: High
Compensating Controls

Apply vendor patch.

Threat Intelligence Overview

EPSS Score
0.0%
Top 84% exploitable
Threat Intel
1
1 source
Data Quality
7 sources
Confidence: High
EUVD NVD CVE.org EPSS Vulnrichment GHSA Vendor GitHub

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.

SignalValue
Severity HIGH
CISA KEV No
Public POC No
EPSS 0.0%
Patch available Yes
Active threat No
Deciding rule
HIGH severity (CVSS 7.0-8.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision
Out-of-Cycle
Exploitation
Poc
System Exposure· derived
Open
Automatable
Yes
Human Impact
High
Technical Impact
Partial

Priority Score

43
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +43
PoC: 0
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Remote Code Execution represents the critical moment when an attacker successfully runs arbitrary code on a target system without physical access.

Vendor StatusVendor

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy