Introduction
A sandbox breakout in vm2, the popular Node.js sandboxing library, allows an attacker who can execute JavaScript inside a vm2 instance to escape the sandbox entirely and run arbitrary commands on the host operating system. With a CVSS score of 9.8, no required privileges, and a public Proof of Concept already available, CVE-2026-24781 represents a serious risk for any service that evaluates user supplied code using vm2.
vm2 is an open source project designed to run untrusted JavaScript securely within a single Node.js process, mediating access to host objects through a network of ES6 Proxies and whitelisted built in modules. It is widely adopted in platforms that offer server side code evaluation, online coding playgrounds, and automation tools that need to execute user provided scripts. Its position as one of the most downloaded sandboxing libraries on npm makes vulnerabilities in vm2 relevant across a broad swath of the Node.js ecosystem.
Technical Information
Root Cause: Proxy Unwrapping via inspect
The core of CVE-2026-24781 lies in how Node's inspect method logs details of objects. To render object internals, the inspect implementation unwraps proxies. An attacker operating inside the vm2 sandbox can extract these unwrapped values using the this.seen property of the stylize function. This extraction grants direct access to the internal proxy handler of vm2, which contains the sandbox object itself.
Here is where the subtlety matters: because access to the handler is itself wrapped by a vm2 proxy, accessing the sandbox object stored in the proxy handler results in a wrapped sandbox object being passed into the sandbox. The attacker can then write a wrapped host object to the wrapped sandbox object and read the raw host object from the raw sandbox object. This completely bypasses the proxy bridge that vm2 relies on for isolation.
Handler Class Reconstruction
The canonical exploit path takes this proxy leak and escalates it through handler class reconstruction. The sequence works as follows:
-
The attacker calls
p.getPrototypeOf(p)to invoke the handler's owngetPrototypeOftrap. This returns the realBaseHandler.prototype, which carries live host functions for.set,.get, and.constructor. -
By calling
new BaseHandler(attackerObject), the attacker constructs a legitimate handler that wraps attacker controlled state. -
The
.settrap is then used to plant a host realm proxy of that attacker controlled state into attacker visible memory. This creates a cross realm read and write channel. -
With this channel established, the attacker can reach host realm objects and invoke
child_process.execSyncor equivalent functions to execute arbitrary commands on the host system.
Node Version Dependencies
The exploit's reliability varies across Node.js versions. The leak harvest path uses Buffer.prototype.slice routed through Buffer.prototype.inspect. Node 24 and newer versions tightened argument validation on these methods, which means the harvest path never fires on those versions regardless of whether vm2 is patched. However, tests confirm that the harvest path is fully reachable on Node versions 22 and below, with verified sandbox escapes on Node 18.20.7.
It is important to note that relying on Node version behavior is not a valid mitigation strategy. The v3.11.0 release patched multiple independent sandbox escape vectors, and other exploit paths may not share the same Node version constraints.
Patch Defenses in v3.11.0
The patch introduced in version 3.11.0 implements three independent layers of defense to block handler class reconstruction:
Construction token: A module local Symbol is captured in the createBridge() closure. All handler classes require this token as their first constructor argument and throw a VMError otherwise. This prevents an attacker from constructing a handler even if they obtain a reference to the constructor.
WeakMap guard: The getHandlerObject function explicitly checks if the handler is registered in the handlerToObject WeakMap. This blocks method calls on forged receivers, preventing an attacker from calling .set or .get on a handler they constructed outside the normal code path.
Constructor sentinel rebind: The .constructor property on every handler prototype is replaced with a function that unconditionally throws a VMError. This prevents prototype chain walks from reaching a callable class, cutting off the reconstruction path at its entry point.
Affected Systems and Versions
The vulnerability affects vm2 versions 3.10.3 and earlier. The fix was released in version 3.11.0.
The exploit is confirmed to be reachable on Node.js versions 22 and below, with verified escapes on Node 18.20.7. Node 24 and newer versions have tightened argument validation that prevents the specific Buffer.prototype.inspect harvest path from firing, but this should not be treated as a mitigation.
Organizations running any vm2 version prior to 3.11.0 on any Node.js version should consider themselves affected.
Vendor Security History
The vm2 maintainers have a track record of transparency about the inherent difficulty of in process JavaScript sandboxing. They openly acknowledge that researchers continuously discover new ways to escape the sandbox and actively patch vulnerabilities as they are reported.
The release of version 3.11.0 is notable for the volume of critical issues it addressed simultaneously. The following advisories were all patched in this single release:
| GHSA Advisory ID | Vulnerability Description | Impact |
|---|---|---|
| GHSA-grj5-jjm8-h35p | Array species self return sandbox escape | RCE |
| GHSA-v37h-5mfm-c47c | Handler reconstruction via util.inspect leak | RCE |
| GHSA-qcp4-v2jj-fjx8 | Trap method on leaked handler with forged target | RCE |
| GHSA-47x8-96vw-5wg6 | Cross realm symbol extraction from host objects | RCE |
| GHSA-55hx-c926-fr95 | Promise structural leak and sanitisation bypass | RCE |
| GHSA-vwrp-x96c-mhwq | Host intrinsic prototype pollution via bridge write traps | Prototype Pollution |
| GHSA-947f-4v7f-x2v8 | NodeVM builtin allowlist bypass via host passthrough | RCE |
Six out of seven advisories carry RCE impact. This concentration of critical sandbox escapes in a single release underscores the fragility of proxy based isolation and reinforces the maintainers' own recommendation that vm2 should never be the sole security boundary for untrusted code execution.
References
- NVD: CVE-2026-24781
- GitHub Security Advisory: GHSA-v37h-5mfm-c47c (Sandbox Breakout Through Inspect)
- Patch Commit: Block handler class reconstruction
- Test Commit: Switch prelude to p.getPrototypeOf(p)
- Test Commit: Add leak harvest history coverage (PoCs #1 through #6)
- Release v3.11.0
- Snyk: vm2 Package Security
- vm2 GitHub Repository



