Introduction
A sandbox breakout in vm2, the popular Node.js sandboxing library, lets an attacker who can execute JavaScript inside a vm2 instance escape the sandbox entirely and run arbitrary commands on the host operating system. CVE-2026-24781 has a CVSS score of 9.8, requires no privileges, and already has a public proof of concept. That makes it a serious risk for any service that evaluates user-supplied code with vm2.
vm2 is an open-source project designed to run untrusted JavaScript securely within a single Node.js process. It mediates 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, in online coding playgrounds, and in automation tools that need to execute user-provided scripts. Because it is one of the most downloaded sandboxing libraries on npm, a vulnerability in vm2 reaches a broad swath of the Node.js ecosystem.
Technical Information
Root Cause: Proxy Unwrapping via inspect
CVE-2026-24781 starts with how Node's inspect method logs object details. To render object internals, the inspect implementation unwraps proxies. An attacker inside the vm2 sandbox can extract these unwrapped values through the this.seen property of the stylize function. That gives the attacker direct access to vm2's internal proxy handler, which contains the sandbox object itself.
Access to the handler is itself wrapped by a vm2 proxy, so accessing the sandbox object stored in the proxy handler passes a wrapped sandbox object into the sandbox. The attacker can then write a wrapped host object to the wrapped sandbox object and read the raw host object back from the raw sandbox object. This completely bypasses the proxy bridge that vm2 relies on for isolation.
Handler Class Reconstruction
The canonical exploit path escalates this proxy leak through handler class reconstruction, in four steps:
-
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 attacker uses the
.settrap to plant a host-realm proxy of that attacker-controlled state into attacker-visible memory. This creates a cross-realm read and write channel. -
Through this channel, the attacker can reach host-realm objects and invoke
child_process.execSyncor an equivalent function 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 tightened argument validation on these methods, so on those versions the harvest path never fires, whether or not vm2 is patched. On Node 22 and below, tests confirm the harvest path is fully reachable, with verified sandbox escapes on Node 18.20.7.
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
Version 3.11.0 blocks handler class reconstruction with three independent layers of defense.
Construction token: A module-local Symbol is captured in the createBridge() closure. Every handler class requires this token as its first constructor argument and throws a VMError without it. An attacker who obtains a reference to the constructor still cannot construct a handler.
WeakMap guard: The getHandlerObject function explicitly checks whether the handler is registered in the handlerToObject WeakMap. This blocks method calls on forged receivers, so an attacker cannot call .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. Prototype-chain walks can no longer reach a callable class, which cuts 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 reachable on Node.js versions 22 and below, with verified escapes on Node 18.20.7. Node 24 and newer tightened argument validation in a way that stops the specific Buffer.prototype.inspect harvest path from firing, but this should not be treated as a mitigation.
Organizations running any vm2 version before 3.11.0, on any Node.js version, should consider themselves affected.
Vendor Security History
The vm2 maintainers have a track record of being open about how hard in-process JavaScript sandboxing is. They acknowledge that researchers continuously discover new ways to escape the sandbox, and they actively patch vulnerabilities as they are reported.
Version 3.11.0 is notable for how many critical issues it fixed at once. All of the following advisories were patched in that 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 of the seven advisories carry RCE impact. That many critical sandbox escapes in one release shows how fragile proxy-based isolation is, and it supports 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



