VDB
Sign up
CRITICAL10.0

GHSA-3vgf-8m4q-q4qr

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

Quick fix

GHSA-3vgf-8m4q-q4qr — vm2: upgrade to the fixed version with the command below.

npm install vm2@3.11.8

Details

## Summary

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:

```js 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:

```js 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:

```json { "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 chain - `ArrayBuffer.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%.prototype` - `ArrayBuffer.prototype` - `SharedArrayBuffer.prototype` when present - `DataView.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.prototype` when present, `Float32Array.prototype`, `Float64Array.prototype`, `BigInt64Array.prototype`, and `BigUint64Array.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 a `NodeVM` configuration; this PoV uses default `VM`. - `nesting: true` is 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.

Are you affected?

Enter the version of the package you're using.

Affected packages

npm/vm2
Introduced in: 3.11.0Fixed in: 3.11.8
Fixnpm install vm2@3.11.8

References