GHSA-q3fm-4wcw-g57x: Vm2 Vm2 Setup-sandbox.js Violates Defense Invariant Vulnerability
LOWGHSA-q3fm-4wcw-g57x is a LOW vm2 setup-sandbox.js violates defense invariant vulnerability (CWE-693: Protection Mechanism Failure) affecting Vm2 libraries in the npm ecosystem. The issue involves vm2 setup-sandbox.js violates defense invariant #11 in stack-trace formatter. Remediation is available by upgrading affected packages to version 3.11.4.
GHSA-q3fm-4wcw-g57x at a Glance
How to Fix GHSA-q3fm-4wcw-g57x
To remediate this vulnerability, upgrade all affected Vm2 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install vm2@3.11.4
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls vm2
How to Check if You Are Affected
To determine whether your Node.js application is affected by GHSA-q3fm-4wcw-g57x, inspect your project dependencies using npm:
npm ls vm2
Analyze the terminal output to locate any version of Vm2 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.11.4), your application is affected.
Note: The npm ls vm2 command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-693: Protection Mechanism Failure, where the product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product. In Vm2, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-q3fm-4wcw-g57x Affected Vm2 Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
vm2
|
npm |
< 3.11.4
|
3.11.4
|
References
Frequently Asked Questions
What is GHSA-q3fm-4wcw-g57x?
Which Vm2 versions are affected?
What version fixes GHSA-q3fm-4wcw-g57x?
Is vm2 affected?
What CWE is associated with GHSA-q3fm-4wcw-g57x?
How can I check npm dependencies for GHSA-q3fm-4wcw-g57x?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-q3fm-4wcw-g57x",
"modified" : "2026-05-29T17:38:33Z",
"published" : "2026-05-29T17:38:33Z",
"aliases" : [ ],
"summary" : "vm2 setup-sandbox.js violates Defense Invariant #11 in stack-trace formatter",
"details" : "## Summary\n\n`defaultSandboxPrepareStackTrace` in `lib/setup-sandbox.js` (lines 605, 607) appends to a fresh sandbox-realm `lines = []` via `lines[lines.length] = value`. This is the exact invariant-violating pattern that GHSA-9qj6-qjgg-37qq (commit ca195f0, 2026-05-01) just patched in `neutralizeArraySpeciesBatch` and codified as Defense Invariant #11 (\"Bridge-internal containers must not invoke sandbox code\"). A sandbox-installed `Array.prototype[N]` setter fires during the bridge's safe-default stack-trace formatting and observes / intercepts each appended line.\n\n## Details\n\nThe post-9qj6 audit note in `docs/ATTACKS.md` (line 2111) states:\n\n> Equivalent pattern elsewhere in the bridge: audited; thisFromOtherArguments, otherFromThisArguments, and every other index-write site already use thisReflectDefineProperty or otherReflectDefineProperty. neutralizeArraySpeciesBatch was the lone outlier.\n\nThe audit is scoped to `lib/bridge.js`. `lib/setup-sandbox.js` was not covered. `defaultSandboxPrepareStackTrace` (added under post-#563 hardening for GHSA-v27g) constructs a sandbox-realm `[header]` array and appends each frame via the prototype-walking index assignment:\n\n```\n// lib/setup-sandbox.js, lines 601-610\nconst lines = [header];\nfor (let i = 0; i < callSites.length; i++) {\n try {\n lines[lines.length] = ' at ' + callSites[i];\n } catch (e) {\n lines[lines.length] = ' at <error formatting frame>';\n }\n}\nreturn lines.join('\\n');\n```\n\nThis function runs every time sandbox code reads `error.stack` (or any path that triggers `Error.prepareStackTrace`). At the time it runs, user code has already had the opportunity to install a setter on `Array.prototype[N]`. Because `lines` starts at length 1, the first iteration writes index 1; if `lines[1]` has no own data property, V8 walks the prototype chain and invokes the sandbox-controlled setter.\n\nThe currently-assigned value is the string `' at ' + callSites[i]` (the wrapped `CallSite` class's safe `toString()` returns `'CallSite {}'`), which limits the immediate impact to a side channel, not an RCE pivot. The concern is structural rather than exploit-today:\n\n- The just-codified Defense Invariant #11 explicitly requires that any list, set, or map allocated for the bridge's exclusive use must read and write through identity-stable, prototype-bypassing primitives. This site does not.\n- The `catch` branch at line 607 also uses the same pattern, so a sandbox getter that throws on `callSites[i]` access still routes its retry write through the prototype chain.\n- A future change that makes the appended slot value an object holding a host-realm reference (for example, an enriched frame record) would re-introduce the exact GHSA-9qj6 attack shape against this codepath.\n\nThe fix is mechanical and mirrors the GHSA-9qj6 patch: install entries via `localReflectDefineProperty` so each appended slot is an own data property and the prototype-chain setter is bypassed.\n\n```javascript\n// Suggested patch (sketch)\nlet linesLen = 1;\nfunction append(s) {\n localReflectDefineProperty(lines, linesLen, {\n __proto__: null,\n value: s,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n linesLen++;\n}\nfor (let i = 0; i < callSites.length; i++) {\n try {\n append(' at ' + callSites[i]);\n } catch (e) {\n append(' at <error formatting frame>');\n }\n}\n```\n\nThe same pattern at `callSiteGetters[callSiteGetters.length] = {...}` (line 649) runs only at sandbox setup, before user code can install setters, so it is safe today. Converting it for symmetry would be cheap and forward-compatible.\n\n## PoC\n\nvm2 v3.11.2, Node v24.\n\n```javascript\nconst { VM } = require('vm2');\nconst result = new VM().run(`\n var observed = { setterFired: false, capturedValue: null, indexFired: null };\n Object.defineProperty(Array.prototype, 1, {\n configurable: true,\n set(value) {\n observed.setterFired = true;\n observed.indexFired = 1;\n observed.capturedValue =\n typeof value === 'string' ? value.slice(0, 40) : typeof value;\n },\n get() { return undefined; }\n });\n var e = new Error('x');\n e.stack;\n observed;\n`);\nconsole.log(result);\n// {\n// setterFired: true,\n// capturedValue: ' at CallSite {}',\n// indexFired: 1\n// }\n```\n\nSandbox code observed and intercepted the bridge-internal write to `lines[1]`. Repeating the PoC with the setter installed at multiple indices (0, 1, 2, ...) captures every frame the formatter would otherwise return.\n\n## Impact\n\nHardening / Defense Invariant #11 violation. No direct sandbox escape on the current codebase: the value passed to the setter is a primitive string after the wrapped `CallSite.toString()`, so attacker-controlled code does not gain a host-realm reference from the setter argument alone. The GHSA-9qj6 entry's \"Considered Attack Surfaces\" note states the audit covered `lib/bridge.js` index-write sites; this filing reports the equivalent pattern in `lib/setup-sandbox.js` so the invariant is uniform across the bridge boundary and future enrichments of the appended record cannot regress into the GHSA-9qj6 shape.",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "vm2"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.11.4"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 3.11.3"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/patriksimek/vm2/security/advisories/GHSA-q3fm-4wcw-g57x"
}, {
"type" : "WEB",
"url" : "https://github.com/patriksimek/vm2/commit/ad31adc1fc4a2c163f2f8c11ab4af206074528fd"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/patriksimek/vm2"
}, {
"type" : "WEB",
"url" : "https://github.com/patriksimek/vm2/releases/tag/v3.11.4"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-693" ],
"severity" : "LOW",
"github_reviewed" : true,
"github_reviewed_at" : "2026-05-29T17:38:33Z",
"nvd_published_at" : null
}
}