GHSA-R273-HXVJ-FXHP
Vulnerability from github – Published: 2026-10-05 22:37 – Updated: 2026-10-05 22:37Summary
NodeVM exposes the host util module to the sandbox through an unfiltered shallow copy (Object.assign({}, util)). On Node.js >= 22.9 this hands sandboxed code util.getCallSites(), a programmatic stack-introspection API that returns the host process's full call stack — absolute file paths, function names, and line numbers — including vm2 bridge internals and the embedding application's entrypoint. This bypasses the host-frame redaction established in GHSA-v27g-jcqj-v8rw, which only covers the Error.prepareStackTrace channel.
Details
- Root cause —
defaultBuiltinLoaderUtilcopies every static member of the hostutilmodule and wraps the copy invm.readonly()without filtering any member, so newly added Node APIs land in the sandbox automatically: https://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/builtin.js#L25-L38 - Second equivalent channel — the deprecated
sysbuiltin (an alias of hostutil) goes through the generic builtin loadervm.readonly(hostRequire(key)), which also carriesgetCallSites: https://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/builtin.js#L230 - Bypassed protection — GHSA-v27g's redaction (
isHostFrameFileName+applyCallSiteGetters) rewrites host-frame metadata getters tonullonly when the sandbox realm formats an error stack: https://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/setup-sandbox.js#L818-L870
util.getCallSites() produces its data host-side and never passes through that formatter, so frames such as lib/bridge.js @apply (the bridge apply trap), lib/nodevm.js @run, the embedder's own entry file, and node:internal/* frames reach the sandbox verbatim as data properties (scriptName, functionName, lineNumber, columnNumber, scriptId). A repository-wide grep (source, docs/ATTACKS.md, CHANGELOG, tests) shows no occurrence of getCallSites; the member was never considered.
PoC
Prerequisites: Node.js >= 22.9 (verified on v22.23.1); run npm ci --no-audit --no-fund at the repository root.
One-line reproducer (prints /workspace/repo/lib/bridge.js, i.e. a vm2-internal host path):
node -e "const {NodeVM}=require('/workspace/repo'); console.log(new NodeVM({require:{builtin:['util']}}).run(\"module.exports = require('util').getCallSites(4)[0].scriptName\"))"
Observed frames inside the sandbox, with both require: { builtin: ['util'] } and require: { builtin: ['*'] }:
/workspace/repo/lib/bridge.js @apply:1664
vm.js @:5
/workspace/repo/lib/bridge.js @apply:1664
/workspace/repo/lib/nodevm.js @run:563
/workspace/out/<embedder entrypoint>.js @main:29
node:internal/modules/cjs/loader @:1781
node:internal/modules/cjs/loader @:1913
... (8 node:internal/* frames in total)
Impact
Information disclosure. Any NodeVM configuration that allows the util (or sys) builtin — including the wildcard builtin: ['*'], both typical configurations from the README — lets untrusted sandboxed code programmatically read the host call stack: the vm2 installation path, the embedding application's file layout and entrypoint, and internal function names and line numbers. This is the same information category redacted by GHSA-v27g-jcqj-v8rw (Defense Invariant #5 in docs/ATTACKS.md) and breaks defense-in-depth assumptions of embedders that rely on host frames being invisible to the sandbox. The API returns only strings/numbers (no host object references); no escalation to code execution was identified.
Suggested fix: filter members in defaultBuiltinLoaderUtil (allowlist, or at minimum drop getCallSites), apply the same treatment to the generic loader path used by sys, and extend the GHSA-v27g regression tests to cover programmatic stack-introspection APIs.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.11.7"
},
"package": {
"ecosystem": "npm",
"name": "vm2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.11.8"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-92933"
],
"database_specific": {
"cwe_ids": [
"CWE-200",
"CWE-693"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-05T22:37:40Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nNodeVM exposes the host `util` module to the sandbox through an unfiltered shallow copy (`Object.assign({}, util)`). On Node.js \u003e= 22.9 this hands sandboxed code `util.getCallSites()`, a programmatic stack-introspection API that returns the host process\u0027s full call stack \u2014 absolute file paths, function names, and line numbers \u2014 including vm2 bridge internals and the embedding application\u0027s entrypoint. This bypasses the host-frame redaction established in GHSA-v27g-jcqj-v8rw, which only covers the `Error.prepareStackTrace` channel.\n\n### Details\n\n- Root cause \u2014 `defaultBuiltinLoaderUtil` copies every static member of the host `util` module and wraps the copy in `vm.readonly()` without filtering any member, so newly added Node APIs land in the sandbox automatically:\n https://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/builtin.js#L25-L38\n- Second equivalent channel \u2014 the deprecated `sys` builtin (an alias of host `util`) goes through the generic builtin loader `vm.readonly(hostRequire(key))`, which also carries `getCallSites`:\n https://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/builtin.js#L230\n- Bypassed protection \u2014 GHSA-v27g\u0027s redaction (`isHostFrameFileName` + `applyCallSiteGetters`) rewrites host-frame metadata getters to `null` only when the *sandbox realm* formats an error stack:\n https://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/setup-sandbox.js#L818-L870\n\n`util.getCallSites()` produces its data host-side and never passes through that formatter, so frames such as `lib/bridge.js @apply` (the bridge apply trap), `lib/nodevm.js @run`, the embedder\u0027s own entry file, and `node:internal/*` frames reach the sandbox verbatim as data properties (`scriptName`, `functionName`, `lineNumber`, `columnNumber`, `scriptId`). A repository-wide grep (source, docs/ATTACKS.md, CHANGELOG, tests) shows no occurrence of `getCallSites`; the member was never considered.\n\n### PoC\n\nPrerequisites: Node.js \u003e= 22.9 (verified on v22.23.1); run `npm ci --no-audit --no-fund` at the repository root.\n\nOne-line reproducer (prints `/workspace/repo/lib/bridge.js`, i.e. a vm2-internal host path):\n\n node -e \"const {NodeVM}=require(\u0027/workspace/repo\u0027); console.log(new NodeVM({require:{builtin:[\u0027util\u0027]}}).run(\\\"module.exports = require(\u0027util\u0027).getCallSites(4)[0].scriptName\\\"))\"\n\nObserved frames inside the sandbox, with both `require: { builtin: [\u0027util\u0027] }` and `require: { builtin: [\u0027*\u0027] }`:\n\n /workspace/repo/lib/bridge.js @apply:1664\n vm.js @:5\n /workspace/repo/lib/bridge.js @apply:1664\n /workspace/repo/lib/nodevm.js @run:563\n /workspace/out/\u003cembedder entrypoint\u003e.js @main:29\n node:internal/modules/cjs/loader @:1781\n node:internal/modules/cjs/loader @:1913\n ... (8 node:internal/* frames in total)\n\n### Impact\n\nInformation disclosure. Any NodeVM configuration that allows the `util` (or `sys`) builtin \u2014 including the wildcard `builtin: [\u0027*\u0027]`, both typical configurations from the README \u2014 lets untrusted sandboxed code programmatically read the host call stack: the vm2 installation path, the embedding application\u0027s file layout and entrypoint, and internal function names and line numbers. This is the same information category redacted by GHSA-v27g-jcqj-v8rw (Defense Invariant #5 in docs/ATTACKS.md) and breaks defense-in-depth assumptions of embedders that rely on host frames being invisible to the sandbox. The API returns only strings/numbers (no host object references); no escalation to code execution was identified.\n\nSuggested fix: filter members in `defaultBuiltinLoaderUtil` (allowlist, or at minimum drop `getCallSites`), apply the same treatment to the generic loader path used by `sys`, and extend the GHSA-v27g regression tests to cover programmatic stack-introspection APIs.",
"id": "GHSA-r273-hxvj-fxhp",
"modified": "2026-10-05T22:37:40Z",
"published": "2026-10-05T22:37:40Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/patriksimek/vm2/security/advisories/GHSA-r273-hxvj-fxhp"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-92933"
},
{
"type": "WEB",
"url": "https://github.com/patriksimek/vm2/commit/e10bd2f539ab1a90c2d37466e6aae740d4a1ce2a"
},
{
"type": "PACKAGE",
"url": "https://github.com/patriksimek/vm2"
},
{
"type": "WEB",
"url": "https://github.com/patriksimek/vm2/releases/tag/v3.11.8"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/vm2-before-3.11.8-information-disclosure-via-util-getcallsites"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "vm2: util.getCallSites() bypasses GHSA-v27g-jcqj-v8rw host-frame redaction, leaks host call stack"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.