GHSA-vwrp-x96c-mhwq

Suggest an improvement
Source
https://github.com/advisories/GHSA-vwrp-x96c-mhwq
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-vwrp-x96c-mhwq/GHSA-vwrp-x96c-mhwq.json
JSON Data
https://api.test.osv.dev/v1/vulns/GHSA-vwrp-x96c-mhwq
Aliases
  • CVE-2026-44005
Published
2026-05-07T04:07:05Z
Modified
2026-05-07T04:18:45.425638Z
Severity
  • 10.0 (Critical) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H CVSS Calculator
Summary
vm2: Mutable Proxies for Host Intrinsic Prototypes Allows Sandbox Escape
Details

Summary

vm2's bridge exposes mutable proxies for real host-realm intrinsic prototypes and then forwards sandbox writes into the underlying host objects with otherReflectSet() and otherReflectDefineProperty(), which lets attacker-controlled JavaScript running in a default VM or inherited NodeVM mutate shared host Object.prototype, Array.prototype, and Function.prototype from inside the sandbox.

Details

BaseHandler.apply() unwraps sandbox-controlled receivers and arguments with otherFromThis() / otherFromThisArguments() and then directly invokes the real host function with ret = otherReflectApply(object, context, args), so any default-exposed host function that can surface a prototype getter becomes a prototype-walking primitive (lib/bridge.js:665-676). BaseHandler.get() special-cases proto and returns the host-side descriptor or proxy target prototype, which is enough for the attacker to reuse the host lookupGetter('proto') accessor repeatedly until the walk lands on host Object.prototype, Array.prototype, or Function.prototype (lib/bridge.js:590-616). Once the attacker has a proxy to a host intrinsic prototype, BaseHandler.set() performs value = otherFromThis(value); return otherReflectSet(object, key, value) === true;, which writes attacker-controlled data directly into the shared host object instead of keeping the mutation sandbox-local; BaseHandler.defineProperty() repeats the same design at otherReflectDefineProperty(object, prop, otherDesc) for descriptor-based writes (lib/bridge.js:641-649, lib/bridge.js:753-774). Existing validation does not stop the attack because the constructor filter only blocks one dangerous-property access pattern, setPrototypeOf() only blocks prototype replacement rather than ordinary property assignment, and containsDangerousConstructor() only protects one later re-unwrapping path instead of the initial host-prototype write sink (lib/bridge.js:494-530, lib/bridge.js:595-610, lib/bridge.js:660-662).

PoC

Run the following code snippet and observe that the value of vm2EscapeMarker is polluted:

const { VM } = require('vm2');
const vm = new VM();
vm.run(`
  const g = ({}).__lookupGetter__;
  const a = Buffer.apply;
  const p = a.apply(g, [Buffer, ['__proto__']]);
  const hostObjectProto = p.call(p.call(p.call(p.call(Buffer.of()))));
  hostObjectProto.vm2EscapeMarker = 'polluted-object-prototype';
`);
console.log({}.vm2EscapeMarker)

Impact

Sandbox escape and prototype pollution.

Database specific
{
    "github_reviewed": true,
    "cwe_ids": [
        "CWE-1321",
        "CWE-94"
    ],
    "severity": "CRITICAL",
    "github_reviewed_at": "2026-05-07T04:07:05Z",
    "nvd_published_at": null
}
References

Affected packages

npm / vm2

Package

Affected ranges

Type
SEMVER
Events
Introduced
3.9.6
Fixed
3.11.0

Database specific

last_known_affected_version_range
"<= 3.10.5"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-vwrp-x96c-mhwq/GHSA-vwrp-x96c-mhwq.json"