CVE-2026-100722
vm2 before 3.12.2 Host Process Termination via Construct Trap
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.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
8.9
vm2 before 3.12.2 Host Process Termination via Construct Trap
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.
- Record
CVE-2026-100722- CVSS 4.0
- 8.9 High
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 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:N/A:H - Weakness
- CWE-248
- Updated
- 2026-11-02
EUVDEUVD-2026-87857
8.9
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.
- Record
EUVD-2026-87857- CVSS 4.0
- 8.9 High
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 - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-2v2p-6j97-cjg9
8.9
vm2: Host Promise rejection from an exposed constructor can terminate the vm2 host process
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:
'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:
NODE_OPTIONS=--unhandled-rejections=strict node --abort-on-uncaught-exception construct-promise-poc.js vulnerable
Abridged vulnerable output:
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:
NODE_OPTIONS=--unhandled-rejections=strict node --abort-on-uncaught-exception construct-promise-poc.js control
Control output:
CONTROL_ALIVE
The control exits successfully. The crash is a process-level…
- Record
GHSA-2v2p-6j97-cjg9- CVSS 4.0
- 8.9 High
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
- 8.6 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H - Weakness
- CWE-248, CWE-703
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-7rr2-8fr3-fjqx
8.9
Duplicate Advisory: vm2: Host Promise rejection from an exposed constructor can terminate the vm2 host process
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.
- Record
GHSA-7rr2-8fr3-fjqx- CVSS 4.0
- 8.9 High
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
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:N/A:H - Weakness
- CWE-248
- Updated
- 2026-11-02
NVD
8.9
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.
- Record
CVE-2026-100722- CVSS 4.0
- 8.9 High
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
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:N/A:H - Weakness
- CWE-248
- Updated
- 2026-11-02
OSV
8.9
vm2 before 3.12.2 Host Process Termination via Construct Trap
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.
- Record
CVE-2026-100722- CVSS 4.0
- 8.9 High
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 - Weakness
- CWE-248
- Updated
- 2026-11-02
Show 6 moreShow less
OSVGHSA-2v2p-6j97-cjg9
8.9
vm2: Host Promise rejection from an exposed constructor can terminate the vm2 host process
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:
'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:
NODE_OPTIONS=--unhandled-rejections=strict node --abort-on-uncaught-exception construct-promise-poc.js vulnerable
Abridged vulnerable output:
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:
NODE_OPTIONS=--unhandled-rejections=strict node --abort-on-uncaught-exception construct-promise-poc.js control
Control output:
CONTROL_ALIVE
The control exits successfully. The crash is a process-level…
- Record
GHSA-2v2p-6j97-cjg9- CVSS 4.0
- 8.9 High
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
- 8.6 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H - Weakness
- CWE-248, CWE-703
- Updated
- 2026-11-02
OSVGHSA-7rr2-8fr3-fjqx
8.9
Duplicate Advisory: vm2: Host Promise rejection from an exposed constructor can terminate the vm2 host process
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.
- Record
GHSA-7rr2-8fr3-fjqx- CVSS 4.0
- 8.9 High
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
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:N/A:H - Weakness
- CWE-248
- Updated
- 2026-11-02
OSVCLSA-2026-1791279062 No score
TuxCare security update for vm2 (8 CVEs)
TuxCare rebuilt vm2 to address 8 CVEs. Fixed in 3.11.7-tuxcare.1.
- Record
CLSA-2026-1791279062- Updated
- 2026-11-02
OSVCLSA-2026-1791282796 No score
TuxCare security update for vm2 (3 CVEs)
TuxCare rebuilt vm2 to address 3 CVEs. Fixed in 3.11.5-tuxcare.5.
- Record
CLSA-2026-1791282796- Updated
- 2026-11-02
OSVCLSA-2026-1791666488 No score
TuxCare security update for vm2 (4 CVEs)
TuxCare rebuilt vm2 to address 4 CVEs. Fixed in 3.10.0-tuxcare.7.
- Record
CLSA-2026-1791666488- Updated
- 2026-11-02
OSVROOT-APP-NPM-CVE-2026-100722 No score
CVE-2026-100722 in vm2 - Patched by Root
Root has patched CVE-2026-100722 in the vm2 package for Root:npm. Multiple fixed versions available.
- Record
ROOT-APP-NPM-CVE-2026-100722- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
vm2npm |
through 3.12.1 | 3.12.2 |
vm2TuxCare:npm |
3.11.7-tuxcare.0 up to 3.11.7-tuxcare.1, plus listed versions 3.11.7 |
3.11.7-tuxcare.1 |
vm2TuxCare:npm |
3.11.5-tuxcare.0 up to 3.11.5-tuxcare.5, plus listed versions 3.11.53.11.5-tuxcare.13.11.5-tuxcare.23.11.5-tuxcare.33.11.5-tuxcare.4 |
3.11.5-tuxcare.5 |
vm2TuxCare:npm |
3.10.0-tuxcare.0 up to 3.10.0-tuxcare.7, plus listed versions 3.10.03.10.0-tuxcare.13.10.0-tuxcare.23.10.0-tuxcare.33.10.0-tuxcare.43.10.0-tuxcare.53.10.0-tuxcare.6 |
3.10.0-tuxcare.7 |
vm2npm |
before 3.12.2 | 3.12.2 |
vm2Root:npm |
before 3.11.6-aikido.1 | 3.11.6-aikido.1 |
@rootio/vm2Root:npm |
before 3.11.6-root.io.1 | 3.11.6-root.io.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-100722 is a high vulnerability (CVSS 8.9) in Patriksimek Vm2, published September 27, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.32%, the 24th percentile. Fixed in vm2 3.12.2.
References
github.com/patriksimek/vm2/security/advisories/GHSA-2v2p-6j97-cjg9
vulncheck.com/advisories/vm2-before-3.12.2-host-process-termination-via-construct-trap
nvd.nist.gov/vuln/detail/CVE-2026-100722
github.com/patriksimek/vm2/commit/6e487df9518e455e5e60faec3df3a0e2e27f9940
github.com/patriksimek/vm2/releases/tag/v3.12.2
github.com/advisories/GHSA-2v2p-6j97-cjg9
github.com/advisories/GHSA-7rr2-8fr3-fjqx
github.com/CVEProject/cvelistV5/tree/main/cves/2026/100xxx/CVE-2026-100722.json
github.com/patriksimek/vm2
Left out here: the rest of 2 source descriptions. The JSON record has them.