Vulnerability GHSA-3vgf-8m4q-q4qr
Summary
vm2: Default VM can mutate host TypedArray and ArrayBuffer intrinsics after the host-prototype pollution fix
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:
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.
Related Vulnerabilities
Other vulnerabilities affecting the same packages