Patch Forecast
Red warning
Act within 24 hours.

CVE-2026-92944 · GHSA-27g9-p43v-cw3v

vm2 sandbox escape on Node.js 26 through a stale PromiseThenLookupChain protector

Risk score
85/100
CVSS
9.8 v3.1
Exploit chance (30 days)
0.8%
Published
1 Oct 2026

Open in the live forecast Today's forecast

What to know

What it is
Name or handle: [YMsora] Report date: 2026-08-14 The latest published vm2 release, 3.11.5, and the current main branch are vulnerable to a sandbox escape when used on Node.js 26. An ordinary fulfilled Promise created by an async function can retain an attacker-controlled constructor[Symbol.species] across Promise.prot…
Affected
vm2 (npm) >= 3.10.2, <= 3.11.6
Fix
Upgrade vm2 to 3.11.7 or later.
Exploitation
No known exploitation

Story map

Story map: vm2THE FLAWSTHE FIXCVE-2026-92944Vm2 sandbox escape on Node.js26 through a stalePromiseThenLookupChain…CVSS 9.8 · EPSS 1%CVE-2026-92946NodeVM `require.external`without an explicit`require.root` grants…CVSS 10.0 · EPSS 1%CVE-2026-92956Vm2 sandbox escape viaWebAssembly.compileStreaming Promise species bypassCVSS 10.0 · EPSS 1%CVE-2026-92937GHSA-m283-3h24-438v fixbypass leads to host RCE viacall/apply indirectionCVSS 10.0 · EPSS 1%FixUpgrade vm2 to 3.11.7 or later.Who is exposedvm2 (npm) <= 3.11.6RED · ACT TODAYvm2NodeVM's require.externaloption lets sandboxed coderequire() local files and npmpackages. When require.externalis enabled and require.root is notexplicitly set to a path thatexcludes node_modules, two…Where it standsPublic proof of conceptCONNECTS TOSame weaknessCWE-693 (CWE-693)58 other warnings in 30 days →
Story map: the flaws, the fix, how it unfolded and what it connects to. Boxes link to the record or article.

At a glance

vm2: Red, act today. NodeVM's require.external option lets sandboxed code require() local files and npm packages. When require.external is enabled and require.root is not explicitly set to a path that excludes node_modules, two defaults combine to fully defeat the sandbox: require.root defaults to unrestricted — "if omitted every path is …

The flaws

  • CVE-2026-92944: Vm2 sandbox escape on Node.js 26 through a stale PromiseThenLookupChain protector (CVSS 9.8 · EPSS 1%)
  • CVE-2026-92946: NodeVM `require.external` without an explicit `require.root` grants unrestricted host filesystem access and full RCE (CVSS 10.0 · EPSS 1%)
  • CVE-2026-92956: Vm2 sandbox escape via WebAssembly.compileStreaming Promise species bypass (CVSS 10.0 · EPSS 1%)
  • CVE-2026-92937: GHSA-m283-3h24-438v fix bypass leads to host RCE via call/apply indirection (CVSS 10.0 · EPSS 1%)

The fix

Fix: Upgrade vm2 to 3.11.7 or later.

Who is exposed: vm2 (npm) <= 3.11.6

Where it stands

  • Public proof of concept

Connects to

Why it is Red

  • CVSS 9.8 critical
  • Remotely exploitable without logging in
  • On your watchlist: node.js, npm

Affected packages

PackageEcosystemVulnerableFixed in
vm2npm>= 3.10.2, <= 3.11.63.11.7

References

Original description

## Reporter - Name or handle: `[YMsora]` - Report date: 2026-08-14 ## Summary The latest published vm2 release, **3.11.5**, and the current `main` branch are vulnerable to a sandbox escape when used on Node.js 26. An ordinary fulfilled Promise created by an async function can retain an attacker-controlled `constructor[Symbol.species]` across `Promise.prototype.finally()`. vm2 installs wrappers on the intrinsic `Promise.prototype.then` and `catch` methods. On Node.js 26 / V8 14.6, V8's `SetPrototypeProperties` path updates these existing data properties without invalidating the `PromiseThenLookupChain` protector. `Promise.prototype.finally()` subsequently trusts the stale protector and uses an internal `InvokeThen` fast path that calls the original native `then`, bypassing vm2's wrapper and its `resetPromiseSpecies(this)` hardening. The attacker-controlled species constructor therefore supplies the resolve and reject functions used by a native Promise reaction. A calibrated stack overflow at that native reaction boundary yields a raw host-realm `RangeError` to the attacker-controlled reject function. Its constructor chain reaches the host `Function` constructor and consequently the host `process` object. The attached proof is non-destructive: it reads only `process.version`. It does not execute commands, access files, or perform network requests. ## Latest-version status Verified on 2026-08-14: - npm `latest`: `vm2@3.11.5` - `vm2@3.11.5` publication time: 2026-05-18T15:37:26.138Z - npm `gitHead`: `7a1f5100b96f48d34e0fe104ab37c0acc5944f92` - npm tarball: `https://registry.npmjs.org/vm2/-/vm2-3.11.5.tgz` - npm integrity: `sha512-RSrkBiwrj6FRU+QdqNs6KG0XdlvJCjpQ4GXiqmMbrhmwfu5k/XIMpAer0L8f6iuf0uJ3a4T1xJN126Q8yf0VIA==` - GitHub default branch `main` HEAD: the same commit, also tagged `v3.11.5` - latest formal Node.js release: `v26.7.0`, V8 `14.6.202.34` The npm tarball and current GitHub `main` contain the same relevant `lib/setup-sandbox.js` Git blob. There is no unpublished fix on `main` at the time of this report. Permanent source reference: <https://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/setup-sandbox.js#L345-L390> ## Affected configurations ### vm2 versions tested on Node.js 26.7.0 | vm2 version | Result | | --- | --- | | 3.10.2 through 3.10.5 | host realm reached, 3/3 per release | | 3.11.0 | host realm reached, 3/3 | | 3.11.1 | host realm reached, 3/3 | | 3.11.2 | host realm reached, 5/5 | | 3.11.3 | host realm reached, 3/3 | | 3.11.4 | host realm reached, 5/5 | | 3.11.5 | host realm reached, 5/5 | | current `main` (`7a1f5100`) | host realm reached, 5/5 | This report therefore confirms **vm2 3.10.2 through 3.11.5** as affected when used with the vulnerable runtime. Earlier vm2 versions are not claimed: they may be independently vulnerable through older published issues, which would not establish this root cause. ### Node.js versions tested with stock vm2 3.11.5 | Node.js | V8 | Result | | --- | --- | --- | | 22.23.2 | 12.4.254.21 | safe, 5/5 | | 24.19.0 | 13.6.233.17 | safe, 5/5 | | 25.9.0 | 14.1.146.11 | safe, 5/5 | | 26.0.0 | 14.6.202.33 | host realm reached, 5/5 | | every formal release from 26.1.0 through 26.7.0 | 14.6.202.34 | host realm reached, 5/5 per release | | v27.0.0-nightly202608131b2de5e052 | 14.6.202.34 | host realm reached | | v27.0.0-v8-canary20260812f3fe529a1e | 15.3.55 | safe | The vulnerable behavior was reproduced on Linux/musl, Linux/glibc, and Windows x64. It is not Alpine-specific. ## Configuration requirements The default configuration is affected: ```js const vm = new VM(); ``` Default `new VM()` was verified 3/3 on Node.js 26.7.0 and vm2 3.11.5 with the operating system's default Node stack size. The escape also remains reproducible with stronger settings: ```js const vm = new VM({ allowAsync: true, eval: false, wasm: false, timeout: 5000 }); ``` Therefore the exploit does **not** require: - WebAssembly or JSPI; - dynamic evaluation being enabled; - Buffer; - an exposed host object; - a custom Promise supplied by the embedder; - a specific operating system; or - any challenge-specific code. The demonstrated producer uses an async function, so async support must be available. This is vm2's default. The V8 `PromiseThenLookupChain` protector must still be intact when vm2 installs its wrappers. A fresh/default Node.js process satisfies this condition. An unrelated earlier mutation that correctly invalidates the protector can make this exact path safe, but that is not a documented vm2 mitigation and is not present in the default setup. ## Reproduction The attachment `poc.js` uses only the published npm package and prints a benign host-version marker. ```sh mkdir vm2-node26-repro cd vm2-node26-repro npm init -y npm install --ignore-scripts --no-audit --no-fund vm2@3.11.5 cp /path/to/poc.js . node poc.js ``` Alternatively, with the official Node.js 26.7.0 container: ```sh docker run --rm -v "$PWD:/poc:ro" -w /tmp node:26.7.0-bookworm-slim sh -lc ' npm init -y >/dev/null 2>&1 && npm install --ignore-scripts --no-audit --no-fund vm2@3.11.5 >/dev/null 2>&1 && cp /poc/poc.js . && node poc.js ' ``` Representative output: ```json { "node": "v26.7.0", "v8": "14.6.202.34-node.28", "vm2": "3.11.5", "config": "default", "result": "HOST", "hostVersion": "v26.7.0", "speciesCalls": 1, "localError": false, "localRangeError": false } ``` The calibrated depth varies with the platform and process layout; this is expected. The PoC searches the boundary instead of relying on a fixed depth. To verify that disabling eval and WebAssembly is not sufficient: ```sh STRICT=1 node poc.js ``` ## Technical root cause 1. vm2 replaces the intrinsic `Promise.prototype.then` and `catch` methods with wrappers that sanitize callbacks and execute `resetPromiseSpecies(this)`. 2. These two prototype writes are consecutive direct assignments in `lib/setup-sandbox.js`. 3. V8 14.6 enables the `p

Last activity 1 Oct 2026. Patch Forecast updates every hour.