{"id":"GHSA-r273-hxvj-fxhp","summary":"vm2: util.getCallSites() bypasses GHSA-v27g-jcqj-v8rw host-frame redaction, leaks host call stack","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'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.\n\n### Details\n\n- Root cause — `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 — 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 — GHSA-v27g's 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'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.\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('/workspace/repo'); console.log(new NodeVM({require:{builtin:['util']}}).run(\\\"module.exports = require('util').getCallSites(4)[0].scriptName\\\"))\"\n\nObserved frames inside the sandbox, with both `require: { builtin: ['util'] }` and `require: { builtin: ['*'] }`:\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 — 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.\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.","aliases":["CVE-2026-92933"],"modified":"2026-10-05T22:45:06.581201369Z","published":"2026-10-05T22:37:40Z","database_specific":{"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-10-05T22:37:40Z","nvd_published_at":null,"cwe_ids":["CWE-200","CWE-693"]},"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"}],"affected":[{"package":{"name":"vm2","ecosystem":"npm","purl":"pkg:npm/vm2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"3.11.8"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-r273-hxvj-fxhp/GHSA-r273-hxvj-fxhp.json","last_known_affected_version_range":"\u003c= 3.11.7"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N"},{"type":"CVSS_V4","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"}]}