vm2 before 3.12.2 Host Process Termination via Construct Trap
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-100722
Publishedvm2 before 3.12.2 does not apply host-side Promise rejection handling in the sandbox-to-host construct trap. In BaseHandler, the apply trap calls markHostPromiseHandled() on the returned value, but the adjacent construct path returns the result of Reflect.construct without the same sanitization. If an embedder exposes a constructable host function whose constructor returns a native rejected Promise, an untrusted script executed via VM.run can invoke it with `new` and ignore the result; the rejected host Promise crosses the bridge unhandled and, under Node's strict unhandled-rejection policy, is promoted to an uncaught exception that terminates the host process.
- Source published
- 27 Sep 2026 01:28:29 UTC
- Source modified
- 28 Sep 2026 13:51:49 UTC
- Observed here
- 23 Oct 2026 15:48:27 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 4.0 · 8.9 CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H
CVSS 3.1 · 6.8 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:N/A:H
euvd · EUVD-2026-87857
Publishedvm2 before 3.12.2 does not apply host-side Promise rejection handling in the sandbox-to-host construct trap. In BaseHandler, the apply trap calls markHostPromiseHandled() on the returned value, but the adjacent construct path returns the result of Reflect.construct without the same sanitization. If an embedder exposes a constructable host function whose constructor returns a native rejected Promise, an untrusted script executed via VM.run can invoke it with `new` and ignore the result; the rejected host Promise crosses the bridge unhandled and, under Node's strict unhandled-rejection policy, is promoted to an uncaught exception that terminates the host process.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Sep 27, 2026, 1:28:29 AM
dateUpdated: Sep 28, 2026, 1:51:49 PM
- Source published
- Sep 27, 2026, 1:28:29 AM (timezone not stated)
- Source modified
- Sep 28, 2026, 1:51:49 PM (timezone not stated)
- Observed here
- 11 Nov 2026 05:27:44 UTC
- Source terms
- ENISA EUVD · source terms and attribution apply
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.
EUVD CVSS 4.0 · 8.9 CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H
nvd · CVE-2026-100722
Publishedvm2 before 3.12.2 does not apply host-side Promise rejection handling in the sandbox-to-host construct trap. In BaseHandler, the apply trap calls markHostPromiseHandled() on the returned value, but the adjacent construct path returns the result of Reflect.construct without the same sanitization. If an embedder exposes a constructable host function whose constructor returns a native rejected Promise, an untrusted script executed via VM.run can invoke it with `new` and ignore the result; the rejected host Promise crosses the bridge unhandled and, under Node's strict unhandled-rejection policy, is promoted to an uncaught exception that terminates the host process.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Deferred
Weakness classifications: CWE-248
Original publisher date fields
published: 2026-09-27T02:17:18.817
lastModified: 2026-09-28T14:17:09.373
- Source published
- 27 Sep 2026 02:17:18 UTC
- Source modified
- 28 Sep 2026 14:17:09 UTC
- Observed here
- 25 Oct 2026 04:17:26 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 4.0 · [email protected] · Secondary · 8.9 CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
CVSS 3.1 · [email protected] · Secondary · 6.8 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:N/A:H
osv · GHSA-2v2p-6j97-cjg9
Published## Summary An untrusted script run by `VM.run` can construct an embedder-exposed host function that returns a rejected native Promise and ignore the result. The sandbox-to-host `construct` trap forwards that Promise without applying the host-side rejection handling already used by the neighboring `apply` trap, so Node's strict unhandled-rejection policy terminates the host process. ## Technical Details The precondition is an application-supplied constructable host function in the VM sandbox whose constructor body returns a native rejected Promise. In JavaScript, an object explicitly returned by a constructor replaces the newly allocated instance, so `new HostReject()` produces that Promise. `VM.run` executes the attacker-controlled source. For an ordinary host-function call, `BaseHandler.apply` invokes the host function, calls `markHostPromiseHandled(ret)`, and then wraps the result. The adjacent `BaseHandler.construct` path instead calls `Reflect.construct` and returns `thisFromOtherWithFactory(...)` without calling the same sanitizer. The rejected host Promise therefore crosses the bridge still unhandled. With Node's strict unhandled-rejection behavior, the rejection is promoted to an uncaught exception and kills the host process. The shortest path is `VM.run` → the sandbox bridge → `BaseHandler.construct`. The control changes only the guest expression: it attaches `.catch(function () {})` to the constructed Promise before ignoring it. The host function, VM configuration, rejection, and Node policy remain the same, and the control process survives. This is unintended because the repository's GHSA-gjq8 hardening explicitly marks host Promises handled at the apply boundary, while no equivalent handling exists in the adjacent construct return path. The construct path is a distinct boundary and fix surface, not a second invocation of the already-fixed apply path. ## PoV Save the following as `construct-promise-poc.js`: ```js 'use strict'; const { VM } = require(process.cwd() + '/lib/main.js'); function HostReject() { return Promise.reject(new Error('constructed-host-boom')); } const mode = process.argv[2]; const vm = new VM({ sandbox: { HostReject } }); if (mode === 'vulnerable') { console.log('VULNERABLE_STARTED'); vm.run('new HostReject(); 1'); setTimeout(() => console.log('ALIVE'), 300); } else if (mode === 'control') { vm.run('new HostReject().catch(function () {}); 1'); setTimeout(() => console.log('CONTROL_ALIVE'), 300); } else { throw new Error('usage: node construct-promise-poc.js vulnerable|control'); } ``` ## PoC Check out vm2 revision `91034466bfb7f56b95fd48083ec6ca36d058f164`, install its declared dependencies with `npm ci --ignore-scripts`, and save `construct-promise-poc.js` in that checkout's module root (the directory containing `package.json` and `lib/`). Run the script and both commands below from that same module-root directory. The script resolves `lib/main.js` from the current directory, so the test uses the checked-out vm2 source. Tested with vm2 `3.11.8` at that revision on Node.js `v26.8.1`, with `NODE_OPTIONS=--unhandled-rejections=strict`. The abort flag makes the process crash signal explicit: ```text NODE_OPTIONS=--unhandled-rejections=strict node --abort-on-uncaught-exception construct-promise-poc.js vulnerable ``` Abridged vulnerable output: ```text VULNERABLE_STARTED Error: constructed-host-boom at VM2 Wrapper.construct (.../lib/bridge.js:2340:11) at VM.run (.../lib/vm.js:613:16) ``` The process exits before printing `ALIVE` (exit status 139 in the tested execution). The stack paths are environment-dependent; the `construct` and `VM.run` frames identify the relevant target functions. The otherwise identical control is: ```text NODE_OPTIONS=--unhandled-rejections=strict node --abort-on-uncaught-exception construct-promise-poc.js control ``` Control output: ```text CONTROL_ALIVE ``` The control exits successfully. The crash is a process-level availability failure, not a guest exception caught and returned by `VM.run`. ## Impact An attacker who can submit JavaScript to a VM and who receives a constructable Promise-returning host API can terminate the Node.js process hosting the sandbox with one expression. This can take down a plugin worker, notebook kernel, queue consumer, or multi-tenant execution worker serving other users. The exploit does not require filesystem access, a Node builtin, nested VMs, or host compromise. It requires the embedder to expose the host constructor and the host to use strict unhandled-rejection handling. The impact is host availability only; this report does not claim confidentiality or integrity impact. The typed classification is CWE-248 (Uncaught Exception) and CWE-703 (Improper Check or Handling of Exceptional Conditions), with CVSS 3.1 `AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H` (8.6, High) for a deployment that accepts untrusted code over a network boundary. ## Suggested Fix Restore the bridge invariant that every host-native Promise crossing into the sandbox is marked handled before it can be ignored. In `BaseHandler.construct`, add the same `markHostPromiseHandled(ret)` call used by `BaseHandler.apply`, after the host result is produced and before it is converted and returned: ```js stripDangerousSymbolsFromHostResult(ret); markHostPromiseHandled(ret); return thisFromOtherWithFactory(getHandlerFactory(this), ret, thisFromOther(object)); ``` The call should attach the benign host-side rejection reaction without changing the Promise value or preventing sandbox code that later attaches `.catch()` or `.then(..., onRejected)` from observing the rejection. Regression tests should cover an ignored rejected Promise returned through `new`, the caught control, ordinary non-Promise constructor returns, and the existing apply-path behavior. ## Affected Package/Versions - Package: `vm2` (npm). - Confirmed vulnerable: `3.11.8`, including revision `91034466bfb7f56b95fd48083ec6ca36d058f164`. - The exact tested affected range is `3.11.8`; no broader version range is inferred from this test. ## Advisory History The public [GHSA-gjq8-xm47-88rc advisory](https://github.com/patriksimek/vm2/security/advisories/GHSA-gjq8-xm47-88rc) describes host-returned Promise rejection termination and records `3.11.8` as its patched version. Its fix and reproduction cover a host function invoked through the bridge `apply` route. This report uses the distinct `construct` route reached by `new`, where the tested `3.11.8` source still omits `markHostPromiseHandled(ret)`. A fix for `apply` does not automatically fix this adjacent return path. The checked related public history includes [GHSA-hw58-p9xv-2mjh](https://github.com/patriksimek/vm2/security/advisories/GHSA-hw58-p9xv-2mjh), the earlier sandbox-Promise constructor issue. GHSA-hw58 concerns an error raised by a Promise executor for a Promise created inside the sandbox; it is the sandbox-native `localPromise`/executor path, not GHSA-gjq8's host-returned Promise through `BaseHandler.apply` and not this host-constructor result through `BaseHandler.construct`. The checked search also found [PR #421, “Handle errors thrown in async functions”](https://github.com/patriksimek/vm2/pull/421). That is a separate async-error history item concerning errors from async functions and timer callbacks, with general unhandled-rejection handling, rather than a host Promise returned through the GHSA-gjq8 `apply` boundary or a Promise returned by an exposed constructor through this `construct` trap. The bound prior local report, titled “NodeVM crypto sanitizer exposes process-wide crypto.setFips,” covers a different root cause: an allowlisted `crypto` builtin exposes `setFips`, allowing guest code to mutate host-wide cryptographic state. Its boundary and fix surface are builtin sanitization and process-state mutation, not host-Promise rejection handling; it therefore differs from both the GHSA-gjq8 `apply` route and this `BaseHandler.construct` route. No prior report covering this construct-trap root cause was found.
- Source published
- 5 Oct 2026 23:19:55 UTC
- Source modified
- 5 Oct 2026 23:30:04 UTC
- Observed here
- 24 Oct 2026 13:17:17 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H
CVSS_V4 · CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H
Publisher rating · HIGH
osv · GHSA-7rr2-8fr3-fjqx
Withdrawn## Duplicate Advisory This advisory has been withdrawn because it is a duplicate of GHSA-2v2p-6j97-cjg9. This link is maintained to preserve external references. ## Original Description vm2 before 3.12.2 does not apply host-side Promise rejection handling in the sandbox-to-host construct trap. In BaseHandler, the apply trap calls markHostPromiseHandled() on the returned value, but the adjacent construct path returns the result of Reflect.construct without the same sanitization. If an embedder exposes a constructable host function whose constructor returns a native rejected Promise, an untrusted script executed via VM.run can invoke it with `new` and ignore the result; the rejected host Promise crosses the bridge unhandled and, under Node's strict unhandled-rejection policy, is promoted to an uncaught exception that terminates the host process.
- Source published
- 27 Sep 2026 03:31:03 UTC
- Source modified
- 5 Oct 2026 23:30:04 UTC
- Observed here
- 24 Oct 2026 13:17:17 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:N/A:H
CVSS_V4 · CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Publisher rating · HIGH