Automated Threat Modeling

vm2 CVE-2026-47209: Brief Summary of the Bridge Proxy Set Trap Receiver Bypass

A short review of CVE-2026-47209, a high-severity sandbox escape in the vm2 Node.js library. The Bridge Proxy set trap ignores the receiver parameter, so sandbox code can write properties directly to host objects. Includes patch analysis and affected-version details.

CVE Analysis

12 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-06-12

vm2 CVE-2026-47209: Brief Summary of the Bridge Proxy Set Trap Receiver Bypass
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A flaw in how the vm2 Node.js sandbox library handles JavaScript's Proxy set trap lets untrusted sandbox code write properties directly onto host objects. That breaks the isolation guarantee vm2 exists to provide. With over 56 million total npm downloads and roughly 1 million weekly downloads, vm2 is one of the most widely deployed JavaScript sandboxing libraries. It runs in code execution platforms, online IDEs, and, increasingly, AI agent frameworks that evaluate LLM-generated JavaScript.

vm2 is an open-source advanced sandbox for Node.js, maintained by Patrik Simek, that lets embedders run untrusted JavaScript with whitelisted Node built-in modules. The repository has 4,100+ stars and 324 forks on GitHub. AI agent frameworks rely on vm2 to execute code produced by large language models safely, so a sandbox escape there can turn a prompt injection into host-level remote code execution.

CVE-2026-47209 has a CVSS v3.1 score of 8.6 (HIGH). The attack vector is network, no privileges or user interaction are required, and the scope is changed, meaning the impact crosses the sandbox boundary. The vulnerability affects all vm2 versions from 3.0.0 through 3.11.3 and was patched in version 3.11.4.

Technical Information

Root Cause: The Ignored receiver Parameter

The defect is in the BaseHandler.set trap in lib/bridge.js, at line 1231. It comes down to how JavaScript's [[Set]] internal method interacts with Proxy objects.

Per ECMA-262 §9.5.9, when a property write walks up a prototype chain and reaches a Proxy, the engine passes four arguments into the set trap: target, property, value, and receiver. The receiver is the object the assignment started on. When receiver !== proxy (for example, when a child object inherits from the proxy via Object.create), the specification requires the assignment to create an own property on the receiver, not on the proxy's underlying target.

vm2 ignored the receiver parameter completely. It always called otherReflectSet(object, key, value) against the host target object. Any property write on an object that prototypically inherits from a sandboxed proxy therefore leaks through to the host-side target and crosses the sandbox boundary.

Classification: CWE-693 Protection Mechanism Failure

This vulnerability is classified under CWE-693, defined by MITRE as a weakness where "the product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks." Here the protection mechanism is the set trap's handling of receiver, and it is insufficient. The trap has no boundary check at all to tell a write to the proxy target apart from a write to a child receiver.

Attack Flow

Exploitation takes three steps:

  1. Sandbox code creates a child object via Object.create(proxy), where proxy is a bridged reference to a host object.

  2. The sandbox code writes a property on the child. Because the receiver (the child) is not the proxy itself, the Proxy specification says the write should create an own property on the child and never touch the host object.

  3. Because of the bug, the set trap ignores the receiver and calls otherReflectSet(object, key, value), so the inherited write lands on the host object.

The write becomes dangerous when the key is a cross-realm Symbol such as nodejs.util.promisify.custom. Symbol-keyed properties like this cause semantic confusion attacks, where the host runtime interprets the injected property in a way that changes how built-in Node.js APIs behave. The inherited write path would also bypass any isDangerousCrossRealmSymbol guard added to the direct set path in the future, because it never goes through that guard.

According to Aikido Intel, this protection mechanism failure can be chained to execute arbitrary code in the host Node.js process, which escalates a boundary violation into full remote code execution.

CVSS Scoring Breakdown

CVSS Metricv3.1 ValueInterpretation
Attack VectorNetwork (AV:N)Exploitable remotely over the network
Attack ComplexityLow (AC:L)No specialized conditions required
Privileges RequiredNone (PR:N)No authentication needed
User InteractionNone (UI:N)No user action required
ScopeChanged (S:C)Impact crosses sandbox boundary
ConfidentialityNone (C:N)No direct information disclosure
IntegrityHigh (I:H)Full control over integrity of host objects
AvailabilityNone (A:N)No direct availability impact

The CVE also has a CVSS v4.0 score, CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:U/U:Amber. Its E:U value records the exploit status as "Unreported" on the assessment date.

Patch Information

Maintainer Patrik Simek patched the vulnerability in commit 26d0318 on May 17, 2026. The fix shipped in vm2 v3.11.4 (released May 18, 2026) under advisory GHSA-c4cf-2hgv-2qv6. The release was a pure security patch with no API changes.

How the Fix Works

Before the fix, BaseHandler.set in lib/bridge.js ignored the receiver argument and forwarded every write to the host object via otherReflectSet(object, key, value). The fix adds a receiver identity check. A write reaches the host object only when the receiver is the canonical bridge proxy, and every other receiver goes to a safe install-on-receiver path.

The change adds 24 lines to BaseHandler.set in lib/bridge.js. The new logic sits after the existing __proto__ and constructor-on-array short-circuit checks and before the host-write forwarding path:

// SECURITY (GHSA-c4cf-2hgv-2qv6): honour ECMA-262 9.5.9 [[Set]] // receiver semantics. const canonicalProxy = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [object]); if (receiver !== canonicalProxy) { return thisReflectDefineProperty(receiver, key, { __proto__: null, value: value, writable: true, enumerable: true, configurable: true, }) === true; }

The logic runs in three steps:

  1. The fix looks up the bridge's canonical sandbox-facing proxy for the given host object in mappingOtherToThis. vm2 already populates this WeakMap during proxy construction on both the !isHost (inner proxy) and isHost (outer proxy2) code paths, so the fix needs no new bridge state.

  2. It compares the trap's receiver to that canonical proxy. If receiver === canonicalProxy, the write came directly through the proxy (e.g., hostProxy.x = v or Reflect.set(hostProxy, k, v, hostProxy)), and the code falls through to the existing, legitimate otherReflectSet forwarding path. This preserves the embedder contract that direct writes to sandbox-exposed host objects still work.

  3. When receiver !== canonicalProxy, the write came from an inheriting sandbox-side child (e.g., Object.create(hostProxy).x = v) or from a forged Reflect.set call with a custom receiver. In that case the trap installs the property on the receiver itself via Reflect.defineProperty, and the host object is not touched at all. The descriptor has a null prototype (__proto__: null), which prevents prototype pollution of the descriptor itself, and it creates a standard writable/enumerable/configurable data property on the receiver.

Consistency Across Handler Types

Two other vm2 handlers already worked this way. ReadOnlyHandler.set uses the same install-on-receiver shape unconditionally (since its policy is "host is read only"), and ProtectedHandler.set does the same in its function-value branch. For non-function values, ProtectedHandler.set delegates to super.set and so inherits this fix automatically. All three handler types now follow the spec.

Test Coverage

The commit includes a five-variant test suite in test/ghsa/GHSA-c4cf-2hgv-2qv6/repro.js covering:

  • Symbol-key inherited write via Object.create(hostFn)[Symbol.for(...)] = fn
  • Plain string-key inherited write
  • Forged Reflect.set with an explicit non-proxy receiver
  • Deep prototype-chain write (Object.create(Object.create(hostObj)))
  • Object.assign(Object.create(hostObj), src) bulk pollution
  • A negative control test confirming that direct proxy.x = v writes still correctly mutate the host object

The commit message says the fix restores "Defense Invariant #1" and adds a new corollary. A sandbox-originated write reaches a host-realm object only when the spec [[Set]] receiver equals the canonical bridge proxy for that object's target. Every other write path stays confined to the sandbox-side receiver.

Affected Systems and Versions

All vm2 versions from 3.0.0 through 3.11.3 are affected. The vulnerability is patched in version 3.11.4.

Version RangeStatus
< 3.0.0Not affected (predates the vulnerable code)
3.0.0 through 3.11.3Vulnerable
3.11.4 and laterPatched

The npm registry lists 3.11.5 as the latest published version.

Any application that uses vm2 to execute untrusted or semi-trusted JavaScript may be affected. That includes code execution platforms, online IDEs, plugin sandboxing systems, and AI agent frameworks that evaluate LLM-generated code.

Vendor Security History

vm2 has a well-documented history of sandbox escape vulnerabilities. The table below lists the known CVEs across the library's lifecycle:

CVE IDAffected VersionsPatched VersionSeverityDescription
CVE-2023-29017Prior to 3.9.153.9.15HighSandbox escape via Proxy handler exploitation
CVE-2023-30547Prior to 3.9.173.9.17CriticalException sanitization bypass
CVE-2023-37466Prior to 3.9.183.9.18HighInsufficient fix for prior CVEs
CVE-2026-22709<= 3.10.13.10.2CriticalPromise callback sanitization bypass enabling RCE
CVE-2026-24118< 3.11.03.11.0HighSandbox escape
CVE-2026-24120< 3.10.53.10.5HighInsufficient fix for CVE-2023-37466
CVE-2026-24781< 3.11.03.11.0CriticalSandbox breakout via inspect function
CVE-2026-26332< 3.11.03.11.0HighSandbox escape
CVE-2026-26956< 3.10.53.10.5CriticalSandbox escape via TypeError / Symbol to string conversion
CVE-2026-43997< 3.11.03.11.0HighSandbox escape
CVE-2026-43999< 3.11.03.11.0HighSandbox escape
CVE-2026-44005< 3.11.03.11.0HighSandbox escape
CVE-2026-44006< 3.11.03.11.0HighSandbox escape
CVE-2026-44007< 3.11.13.11.1HighSandbox escape
CVE-2026-44008< 3.11.23.11.2HighSandbox escape
CVE-2026-44009< 3.11.23.11.2HighSandbox escape
CVE-2026-47209< 3.11.43.11.48.6 (High)Bridge Proxy set trap ignores receiver parameter

At least 13 CVEs were disclosed in 2026 alone. That count shows systemic weaknesses in vm2's Proxy-based isolation model, and individual patches cannot fix them. The project was deprecated from July 2023 to October 2025 because of critical security issues, and the maintainer has acknowledged that new bypasses will likely be discovered. Endor Labs recommends against using vm2 for attacker-controlled code in production environments.

Kodem Security calls the 2026 wave of vm2 vulnerabilities particularly concerning for AI agent frameworks, where a sandbox escape can turn a prompt injection into host-level RCE. Microsoft's name for this pattern is "Prompts Become Shells."

Behavioral Indicators of Compromise

For teams monitoring for exploitation of vm2 sandbox escapes in general, Kodem Security lists these behavioral IOCs:

IOC CategoryIndicators
Processnode processes spawning child_process.exec or spawn from sandboxed contexts
Shell activityShell invocations (sh, bash, cmd.exe) parented to Node.js
Credential accessFilesystem reads of ~/.aws/credentials, ~/.npmrc, /proc/self/environ
NetworkUnexpected outbound DNS or HTTPS from agent runtimes

Strategic Recommendation

Organizations using vm2 should work on two tracks. In the short term, apply the 3.11.4 patch immediately. Over the longer term, evaluate a move to sandboxing approaches that do not share vm2's Proxy-based architecture and its pattern of vulnerabilities, such as process-level isolation with Firecracker or gVisor. Running vm2 on top of container-level or process-level isolation adds defense in depth for when the next, inevitable bypass arrives.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization