May 5, 2026·6 min read·43 visits
A critical sandbox escape (CVSS 9.8) in vm2 allows attackers to achieve arbitrary code execution by exploiting WebAssembly try_table and JSTag instructions to leak un-sanitized host-realm objects.
vm2 versions 3.10.4 and below are vulnerable to a critical sandbox escape flaw resulting in unauthenticated remote code execution. Attackers can leverage Node.js v25 WebAssembly (WASM) exception handling mechanisms to bypass JavaScript-level error instrumentation and gain access to the host-realm execution context.
The vm2 library provides a Node.js sandbox environment designed to execute untrusted code securely. It achieves this isolation primarily through source-to-source transformation and JavaScript Proxy objects, intercepting sensitive operations and sanitizing errors before they reach the guest execution context. Version 3.10.4 and all prior versions contain a critical flaw in this instrumentation layer.
The vulnerability, designated as CVE-2026-26956, arises from an architectural mismatch between JavaScript-level sandboxing and low-level engine capabilities introduced in Node.js v25. Specifically, the introduction of WebAssembly (WASM) exception handling instructions creates an execution path that operates outside the visibility of vm2's source-to-source transformers. Attackers can exploit this blind spot to intercept raw host-realm objects.
By leaking a host-realm object into the guest context, an attacker breaks the core isolation boundary of the sandbox. The guest code can then traverse the prototype chain of the leaked object to access fundamental host constructors. This ultimately provides access to the global Function constructor, enabling the execution of arbitrary system commands through the host's child_process module.
The root cause of CVE-2026-26956 is a protection mechanism failure (CWE-693) involving incomplete interception of cross-realm exceptions. vm2 secures the execution environment by rewriting all JavaScript catch clauses during script compilation. The injected handleException() function intercepts thrown errors, ensuring that host-realm error objects are replaced with sanitized guest-realm equivalents before the sandboxed code can interact with them.
Node.js v25 introduced support for the WebAssembly try_table instruction and the WebAssembly.JSTag interface. These features allow WASM modules to catch exceptions directly at the V8 C++ engine level. Because this interception occurs within compiled bytecode rather than JavaScript, vm2's source-to-source transformation cannot inject the necessary handleException() sanitization routine into the WASM control flow.
When a host-realm error is triggered and subsequently caught by the WASM try_table block, the V8 engine returns the raw exception object to the WASM module as an externref. The WASM module can then return this unsanitized externref directly to the guest JavaScript context. This bypasses all proxy handlers and error sanitization routines, providing the guest context with a direct reference to a host object.
The mitigation strategy for this vulnerability required multiple commits across different components of the vm2 architecture. The primary fix targets the WASM execution environment. In commit 1fbdeff743d48fb1416964777f5947057f6f1295, the maintainers removed WebAssembly.JSTag from the sandbox globals. Without JSTag, WASM modules lose the capability to identify and catch JavaScript exceptions, neutralizing the primary escape vector.
// lib/setup-sandbox.js (Vulnerable)
Object.defineProperty(global.WebAssembly, 'JSTag', {
value: WebAssembly.JSTag,
configurable: true,
writable: true
});
// lib/setup-sandbox.js (Patched - 1fbdeff7)
// WebAssembly.JSTag is explicitly omitted from the guest context.
delete global.WebAssembly.JSTag;The vulnerability also exposed weaknesses in how vm2 handles modern ECMAScript error types. Commit a6cd917dddf6f5d5da3f0883977a0342eb3c204f updates the handleException() function to recursively sanitize SuppressedError instances. SuppressedError is a feature of the ES2024 Explicit Resource Management API, which can encapsulate multiple host-realm errors. The patch ensures that both the .error and .suppressed properties are properly evaluated and scrubbed before returning to the guest.
Finally, commit ebcfe94ad2f864f0bc35e78cff1d921107cfd160 implements deeper hardening of the lib/bridge.js file. The patch explicitly caches and blocks code-executing constructors from crossing the proxy bridge. Commit 57971fa423abeb66f09e47e18102986549474ca8 complements this by moving bridge handler methods into closure-scoped functions, preventing attackers from accessing internal bridge methods via util.inspect property enumeration.
Exploiting this vulnerability requires the attacker to supply a malicious script to an application utilizing a vulnerable version of vm2 running on Node.js v25 or higher. The first step in the exploit chain involves triggering a specific host-realm error. The attacker creates a standard JavaScript Error object and overrides its name property with a Symbol. When Node.js attempts to format the stack trace for this error, the engine throws a host-realm TypeError due to the invalid string coercion of the Symbol.
The attacker must then compile and instantiate a WebAssembly module within the guest context. This WASM module is constructed to import a JavaScript function that triggers the previously prepared host error. The module's execution block utilizes the try_table instruction, combined with WebAssembly.JSTag, to catch the resulting exception. Because the exception is caught in WASM, it evades vm2's catch instrumentation.
Once the WASM module catches the error, it exports a function that returns the caught exception to the guest JavaScript context. The guest code receives the raw, unsanitized host-realm TypeError. The attacker accesses the constructor.constructor property of this error object to retrieve the host-realm global Function constructor.
Invoking this host Function constructor allows the attacker to define and execute arbitrary JavaScript within the host context. By executing HostFunction("return process")(), the attacker obtains a reference to the host's process object. From there, the attacker can access process.mainModule.require('child_process').execSync('id'), achieving arbitrary command execution on the underlying operating system.
The impact of CVE-2026-26956 is critical, carrying a CVSS v3.1 score of 9.8. Successful exploitation results in a complete failure of the sandbox isolation boundary, leading directly to unauthenticated remote code execution. Because vm2 is frequently used to evaluate untrusted code in multi-tenant environments, continuous integration platforms, and serverless architectures, a successful escape compromises the entire host application.
An attacker who executes arbitrary code via this vulnerability gains the same privileges as the Node.js process running the vm2 instance. This typically allows the attacker to read sensitive environment variables, access local file systems, establish reverse shells, and pivot to internal network resources. The vulnerability requires no user interaction and can be triggered by submitting standard JavaScript payloads.
While the EPSS score currently sits at 0.00092 (25.50th percentile), indicating low widespread exploitation in the wild, the public availability of the exploitation methodology increases the risk for targeted attacks. Applications that expose a web-based code evaluation endpoint using vm2 are at immediate risk of complete system compromise.
The immediate remediation for this vulnerability is to upgrade vm2 to version 3.10.5. This version correctly removes WebAssembly.JSTag from the guest context and includes comprehensive hardening against SuppressedError leaks and prototype enumeration. Administrators should verify the updated version by checking their package lockfiles to ensure no transitive dependencies are pulling in vulnerable versions of the library.
If upgrading is not immediately feasible, organizations can temporarily mitigate the WASM-based attack vector by disabling WebAssembly support within the sandbox configuration. Instantiating the sandbox with the option wasm: false prevents the compilation of the malicious WebAssembly module required for the primary escape chain. However, this configuration change does not protect against the secondary ES2024 SuppressedError vectors patched in 3.10.5.
Crucially, the maintainers of vm2 have announced that the project is officially deprecated and discontinued. The architectural limitations of source-to-source transformation make it increasingly difficult to secure against continuous changes to the V8 engine and the ECMAScript specification. Organizations currently relying on vm2 must plan a migration to more robust, isolate-based sandboxing solutions, such as isolated-vm, to maintain long-term security.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
vm2 Patrik Simek | <= 3.10.4 | 3.10.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-693 (Protection Mechanism Failure) |
| Attack Vector | Network (Unauthenticated) |
| CVSS v3.1 | 9.8 (Critical) |
| Impact | Remote Code Execution / Sandbox Escape |
| Exploit Status | Proof of Concept Available |
| Vulnerable Component | Error instrumentation / handleException() |
Failure of the sandbox's source-to-source transformation to instrument exceptions caught at the WASM/C++ layer.