CVE-2026-92959
vm2 before 3.11.8 allowAsync Bypass via Promise Thenable
vm2 before 3.11.8 does not fully enforce the allowAsync: false option in VM and NodeVM. While localPromise.prototype.then is replaced with a handler that throws 'Async not available', the sandbox's Promise static methods (Promise.resolve, Promise.all, Promise.race, Promise.any, and Promise.allSettled) still assimilate attacker-supplied thenables: native promise resolution performs PromiseResolveThenableJob and invokes the sandboxed code's then method in a microtask without passing through the patched then, so the async restriction is never applied. As a result, sandboxed script can schedule work that runs after VM.run() or NodeVM.run() has returned and outside the configured timeout, continuing to execute after the host believes execution is complete.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- Low
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
7.1
vm2 before 3.11.8 allowAsync Bypass via Promise Thenable
vm2 before 3.11.8 does not fully enforce the allowAsync: false option in VM and NodeVM. While localPromise.prototype.then is replaced with a handler that throws 'Async not available', the sandbox's Promise static methods (Promise.resolve, Promise.all, Promise.race, Promise.any, and Promise.allSettled) still assimilate attacker-supplied thenables: native promise resolution performs PromiseResolveThenableJob and invokes the sandboxed code's then method in a microtask without passing through the patched then, so the async restriction is never applied. As a result, sandboxed script can schedule work that runs after VM.run() or NodeVM.run() has returned and outside the configured timeout, continuing to execute after the host believes execution is complete.
- Record
CVE-2026-92959- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 7.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H - Weakness
- CWE-693
- Updated
- 2026-11-02
EUVDEUVD-2026-81608
7.1
vm2 before 3.11.8 does not fully enforce the allowAsync: false option in VM and NodeVM. While localPromise.prototype.then is replaced with a handler that throws 'Async not available', the sandbox's Promise static methods (Promise.resolve, Promise.all, Promise.race, Promise.any, and Promise.allSettled) still assimilate attacker-supplied thenables: native promise resolution performs PromiseResolveThenableJob and invokes the sandboxed code's then method in a microtask without passing through the patched then, so the async restriction is never applied. As a result, sandboxed script can schedule work that runs after VM.run() or NodeVM.run() has returned and outside the configured timeout, continuing to execute after the host believes execution is complete.
- Record
EUVD-2026-81608- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-9538-79j2-h62h
7.1
Duplicate Advisory: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-f8gf-w286-fmq2. This link is maintained to preserve external references.
Original Description
vm2 before 3.11.8 does not fully enforce the allowAsync: false option in VM and NodeVM. While localPromise.prototype.then is replaced with a handler that throws 'Async not available', the sandbox's Promise static methods (Promise.resolve, Promise.all, Promise.race, Promise.any, and Promise.allSettled) still assimilate attacker-supplied thenables: native promise resolution performs PromiseResolveThenableJob and invokes the sandboxed code's then method in a microtask without passing through the patched then, so the async restriction is never applied. As a result, sandboxed script can schedule work that runs after VM.run() or NodeVM.run() has returned and outside the configured timeout, continuing to execute after the host believes execution is complete.
- Record
GHSA-9538-79j2-h62h- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N/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
- 7.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H - Weakness
- CWE-693
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-f8gf-w286-fmq2
7.1
vm2: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM
Summary
When allowAsync is set to false, vm2 is expected to reject attempts to run asynchronous code. Direct use of Promise.prototype.then is blocked, but Promise static methods still assimilate attacker-controlled thenables. Promise.resolve(thenable), Promise.all([thenable]), Promise.race([thenable]), Promise.any([thenable]), and Promise.allSettled([thenable]) can invoke the thenable's then method in a microtask after VM.run() or NodeVM.run() has already returned.
This bypasses the documented async-execution restriction and runs outside the configured timeout, allowing sandboxed code to continue executing after the host believes execution is complete.
Details
The documented VM option says allowAsync: false should cause attempts to run async code to throw a VMError; README.md:139-145 also recommends using it with timeout. The implementation enforces part of this policy by replacing localPromise.prototype.then with an AsyncErrorHandler when async is disabled:
lib/setup-sandbox.js:1629-1637definesAsyncErrorHandler, whoseapplyandconstructtraps throwVMError: Async not available.lib/setup-sandbox.js:1743-1752installs that handler onlocalPromise.prototype.thenwhenallowAsyncis false.
However, Promise static methods are still exposed and rebound to localPromise:
lib/setup-sandbox.js:1816-1819wrapsPromise.all.lib/setup-sandbox.js:1821-1824wrapsPromise.race.lib/setup-sandbox.js:1826-1830wrapsPromise.allSettled.lib/setup-sandbox.js:1833-1837wrapsPromise.any.lib/setup-sandbox.js:1840-1843wrapsPromise.resolve.
Those wrappers prevent species attacks by forcing localPromise as the constructor, but they do not reject or neutralize thenables when allowAsync is false. Native Promise resolution then performs PromiseResolveThenableJob and calls the attacker-controlled then method asynchronously. That job does not go through the patched localPromise.prototype.then method, so the AsyncErrorHandler is never reached.
NodeVM inherits the same sandbox Promise setup through VM (lib/nodevm.js:328-332), so the same thenable-assimilation bypass is reachable in NodeVM as well.
The transformer fast path is not the root cause, but it explains why the minimal PoC is parser-independent: payloads below contain none of catch, import, async, with, the internal state identifier, or \u, so lib/transformer.js:82-89 returns without AST parsing.
PoC
Maintainer-runnable clean-checkout recipe:
npm install
node - <<'NODE'
const {VM, NodeVM} = require('./');
async function runVmCase(name, code) {
const events = [];
const vm = new VM({allowAsync: false, timeout: 10, sandbox: {mark: value => events.push(value)}});
try {
const ret = vm.run(code);
console.log(`${name}: returned ${ret}`);
} catch (e) {
console.log(`${name}: threw ${e.name}:${e.message}`);
}
await new Promise(resolve => setImmediate(resolve));
console.log(`${name} events: ${events.length ? events.join(',') : '<none>'}`);
}
async function runNodeVmCase(name, code) {
const events = [];
const vm = new NodeVM({allowAsync: false, sandbox: {mark: value => events.push(value)}});
try {
const ret = vm.run(code);
console.log(`${name}: returned ${ret}`);
} catch (e) {
console.log(`${name}: threw ${e.name}:${e.message}`);
}
await new Promise(resolve => setImmediate(resolve));
console.log(`${name} events: ${events.length ? events.join(',') : '<none>'}`);
}
(async () => {
await runVmCase('VM Promise.resolve thenable', `Promise.resolve({then(r){mark('resolve-thenable')}}); 1`);
await runVmCase('VM Promise.all thenable', `Promise.all([{then(r){mark('all-thenable')}}]); 1`);
await runVmCase('VM Promise.race thenable', `Promise.race([{then(r){mark('race-thenable')}}]); 1`);
await runVmCase('VM Promise.any thenable', `Promise.any([{then(r){mark('any-thenable')}}]); 1`);
await runVmCase('VM…
- Record
GHSA-f8gf-w286-fmq2- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 7.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H - Weakness
- CWE-693
- Updated
- 2026-11-02
NVD
7.1
vm2 before 3.11.8 does not fully enforce the allowAsync: false option in VM and NodeVM. While localPromise.prototype.then is replaced with a handler that throws 'Async not available', the sandbox's Promise static methods (Promise.resolve, Promise.all, Promise.race, Promise.any, and Promise.allSettled) still assimilate attacker-supplied thenables: native promise resolution performs PromiseResolveThenableJob and invokes the sandboxed code's then method in a microtask without passing through the patched then, so the async restriction is never applied. As a result, sandboxed script can schedule work that runs after VM.run() or NodeVM.run() has returned and outside the configured timeout, continuing to execute after the host believes execution is complete.
- Record
CVE-2026-92959- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N/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
- 7.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H - Weakness
- CWE-693
- Updated
- 2026-11-02
OSV
7.1
vm2 before 3.11.8 allowAsync Bypass via Promise Thenable
vm2 before 3.11.8 does not fully enforce the allowAsync: false option in VM and NodeVM. While localPromise.prototype.then is replaced with a handler that throws 'Async not available', the sandbox's Promise static methods (Promise.resolve, Promise.all, Promise.race, Promise.any, and Promise.allSettled) still assimilate attacker-supplied thenables: native promise resolution performs PromiseResolveThenableJob and invokes the sandboxed code's then method in a microtask without passing through the patched then, so the async restriction is never applied. As a result, sandboxed script can schedule work that runs after VM.run() or NodeVM.run() has returned and outside the configured timeout, continuing to execute after the host believes execution is complete.
- Record
CVE-2026-92959- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N - Weakness
- CWE-693
- Updated
- 2026-11-02
Show 6 moreShow less
OSVGHSA-9538-79j2-h62h
7.1
Duplicate Advisory: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-f8gf-w286-fmq2. This link is maintained to preserve external references.
Original Description
vm2 before 3.11.8 does not fully enforce the allowAsync: false option in VM and NodeVM. While localPromise.prototype.then is replaced with a handler that throws 'Async not available', the sandbox's Promise static methods (Promise.resolve, Promise.all, Promise.race, Promise.any, and Promise.allSettled) still assimilate attacker-supplied thenables: native promise resolution performs PromiseResolveThenableJob and invokes the sandboxed code's then method in a microtask without passing through the patched then, so the async restriction is never applied. As a result, sandboxed script can schedule work that runs after VM.run() or NodeVM.run() has returned and outside the configured timeout, continuing to execute after the host believes execution is complete.
- Record
GHSA-9538-79j2-h62h- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N/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
- 7.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H - Weakness
- CWE-693
- Updated
- 2026-11-02
OSVGHSA-f8gf-w286-fmq2
7.1
vm2: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM
Summary
When allowAsync is set to false, vm2 is expected to reject attempts to run asynchronous code. Direct use of Promise.prototype.then is blocked, but Promise static methods still assimilate attacker-controlled thenables. Promise.resolve(thenable), Promise.all([thenable]), Promise.race([thenable]), Promise.any([thenable]), and Promise.allSettled([thenable]) can invoke the thenable's then method in a microtask after VM.run() or NodeVM.run() has already returned.
This bypasses the documented async-execution restriction and runs outside the configured timeout, allowing sandboxed code to continue executing after the host believes execution is complete.
Details
The documented VM option says allowAsync: false should cause attempts to run async code to throw a VMError; README.md:139-145 also recommends using it with timeout. The implementation enforces part of this policy by replacing localPromise.prototype.then with an AsyncErrorHandler when async is disabled:
lib/setup-sandbox.js:1629-1637definesAsyncErrorHandler, whoseapplyandconstructtraps throwVMError: Async not available.lib/setup-sandbox.js:1743-1752installs that handler onlocalPromise.prototype.thenwhenallowAsyncis false.
However, Promise static methods are still exposed and rebound to localPromise:
lib/setup-sandbox.js:1816-1819wrapsPromise.all.lib/setup-sandbox.js:1821-1824wrapsPromise.race.lib/setup-sandbox.js:1826-1830wrapsPromise.allSettled.lib/setup-sandbox.js:1833-1837wrapsPromise.any.lib/setup-sandbox.js:1840-1843wrapsPromise.resolve.
Those wrappers prevent species attacks by forcing localPromise as the constructor, but they do not reject or neutralize thenables when allowAsync is false. Native Promise resolution then performs PromiseResolveThenableJob and calls the attacker-controlled then method asynchronously. That job does not go through the patched localPromise.prototype.then method, so the AsyncErrorHandler is never reached.
NodeVM inherits the same sandbox Promise setup through VM (lib/nodevm.js:328-332), so the same thenable-assimilation bypass is reachable in NodeVM as well.
The transformer fast path is not the root cause, but it explains why the minimal PoC is parser-independent: payloads below contain none of catch, import, async, with, the internal state identifier, or \u, so lib/transformer.js:82-89 returns without AST parsing.
PoC
Maintainer-runnable clean-checkout recipe:
npm install
node - <<'NODE'
const {VM, NodeVM} = require('./');
async function runVmCase(name, code) {
const events = [];
const vm = new VM({allowAsync: false, timeout: 10, sandbox: {mark: value => events.push(value)}});
try {
const ret = vm.run(code);
console.log(`${name}: returned ${ret}`);
} catch (e) {
console.log(`${name}: threw ${e.name}:${e.message}`);
}
await new Promise(resolve => setImmediate(resolve));
console.log(`${name} events: ${events.length ? events.join(',') : '<none>'}`);
}
async function runNodeVmCase(name, code) {
const events = [];
const vm = new NodeVM({allowAsync: false, sandbox: {mark: value => events.push(value)}});
try {
const ret = vm.run(code);
console.log(`${name}: returned ${ret}`);
} catch (e) {
console.log(`${name}: threw ${e.name}:${e.message}`);
}
await new Promise(resolve => setImmediate(resolve));
console.log(`${name} events: ${events.length ? events.join(',') : '<none>'}`);
}
(async () => {
await runVmCase('VM Promise.resolve thenable', `Promise.resolve({then(r){mark('resolve-thenable')}}); 1`);
await runVmCase('VM Promise.all thenable', `Promise.all([{then(r){mark('all-thenable')}}]); 1`);
await runVmCase('VM Promise.race thenable', `Promise.race([{then(r){mark('race-thenable')}}]); 1`);
await runVmCase('VM Promise.any thenable', `Promise.any([{then(r){mark('any-thenable')}}]); 1`);
await runVmCase('VM…
- Record
GHSA-f8gf-w286-fmq2- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 7.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H - Weakness
- CWE-693
- 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-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
OSVCLSA-2026-1791888184 No score
TuxCare security update for vm2 (2 CVEs)
TuxCare rebuilt vm2 to address 2 CVEs. Fixed in 3.11.5-tuxcare.8.
- Record
CLSA-2026-1791888184- Updated
- 2026-11-02
OSVROOT-APP-NPM-CVE-2026-92959 No score
CVE-2026-92959 in vm2 - Patched by Root
Root has patched CVE-2026-92959 in the vm2 package for Root:npm. Multiple fixed versions available.
- Record
ROOT-APP-NPM-CVE-2026-92959- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
vm2npm |
through 3.11.7 | 3.11.8 |
vm2TuxCare:npm From an advisory covering 8 CVEs |
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 From an advisory covering 4 CVEs |
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 |
vm2TuxCare:npm From an advisory covering 2 CVEs |
3.11.5-tuxcare.0 up to 3.11.5-tuxcare.8, plus listed versions 3.11.53.11.5-tuxcare.13.11.5-tuxcare.23.11.5-tuxcare.33.11.5-tuxcare.43.11.5-tuxcare.53.11.5-tuxcare.63.11.5-tuxcare.7 |
3.11.5-tuxcare.8 |
vm2npm |
before 3.11.8 | 3.11.8 |
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, facts | ||
| 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-92959 is a high vulnerability (CVSS 7.1) in Patriksimek Vm2, published September 17, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.45%, the 39th percentile. Fixed in vm2 3.11.8.
References
github.com/patriksimek/vm2/security/advisories/GHSA-f8gf-w286-fmq2
vulncheck.com/advisories/vm2-before-3.11.8-allowasync-bypass-via-promise-thenable
nvd.nist.gov/vuln/detail/CVE-2026-92959
github.com/advisories/GHSA-9538-79j2-h62h
github.com/patriksimek/vm2/commit/1d1aa437a5a39b00b3de0deb3e31f751d4884f5f
github.com/patriksimek/vm2/releases/tag/v3.11.8
github.com/advisories/GHSA-f8gf-w286-fmq2
github.com/CVEProject/cvelistV5/tree/main/cves/2026/92xxx/CVE-2026-92959.json
github.com/patriksimek/vm2
Left out here: the rest of 2 source descriptions. The JSON record has them.