GHSA-3VGF-8M4Q-Q4QR
Vulnerability from github – Published: 2026-10-05 22:45 – Updated: 2026-10-05 22:45Summary
vm2's current host-intrinsic prototype protection is incomplete. The fix for GHSA-vwrp-x96c-mhwq blocks sandbox writes into classic host intrinsics such as Object.prototype, Array.prototype, and Function.prototype, but current head still lets sandbox code in a default VM reach and mutate host Uint8Array.prototype, %TypedArray%.prototype, and ArrayBuffer.prototype.
After VM.run() returns, normal host typed-array and ArrayBuffer objects observe attacker-controlled properties and methods installed by the sandbox.
Technical Details
The existing mitigation relies on protectedHostObjects in lib/bridge.js. That set is populated from otherGlobalPrototypes, which is built from a fixed inventory of classic globals:
const globalsList = [
'Number', 'String', 'Boolean', 'Date', 'RegExp', 'Map', 'WeakMap',
'Set', 'WeakSet', 'Promise', 'Function'
];
The inventory omits typed-array and ArrayBuffer intrinsics. Sandbox code can still reuse the host-prototype walking primitive from the prior public advisory:
const lookupGetter = ({}).__lookupGetter__;
const apply = Buffer.apply;
const protoGetter = apply.apply(lookupGetter, [Buffer, ['__proto__']]);
const hostBuffer = Buffer.from([1]);
const hostBufferPrototype = protoGetter.call(hostBuffer);
const hostUint8ArrayPrototype = protoGetter.call(hostBufferPrototype);
const hostTypedArrayPrototype = protoGetter.call(hostUint8ArrayPrototype);
const hostArrayBufferPrototype = protoGetter.call(hostBuffer.buffer);
Those objects are not sandbox-local. Inside the sandbox, hostUint8ArrayPrototype === Uint8Array.prototype, hostTypedArrayPrototype === Object.getPrototypeOf(Uint8Array.prototype), and hostArrayBufferPrototype === ArrayBuffer.prototype are all false. Because the objects are not in protectedHostObjects, bridge defineProperty writes are forwarded into the real host objects.
This is the same guard-coverage boundary as the previous host-intrinsic prototype pollution fix, but with a missing intrinsic family. The bridge already has the correct enforcement shape; the protected inventory is too narrow.
PoC
PoV: poc/pov-host-typedarray-arraybuffer-prototype-pollution.js
Current-head output:
{
"status": "completed",
"vulnerable": true,
"node": "v25.8.0",
"v8": "14.1.146.11-node.20",
"control": {
"defineResult": true,
"sandboxReadsBack": "sandbox-only",
"hostControlAfter": null
},
"exploit": {
"hostUint8IsSandboxUint8": false,
"hostTypedArrayIsSandboxTypedArray": false,
"hostArrayBufferIsSandboxArrayBuffer": false,
"defineUint8": true,
"defineTypedArray": true,
"defineArrayBuffer": true,
"defineMethod": true
},
"hostEffect": {
"uint8Marker": "polluted-host-uint8array-prototype",
"typedArrayMarker": "polluted-host-typedarray-prototype",
"arrayBufferMarker": "polluted-host-arraybuffer-prototype",
"methodReturn": "sandbox-method-reached-host-uint8array"
}
}
The control proves sandbox-local Uint8Array.prototype writes remain sandbox-local. The exploit path reaches host prototypes through the bridge and causes host-created objects to observe sandbox-installed properties after VM.run() returns.
Impact
This is a sandbox boundary violation and host intrinsic prototype pollution. An attacker who can run JavaScript in a default vm2 VM can mutate shared host typed-array and ArrayBuffer behavior without NodeVM, require, wildcard builtins, nesting: true, or any host-provided typed-array object.
The supplied PoV demonstrates integrity and availability impact by installing markers and a method on host prototypes:
Uint8Array.prototype%TypedArray%.prototype, which affects typed-array families through the shared typed-array prototype chainArrayBuffer.prototype
The PoV does not claim direct host command execution or direct confidentiality impact. It is local-only and restores touched descriptors before exit.
Suggested Fix
Extend the protected host-object inventory and identity/prototype mappings to typed-array and binary-data intrinsics, including at least:
%TypedArray%.prototypeArrayBuffer.prototypeSharedArrayBuffer.prototypewhen presentDataView.prototype- all concrete typed-array prototypes present in the runtime, including
Uint8Array.prototype,Uint8ClampedArray.prototype,Int8Array.prototype,Uint16Array.prototype,Int16Array.prototype,Uint32Array.prototype,Int32Array.prototype,Float16Array.prototypewhen present,Float32Array.prototype,Float64Array.prototype,BigInt64Array.prototype, andBigUint64Array.prototype
A temporary patched-control that added this intrinsic family to thisGlobalPrototypes caused the same PoV's Reflect.defineProperty() calls to throw VMError: Operation not allowed on contextified object, and no host markers were installed.
The fix should cover the same host mutation traps used by the existing host-intrinsic protection: set, defineProperty, deleteProperty, and preventExtensions. It should not special-case Buffer; Buffer is only one way to reach the omitted host prototypes.
Affected Package/Versions
Confirmed on current head 7a1f5100b96f48d34e0fe104ab37c0acc5944f92 / v3.11.5 with Node v25.8.0 / V8 14.1.146.11-node.20.
Confirmed affected after the prior patch: npm:vm2 >= 3.11.0, <= 3.11.5.
Local sweep also reproduces on v3.10.0 through v3.10.5, but that range overlaps the already-published GHSA-vwrp-x96c-mhwq range. v3.9.0 through v3.9.2 did not produce host-visible pollution in the same local test.
Why This Is Not Intended Behavior
vm2 documents VM as a sandbox for untrusted code without require, with only JavaScript built-ins and Node's Buffer available by default. The known escape hatches do not explain this issue:
require.builtin: ['*']is aNodeVMconfiguration; this PoV uses defaultVM.nesting: trueis not enabled or used.- The README timeout caveat covers host code operating on objects returned from the sandbox; this PoV mutates host intrinsics and later affects ordinary host-created typed arrays and ArrayBuffers.
The project's own attack notes state that host-realm intrinsic prototypes should be protected from sandbox writes, while non-intrinsic host objects may remain mutable when intentionally exposed. Typed-array and ArrayBuffer prototypes are host intrinsics, not embedder-owned application objects.
Buffer availability explains how the PoV reaches the host prototype chain, but it does not authorize mutation of unrelated host-realm intrinsics. The host effects are observed on new host-created typed arrays and ArrayBuffers after VM.run() returns; the host is not invoking an object returned from the sandbox.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.11.7"
},
"package": {
"ecosystem": "npm",
"name": "vm2"
},
"ranges": [
{
"events": [
{
"introduced": "3.11.0"
},
{
"fixed": "3.11.8"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-92953"
],
"database_specific": {
"cwe_ids": [
"CWE-1321",
"CWE-913"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-05T22:45:22Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "## Summary\n\nvm2\u0027s current host-intrinsic prototype protection is incomplete. The fix for `GHSA-vwrp-x96c-mhwq` blocks sandbox writes into classic host intrinsics such as `Object.prototype`, `Array.prototype`, and `Function.prototype`, but current head still lets sandbox code in a default `VM` reach and mutate host `Uint8Array.prototype`, `%TypedArray%.prototype`, and `ArrayBuffer.prototype`.\n\nAfter `VM.run()` returns, normal host typed-array and ArrayBuffer objects observe attacker-controlled properties and methods installed by the sandbox.\n\n## Technical Details\n\nThe existing mitigation relies on `protectedHostObjects` in `lib/bridge.js`. That set is populated from `otherGlobalPrototypes`, which is built from a fixed inventory of classic globals:\n\n```js\nconst globalsList = [\n \u0027Number\u0027, \u0027String\u0027, \u0027Boolean\u0027, \u0027Date\u0027, \u0027RegExp\u0027, \u0027Map\u0027, \u0027WeakMap\u0027,\n \u0027Set\u0027, \u0027WeakSet\u0027, \u0027Promise\u0027, \u0027Function\u0027\n];\n```\n\nThe inventory omits typed-array and ArrayBuffer intrinsics. Sandbox code can still reuse the host-prototype walking primitive from the prior public advisory:\n\n```js\nconst lookupGetter = ({}).__lookupGetter__;\nconst apply = Buffer.apply;\nconst protoGetter = apply.apply(lookupGetter, [Buffer, [\u0027__proto__\u0027]]);\nconst hostBuffer = Buffer.from([1]);\n\nconst hostBufferPrototype = protoGetter.call(hostBuffer);\nconst hostUint8ArrayPrototype = protoGetter.call(hostBufferPrototype);\nconst hostTypedArrayPrototype = protoGetter.call(hostUint8ArrayPrototype);\nconst hostArrayBufferPrototype = protoGetter.call(hostBuffer.buffer);\n```\n\nThose objects are not sandbox-local. Inside the sandbox, `hostUint8ArrayPrototype === Uint8Array.prototype`, `hostTypedArrayPrototype === Object.getPrototypeOf(Uint8Array.prototype)`, and `hostArrayBufferPrototype === ArrayBuffer.prototype` are all false. Because the objects are not in `protectedHostObjects`, bridge `defineProperty` writes are forwarded into the real host objects.\n\nThis is the same guard-coverage boundary as the previous host-intrinsic prototype pollution fix, but with a missing intrinsic family. The bridge already has the correct enforcement shape; the protected inventory is too narrow.\n\n## PoC\n\nPoV: `poc/pov-host-typedarray-arraybuffer-prototype-pollution.js`\n\nCurrent-head output:\n\n```json\n{\n \"status\": \"completed\",\n \"vulnerable\": true,\n \"node\": \"v25.8.0\",\n \"v8\": \"14.1.146.11-node.20\",\n \"control\": {\n \"defineResult\": true,\n \"sandboxReadsBack\": \"sandbox-only\",\n \"hostControlAfter\": null\n },\n \"exploit\": {\n \"hostUint8IsSandboxUint8\": false,\n \"hostTypedArrayIsSandboxTypedArray\": false,\n \"hostArrayBufferIsSandboxArrayBuffer\": false,\n \"defineUint8\": true,\n \"defineTypedArray\": true,\n \"defineArrayBuffer\": true,\n \"defineMethod\": true\n },\n \"hostEffect\": {\n \"uint8Marker\": \"polluted-host-uint8array-prototype\",\n \"typedArrayMarker\": \"polluted-host-typedarray-prototype\",\n \"arrayBufferMarker\": \"polluted-host-arraybuffer-prototype\",\n \"methodReturn\": \"sandbox-method-reached-host-uint8array\"\n }\n}\n```\n\nThe control proves sandbox-local `Uint8Array.prototype` writes remain sandbox-local. The exploit path reaches host prototypes through the bridge and causes host-created objects to observe sandbox-installed properties after `VM.run()` returns.\n\n## Impact\n\nThis is a sandbox boundary violation and host intrinsic prototype pollution. An attacker who can run JavaScript in a default vm2 `VM` can mutate shared host typed-array and ArrayBuffer behavior without `NodeVM`, `require`, wildcard builtins, `nesting: true`, or any host-provided typed-array object.\n\nThe supplied PoV demonstrates integrity and availability impact by installing markers and a method on host prototypes:\n\n- `Uint8Array.prototype`\n- `%TypedArray%.prototype`, which affects typed-array families through the shared typed-array prototype chain\n- `ArrayBuffer.prototype`\n\nThe PoV does not claim direct host command execution or direct confidentiality impact. It is local-only and restores touched descriptors before exit.\n\n## Suggested Fix\n\nExtend the protected host-object inventory and identity/prototype mappings to typed-array and binary-data intrinsics, including at least:\n\n- `%TypedArray%.prototype`\n- `ArrayBuffer.prototype`\n- `SharedArrayBuffer.prototype` when present\n- `DataView.prototype`\n- all concrete typed-array prototypes present in the runtime, including `Uint8Array.prototype`, `Uint8ClampedArray.prototype`, `Int8Array.prototype`, `Uint16Array.prototype`, `Int16Array.prototype`, `Uint32Array.prototype`, `Int32Array.prototype`, `Float16Array.prototype` when present, `Float32Array.prototype`, `Float64Array.prototype`, `BigInt64Array.prototype`, and `BigUint64Array.prototype`\n\nA temporary patched-control that added this intrinsic family to `thisGlobalPrototypes` caused the same PoV\u0027s `Reflect.defineProperty()` calls to throw `VMError: Operation not allowed on contextified object`, and no host markers were installed.\n\nThe fix should cover the same host mutation traps used by the existing host-intrinsic protection: `set`, `defineProperty`, `deleteProperty`, and `preventExtensions`. It should not special-case `Buffer`; `Buffer` is only one way to reach the omitted host prototypes.\n\n## Affected Package/Versions\n\nConfirmed on current head `7a1f5100b96f48d34e0fe104ab37c0acc5944f92` / `v3.11.5` with Node `v25.8.0` / V8 `14.1.146.11-node.20`.\n\nConfirmed affected after the prior patch: `npm:vm2 \u003e= 3.11.0, \u003c= 3.11.5`.\n\nLocal sweep also reproduces on `v3.10.0` through `v3.10.5`, but that range overlaps the already-published `GHSA-vwrp-x96c-mhwq` range. `v3.9.0` through `v3.9.2` did not produce host-visible pollution in the same local test.\n\n## Why This Is Not Intended Behavior\n\nvm2 documents `VM` as a sandbox for untrusted code without `require`, with only JavaScript built-ins and Node\u0027s `Buffer` available by default. The known escape hatches do not explain this issue:\n\n- `require.builtin: [\u0027*\u0027]` is a `NodeVM` configuration; this PoV uses default `VM`.\n- `nesting: true` is not enabled or used.\n- The README timeout caveat covers host code operating on objects returned from the sandbox; this PoV mutates host intrinsics and later affects ordinary host-created typed arrays and ArrayBuffers.\n\nThe project\u0027s own attack notes state that host-realm intrinsic prototypes should be protected from sandbox writes, while non-intrinsic host objects may remain mutable when intentionally exposed. Typed-array and ArrayBuffer prototypes are host intrinsics, not embedder-owned application objects.\n\n`Buffer` availability explains how the PoV reaches the host prototype chain, but it does not authorize mutation of unrelated host-realm intrinsics. The host effects are observed on new host-created typed arrays and ArrayBuffers after `VM.run()` returns; the host is not invoking an object returned from the sandbox.",
"id": "GHSA-3vgf-8m4q-q4qr",
"modified": "2026-10-05T22:45:23Z",
"published": "2026-10-05T22:45:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/patriksimek/vm2/security/advisories/GHSA-3vgf-8m4q-q4qr"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-92953"
},
{
"type": "WEB",
"url": "https://github.com/patriksimek/vm2/commit/92a10fca7b3ca63bb1574b6795540264f6805b30"
},
{
"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-3.11.0-through-3.11.7-prototype-pollution-via-typedarray"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:H",
"type": "CVSS_V4"
}
],
"summary": "vm2: Default VM can mutate host TypedArray and ArrayBuffer intrinsics after the host-prototype pollution fix"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.