CVE-2026-92944: Vm2 Vm2 Sandbox Escape On Node.js Vulnerability

CRITICAL
Advisory Identifier: GHSA-27g9-p43v-cw3v
CVSS 9.8
CWE-693 CWE-913
Published
Advisory Summary

CVE-2026-92944 is a CRITICAL (CVSS 9.8) vm2 sandbox escape on node.js vulnerability (CWE-693: Protection Mechanism Failure) affecting Vm2 libraries in the npm ecosystem. The issue involves vm2 sandbox escape on node.js 26 through a stale promisethenlookupchain protector. Remediation is available by upgrading affected packages to version 3.11.7.

CVE-2026-92944 at a Glance

CVE Identifier CVE-2026-92944
GHSA Identifier GHSA-27g9-p43v-cw3v
Severity CRITICAL
CVSS v3.1 Base Score 9.8
CWE Classification CWE-693: Protection Mechanism Failure
Affected Product Vm2
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 9.8

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

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

How to Fix CVE-2026-92944

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.7
npm install vm2@3.11.7
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-92944, 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.7), 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

## Reporter - Name or handle: `[YMsora]` - Report date: 2026-08-14 ## Summary The latest published vm2 release, **3.11.5**, and the current `main` branch are vulnerable to a sandbox escape when used on Node.js 26. An ordinary fulfilled Promise created by an async function can retain an attacker-controlled `constructor[Symbol.species]` across `Promise.prototype.finally()`. vm2 installs wrappers on the intrinsic `Promise.prototype.then` and `catch` methods. On Node.js 26 / V8 14.6, V8's `SetPrototypeProperties` path updates these existing data properties without invalidating the `PromiseThenLookupChain` protector. `Promise.prototype.finally()` subsequently trusts the stale protector and uses an internal `InvokeThen` fast path that calls the original native `then`, bypassing vm2's wrapper and its `resetPromiseSpecies(this)` hardening. The attacker-controlled species constructor therefore supplies the resolve and reject functions used by a native Promise reaction. A calibrated stack overflow at that native reaction boundary yields a raw host-realm `RangeError` to the attacker-controlled reject function. Its constructor chain reaches the host `Function` constructor and consequently the host `process` object. The attached proof is non-destructive: it reads only `process.version`. It does not execute commands, access files, or perform network requests. ## Latest-version status Verified on 2026-08-14: - npm `latest`: `vm2@3.11.5` - `vm2@3.11.5` publication time: 2026-05-18T15:37:26.138Z - npm `gitHead`: `7a1f5100b96f48d34e0fe104ab37c0acc5944f92` - npm tarball: `https://registry.npmjs.org/vm2/-/vm2-3.11.5.tgz` - npm integrity: `sha512-RSrkBiwrj6FRU+QdqNs6KG0XdlvJCjpQ4GXiqmMbrhmwfu5k/XIMpAer0L8f6iuf0uJ3a4T1xJN126Q8yf0VIA==` - GitHub default branch `main` HEAD: the same commit, also tagged `v3.11.5` - latest formal Node.js release: `v26.7.0`, V8 `14.6.202.34` The npm tarball and current GitHub `main` contain the same relevant `lib/setup-sandbox.js` Git blob. There is no unpublished fix on `main` at the time of this report. Permanent source reference: <https://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/setup-sandbox.js#L345-L390> ## Affected configurations ### vm2 versions tested on Node.js 26.7.0 | vm2 version | Result | | --- | --- | | 3.10.2 through 3.10.5 | host realm reached, 3/3 per release | | 3.11.0 | host realm reached, 3/3 | | 3.11.1 | host realm reached, 3/3 | | 3.11.2 | host realm reached, 5/5 | | 3.11.3 | host realm reached, 3/3 | | 3.11.4 | host realm reached, 5/5 | | 3.11.5 | host realm reached, 5/5 | | current `main` (`7a1f5100`) | host realm reached, 5/5 | This report therefore confirms **vm2 3.10.2 through 3.11.5** as affected when used with the vulnerable runtime. Earlier vm2 versions are not claimed: they may be independently vulnerable through older published issues, which would not establish this root cause. ### Node.js versions tested with stock vm2 3.11.5 | Node.js | V8 | Result | | --- | --- | --- | | 22.23.2 | 12.4.254.21 | safe, 5/5 | | 24.19.0 | 13.6.233.17 | safe, 5/5 | | 25.9.0 | 14.1.146.11 | safe, 5/5 | | 26.0.0 | 14.6.202.33 | host realm reached, 5/5 | | every formal release from 26.1.0 through 26.7.0 | 14.6.202.34 | host realm reached, 5/5 per release | | v27.0.0-nightly202608131b2de5e052 | 14.6.202.34 | host realm reached | | v27.0.0-v8-canary20260812f3fe529a1e | 15.3.55 | safe | The vulnerable behavior was reproduced on Linux/musl, Linux/glibc, and Windows x64. It is not Alpine-specific. ## Configuration requirements The default configuration is affected: ```js const vm = new VM(); ``` Default `new VM()` was verified 3/3 on Node.js 26.7.0 and vm2 3.11.5 with the operating system's default Node stack size. The escape also remains reproducible with stronger settings: ```js const vm = new VM({ allowAsync: true, eval: false, wasm: false, timeout: 5000 }); ``` Therefore the exploit does **not** require: - WebAssembly or JSPI; - dynamic evaluation being enabled; - Buffer; - an exposed host object; - a custom Promise supplied by the embedder; - a specific operating system; or - any challenge-specific code. The demonstrated producer uses an async function, so async support must be available. This is vm2's default. The V8 `PromiseThenLookupChain` protector must still be intact when vm2 installs its wrappers. A fresh/default Node.js process satisfies this condition. An unrelated earlier mutation that correctly invalidates the protector can make this exact path safe, but that is not a documented vm2 mitigation and is not present in the default setup. ## Reproduction The attachment `poc.js` uses only the published npm package and prints a benign host-version marker. ```sh mkdir vm2-node26-repro cd vm2-node26-repro npm init -y npm install --ignore-scripts --no-audit --no-fund vm2@3.11.5 cp /path/to/poc.js . node poc.js ``` Alternatively, with the official Node.js 26.7.0 container: ```sh docker run --rm -v "$PWD:/poc:ro" -w /tmp node:26.7.0-bookworm-slim sh -lc ' npm init -y >/dev/null 2>&1 && npm install --ignore-scripts --no-audit --no-fund vm2@3.11.5 >/dev/null 2>&1 && cp /poc/poc.js . && node poc.js ' ``` Representative output: ```json { "node": "v26.7.0", "v8": "14.6.202.34-node.28", "vm2": "3.11.5", "config": "default", "result": "HOST", "hostVersion": "v26.7.0", "speciesCalls": 1, "localError": false, "localRangeError": false } ``` The calibrated depth varies with the platform and process layout; this is expected. The PoC searches the boundary instead of relying on a fixed depth. To verify that disabling eval and WebAssembly is not sufficient: ```sh STRICT=1 node poc.js ``` ## Technical root cause 1. vm2 replaces the intrinsic `Promise.prototype.then` and `catch` methods with wrappers that sanitize callbacks and execute `resetPromiseSpecies(this)`. 2. These two prototype writes are consecutive direct assignments in `lib/setup-sandbox.js`. 3. V8 14.6 enables the `proto_assign_seq_opt` optimization, combining this assignment sequence into `SetPrototypeProperties`. 4. In the Node.js 26 implementation, the existing-data-property branch calls `Object::SetDataProperty(&it, value)` without first calling `it.UpdateProtector()`. 5. The JavaScript property now contains vm2's wrapper, but the V8 `PromiseThenLookupChain` protector incorrectly remains valid. 6. `Promise.prototype.finally()` performs an internal `InvokeThen`. Because the protector appears valid, V8 directly selects the native Promise `then` instead of performing the observable property lookup that would reach vm2's wrapper. 7. The wrapper never executes, so the attacker's own `constructor[Symbol.species]` is not reset before the native `then` creates its result capability. 8. A native Promise reaction calls the attacker-provided capability resolve function. At a calibrated stack boundary, V8 creates a host-realm `RangeError` and passes it to the attacker-provided capability reject function without vm2 conversion. 9. `error.constructor.constructor` is consequently the host `Function`, even when the VM was configured with `eval:false`. The root-cause control is strong: ```sh node --no-proto-assign-seq-opt poc.js ``` On Node.js 26.7.0 this changes the result from `HOST` to `SAFE` (3/3). A separate semantic probe also changes from `wrapperCalls=0` to `wrapperCalls=1`. ## Relationship to previous advisories This report intentionally discloses the overlap with prior work: ### CVE-2026-22709 / GHSA-99p7-6v5w-7xg8 That issue concerned bypassing callback sanitization on intrinsic Promises returned by async functions. vm2's fix added/strengthened the `globalPromise.prototype.then` and `catch` wrappers. The current issue is different: on V8 14.6, `finally()` trusts a stale protector and bypasses those installed wrappers at the engine fast path. ### CVE-2026-47208 / GHSA-76w7-j9cq-rx2j That issue used a missing `resetPromiseSpecies` call in `localPromise`'s rejection-swallowing tail. It is marked fixed in 3.11.4. The current entry point is the intrinsic async Promise plus `finally()`/`InvokeThen`; the PoC succeeds against 3.11.4 and 3.11.5. ### CVE-2026-47210 / GHSA-6j2x-vhqr-qr7q That issue required a JSPI-backed Promise and WebAssembly APIs. Its 3.11.4 fix removes the relevant JSPI surface. The current PoC uses an ordinary fulfilled async-function Promise, works with `wasm:false`, and succeeds against 3.11.5. ### Public V8 fix The underlying V8 protector bookkeeping bug is already public and fixed upstream: - data-property branch: <https://github.com/v8/v8/commit/7b26e81087ec88c287d9a4812f969d412f32774e> - follow-up lazy-accessor branch: <https://github.com/v8/v8/commit/4635ddd85839461c8ad791990023a477efefe7bd> This report does not claim discovery of a new V8 bug. It reports a previously undocumented, still-unpatched vm2 sandbox escape created by the interaction between that V8 bug and vm2's Promise hardening. The escape affects vm2's latest release and current main branch. ## Impact An attacker who can execute untrusted JavaScript inside a vm2 `VM` can cross the sandbox boundary and obtain the host `Function` constructor and `process` object. This permits arbitrary code execution with the privileges of the host Node.js process, including access to its filesystem, credentials, environment, network, and child-process facilities. This is the exact security boundary vm2 is intended to enforce; no additional application mistake is required beyond executing attacker-controlled code in the sandbox. ## Suggested remediation 1. Install the intrinsic `then` and `catch` wrappers through an operation that reliably invalidates V8's Promise lookup-chain protector, such as an appropriate `Reflect.defineProperty`/`Object.defineProperty` path, instead of the optimizable consecutive direct-assignment sequence. 2. Wrap the intrinsic `Promise.prototype.finally` entry point and execute `resetPromiseSpecies(this)` before delegating to the cached native `finally` implementation. 3. Add a build/runtime regression test using an intrinsic Promise from `(async () => 1)()`: an attacker-controlled own species must not be constructed by `p.finally()`. 4. Until a vm2 release is available, document Node.js 26 as affected or fail closed on the vulnerable runtime range. 5. Coordinate with Node.js to backport the existing V8 protector fixes to the Node.js 26 release line. Verified controls show that wrapping `finally` before calling the cached native implementation blocks the tested direct variants, while the upstream V8 fix also restores correct wrapper invocation. Credit is requested as: `YMsora`. `https://github.com/YMs0ra` ## Attachments [vm2-node26-finally-private-report.zip](https://github.com/user-attachments/files/31059187/vm2-node26-finally-private-report.zip)

CVE-2026-92944 Affected Vm2 Versions

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

References

Frequently Asked Questions

What is CVE-2026-92944?
CVE-2026-92944 is a security vulnerability in Vm2 involving vm2 sandbox escape on Node.js 26 through a stale PromiseThenLookupChain protector under CWE-693: Protection Mechanism Failure.
Which Vm2 versions are affected?
Affected versions include releases prior to 3.11.7 across affected packages: vm2.
What version fixes CVE-2026-92944?
Upgrading to 3.11.7 (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.7. Upgrade your dependency to 3.11.7 or later.
What is the CVSS score of CVE-2026-92944?
CVE-2026-92944 has a CVSS v3.1 base score of 9.8 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H.
What CWE is associated with CVE-2026-92944?
CVE-2026-92944 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-92944?
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-27g9-p43v-cw3v",
  "modified" : "2026-10-01T15:28:08Z",
  "published" : "2026-10-01T15:28:07Z",
  "aliases" : [ "CVE-2026-92944" ],
  "summary" : "vm2 sandbox escape on Node.js 26 through a stale PromiseThenLookupChain protector",
  "details" : "## Reporter\n\n- Name or handle: `[YMsora]`\n- Report date: 2026-08-14\n\n## Summary\n\nThe latest published vm2 release, **3.11.5**, and the current `main` branch are vulnerable to a sandbox escape when used on Node.js 26. An ordinary fulfilled Promise created by an async function can retain an attacker-controlled `constructor[Symbol.species]` across `Promise.prototype.finally()`.\n\nvm2 installs wrappers on the intrinsic `Promise.prototype.then` and `catch` methods. On Node.js 26 / V8 14.6, V8's `SetPrototypeProperties` path updates these existing data properties without invalidating the `PromiseThenLookupChain` protector. `Promise.prototype.finally()` subsequently trusts the stale protector and uses an internal `InvokeThen` fast path that calls the original native `then`, bypassing vm2's wrapper and its `resetPromiseSpecies(this)` hardening.\n\nThe attacker-controlled species constructor therefore supplies the resolve and reject functions used by a native Promise reaction. A calibrated stack overflow at that native reaction boundary yields a raw host-realm `RangeError` to the attacker-controlled reject function. Its constructor chain reaches the host `Function` constructor and consequently the host `process` object.\n\nThe attached proof is non-destructive: it reads only `process.version`. It does not execute commands, access files, or perform network requests.\n\n## Latest-version status\n\nVerified on 2026-08-14:\n\n- npm `latest`: `vm2@3.11.5`\n- `vm2@3.11.5` publication time: 2026-05-18T15:37:26.138Z\n- npm `gitHead`: `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`\n- npm tarball: `https://registry.npmjs.org/vm2/-/vm2-3.11.5.tgz`\n- npm integrity: `sha512-RSrkBiwrj6FRU+QdqNs6KG0XdlvJCjpQ4GXiqmMbrhmwfu5k/XIMpAer0L8f6iuf0uJ3a4T1xJN126Q8yf0VIA==`\n- GitHub default branch `main` HEAD: the same commit, also tagged `v3.11.5`\n- latest formal Node.js release: `v26.7.0`, V8 `14.6.202.34`\n\nThe npm tarball and current GitHub `main` contain the same relevant `lib/setup-sandbox.js` Git blob. There is no unpublished fix on `main` at the time of this report.\n\nPermanent source reference:\n\n<https://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/setup-sandbox.js#L345-L390>\n\n## Affected configurations\n\n### vm2 versions tested on Node.js 26.7.0\n\n| vm2 version | Result |\n| --- | --- |\n| 3.10.2 through 3.10.5 | host realm reached, 3/3 per release |\n| 3.11.0 | host realm reached, 3/3 |\n| 3.11.1 | host realm reached, 3/3 |\n| 3.11.2 | host realm reached, 5/5 |\n| 3.11.3 | host realm reached, 3/3 |\n| 3.11.4 | host realm reached, 5/5 |\n| 3.11.5 | host realm reached, 5/5 |\n| current `main` (`7a1f5100`) | host realm reached, 5/5 |\n\nThis report therefore confirms **vm2 3.10.2 through 3.11.5** as affected when used with the vulnerable runtime. Earlier vm2 versions are not claimed: they may be independently vulnerable through older published issues, which would not establish this root cause.\n\n### Node.js versions tested with stock vm2 3.11.5\n\n| Node.js | V8 | Result |\n| --- | --- | --- |\n| 22.23.2 | 12.4.254.21 | safe, 5/5 |\n| 24.19.0 | 13.6.233.17 | safe, 5/5 |\n| 25.9.0 | 14.1.146.11 | safe, 5/5 |\n| 26.0.0 | 14.6.202.33 | host realm reached, 5/5 |\n| every formal release from 26.1.0 through 26.7.0 | 14.6.202.34 | host realm reached, 5/5 per release |\n| v27.0.0-nightly202608131b2de5e052 | 14.6.202.34 | host realm reached |\n| v27.0.0-v8-canary20260812f3fe529a1e | 15.3.55 | safe |\n\nThe vulnerable behavior was reproduced on Linux/musl, Linux/glibc, and Windows x64. It is not Alpine-specific.\n\n## Configuration requirements\n\nThe default configuration is affected:\n\n```js\nconst vm = new VM();\n```\n\nDefault `new VM()` was verified 3/3 on Node.js 26.7.0 and vm2 3.11.5 with the operating system's default Node stack size.\n\nThe escape also remains reproducible with stronger settings:\n\n```js\nconst vm = new VM({\n  allowAsync: true,\n  eval: false,\n  wasm: false,\n  timeout: 5000\n});\n```\n\nTherefore the exploit does **not** require:\n\n- WebAssembly or JSPI;\n- dynamic evaluation being enabled;\n- Buffer;\n- an exposed host object;\n- a custom Promise supplied by the embedder;\n- a specific operating system; or\n- any challenge-specific code.\n\nThe demonstrated producer uses an async function, so async support must be available. This is vm2's default.\n\nThe V8 `PromiseThenLookupChain` protector must still be intact when vm2 installs its wrappers. A fresh/default Node.js process satisfies this condition. An unrelated earlier mutation that correctly invalidates the protector can make this exact path safe, but that is not a documented vm2 mitigation and is not present in the default setup.\n\n## Reproduction\n\nThe attachment `poc.js` uses only the published npm package and prints a benign host-version marker.\n\n```sh\nmkdir vm2-node26-repro\ncd vm2-node26-repro\nnpm init -y\nnpm install --ignore-scripts --no-audit --no-fund vm2@3.11.5\ncp /path/to/poc.js .\nnode poc.js\n```\n\nAlternatively, with the official Node.js 26.7.0 container:\n\n```sh\ndocker run --rm -v \"$PWD:/poc:ro\" -w /tmp node:26.7.0-bookworm-slim sh -lc '\n  npm init -y >/dev/null 2>&1 &&\n  npm install --ignore-scripts --no-audit --no-fund vm2@3.11.5 >/dev/null 2>&1 &&\n  cp /poc/poc.js . &&\n  node poc.js\n'\n```\n\nRepresentative output:\n\n```json\n{\n  \"node\": \"v26.7.0\",\n  \"v8\": \"14.6.202.34-node.28\",\n  \"vm2\": \"3.11.5\",\n  \"config\": \"default\",\n  \"result\": \"HOST\",\n  \"hostVersion\": \"v26.7.0\",\n  \"speciesCalls\": 1,\n  \"localError\": false,\n  \"localRangeError\": false\n}\n```\n\nThe calibrated depth varies with the platform and process layout; this is expected. The PoC searches the boundary instead of relying on a fixed depth.\n\nTo verify that disabling eval and WebAssembly is not sufficient:\n\n```sh\nSTRICT=1 node poc.js\n```\n\n## Technical root cause\n\n1. vm2 replaces the intrinsic `Promise.prototype.then` and `catch` methods with wrappers that sanitize callbacks and execute `resetPromiseSpecies(this)`.\n2. These two prototype writes are consecutive direct assignments in `lib/setup-sandbox.js`.\n3. V8 14.6 enables the `proto_assign_seq_opt` optimization, combining this assignment sequence into `SetPrototypeProperties`.\n4. In the Node.js 26 implementation, the existing-data-property branch calls `Object::SetDataProperty(&it, value)` without first calling `it.UpdateProtector()`.\n5. The JavaScript property now contains vm2's wrapper, but the V8 `PromiseThenLookupChain` protector incorrectly remains valid.\n6. `Promise.prototype.finally()` performs an internal `InvokeThen`. Because the protector appears valid, V8 directly selects the native Promise `then` instead of performing the observable property lookup that would reach vm2's wrapper.\n7. The wrapper never executes, so the attacker's own `constructor[Symbol.species]` is not reset before the native `then` creates its result capability.\n8. A native Promise reaction calls the attacker-provided capability resolve function. At a calibrated stack boundary, V8 creates a host-realm `RangeError` and passes it to the attacker-provided capability reject function without vm2 conversion.\n9. `error.constructor.constructor` is consequently the host `Function`, even when the VM was configured with `eval:false`.\n\nThe root-cause control is strong:\n\n```sh\nnode --no-proto-assign-seq-opt poc.js\n```\n\nOn Node.js 26.7.0 this changes the result from `HOST` to `SAFE` (3/3). A separate semantic probe also changes from `wrapperCalls=0` to `wrapperCalls=1`.\n\n## Relationship to previous advisories\n\nThis report intentionally discloses the overlap with prior work:\n\n### CVE-2026-22709 / GHSA-99p7-6v5w-7xg8\n\nThat issue concerned bypassing callback sanitization on intrinsic Promises returned by async functions. vm2's fix added/strengthened the `globalPromise.prototype.then` and `catch` wrappers. The current issue is different: on V8 14.6, `finally()` trusts a stale protector and bypasses those installed wrappers at the engine fast path.\n\n### CVE-2026-47208 / GHSA-76w7-j9cq-rx2j\n\nThat issue used a missing `resetPromiseSpecies` call in `localPromise`'s rejection-swallowing tail. It is marked fixed in 3.11.4. The current entry point is the intrinsic async Promise plus `finally()`/`InvokeThen`; the PoC succeeds against 3.11.4 and 3.11.5.\n\n### CVE-2026-47210 / GHSA-6j2x-vhqr-qr7q\n\nThat issue required a JSPI-backed Promise and WebAssembly APIs. Its 3.11.4 fix removes the relevant JSPI surface. The current PoC uses an ordinary fulfilled async-function Promise, works with `wasm:false`, and succeeds against 3.11.5.\n\n### Public V8 fix\n\nThe underlying V8 protector bookkeeping bug is already public and fixed upstream:\n\n- data-property branch: <https://github.com/v8/v8/commit/7b26e81087ec88c287d9a4812f969d412f32774e>\n- follow-up lazy-accessor branch: <https://github.com/v8/v8/commit/4635ddd85839461c8ad791990023a477efefe7bd>\n\nThis report does not claim discovery of a new V8 bug. It reports a previously undocumented, still-unpatched vm2 sandbox escape created by the interaction between that V8 bug and vm2's Promise hardening. The escape affects vm2's latest release and current main branch.\n\n## Impact\n\nAn attacker who can execute untrusted JavaScript inside a vm2 `VM` can cross the sandbox boundary and obtain the host `Function` constructor and `process` object. This permits arbitrary code execution with the privileges of the host Node.js process, including access to its filesystem, credentials, environment, network, and child-process facilities.\n\nThis is the exact security boundary vm2 is intended to enforce; no additional application mistake is required beyond executing attacker-controlled code in the sandbox.\n\n## Suggested remediation\n\n1. Install the intrinsic `then` and `catch` wrappers through an operation that reliably invalidates V8's Promise lookup-chain protector, such as an appropriate `Reflect.defineProperty`/`Object.defineProperty` path, instead of the optimizable consecutive direct-assignment sequence.\n2. Wrap the intrinsic `Promise.prototype.finally` entry point and execute `resetPromiseSpecies(this)` before delegating to the cached native `finally` implementation.\n3. Add a build/runtime regression test using an intrinsic Promise from `(async () => 1)()`: an attacker-controlled own species must not be constructed by `p.finally()`.\n4. Until a vm2 release is available, document Node.js 26 as affected or fail closed on the vulnerable runtime range.\n5. Coordinate with Node.js to backport the existing V8 protector fixes to the Node.js 26 release line.\n\nVerified controls show that wrapping `finally` before calling the cached native implementation blocks the tested direct variants, while the upstream V8 fix also restores correct wrapper invocation.\n\nCredit is requested as: `YMsora`.  `https://github.com/YMs0ra`\n\n## Attachments\n[vm2-node26-finally-private-report.zip](https://github.com/user-attachments/files/31059187/vm2-node26-finally-private-report.zip)",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "vm2"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "3.10.2"
      }, {
        "fixed" : "3.11.7"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 3.11.6"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/patriksimek/vm2/security/advisories/GHSA-27g9-p43v-cw3v"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-92944"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/patriksimek/vm2/commit/c7df2e21d2c74038a1f1750f40f9fe603ec69c6a"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/patriksimek/vm2"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/patriksimek/vm2/releases/tag/v3.11.7"
  }, {
    "type" : "WEB",
    "url" : "https://www.vulncheck.com/advisories/vm2-3.10.2-through-3.11.6-sandbox-escape-via-promise-protector"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-693", "CWE-913" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-01T15:28:07Z",
    "nvd_published_at" : null
  }
}