{"schema":"threatline.record/2","record":{"id":"CVE-2026-92948","aliases":["GHSA-qhwx-74w5-xhxq"],"summary":"vm2: NodeVM builtin allowlist bypass via node:test.run() execArgv allows sandbox escape","details":"## Summary\n\nOn Node.js 24 and newer, `vm2` can expose the host `node:test` module to sandboxed `NodeVM` code when the embedder explicitly allows the `node:test` builtin. Sandbox code can reach that module through `require('node:node:test')` and call `run()` with attacker-controlled `execArgv`.\n\n`node:test.run()` starts a separate Node process for process-isolated test execution and forwards the supplied `execArgv` values to that process. Supplying `--eval=<JavaScript>` therefore executes arbitrary JavaScript in an unrestricted host Node process, outside the `NodeVM` sandbox.\n\nThe PoC confirms that direct sandbox imports of `fs`, `child_process`, `module`, and `process` remain denied before the spawned process imports host `fs` and writes a harmless marker.\n\n## Affected versions and environment\n\n- Package: `vm2`\n- Affected versions: `>=3.9.6, <=3.11.5`\n- Latest reproduced version: `3.11.5`\n- Reproduced runtime: Node.js `v24.18.0`\n- Exact path is not present on Node.js 22 because `module.builtinModules` does not expose the scheme-only `node:test` entry there\n- Configuration prerequisite:\n\n```js\nrequire: {\n  builtin: ['node:test'],\n  external: false\n}\n```\n\nThe lower version boundary was tested directly: `vm2@3.9.5` blocks `require('node:node:test')`, while `vm2@3.9.6` permits the exploit path. Representative releases through `3.11.5` were also reproduced.\n\n## Root cause\n\nThe issue is a combination of builtin admission, generic host passthrough, and prefix normalization:\n\n1. On Node.js 24+, `module.builtinModules` includes the scheme-only key `node:test`.\n2. `lib/builtin.js` builds `BUILTIN_MODULES` from that array. The family-based `DANGEROUS_BUILTINS` protection does not include `test`, so `node:test` remains eligible.\n3. When the embedder explicitly allows `node:test`, `addDefaultBuiltin()` stores it through the generic loader:\n\n```js\nbuiltins.set(key, special ? special : vm => vm.readonly(hostRequire(key)));\n```\n\n4. In `lib/setup-node-sandbox.js`, `requireImpl()` strips one `node:` prefix before builtin lookup:\n\n```js\nif (localStringPrototypeStartsWith(filename, 'node:')) {\n  id = localStringPrototypeSlice(filename, 5);\n  nmod = loadBuiltinModule(id);\n}\n```\n\n5. Consequently, sandbox code requesting `node:node:test` is normalized to the stored key `node:test` and receives a readonly proxy to the host module.\n6. The readonly proxy does not make `node:test.run()` safe. Calls are forwarded to the host implementation, which accepts attacker-controlled `execArgv` for a newly spawned Node process.\n7. `--eval=<attacker JavaScript>` runs outside vm2 and has normal host builtin access.\n\nThe doubled prefix is the reachability mechanism, but the security boundary failure is broader: the generic host-passthrough loader treats the `test` builtin family as safe even though its `run()` API can launch unrestricted Node processes.\n\n## Proof of concept\n\nFrom the `poc` directory:\n\n```bash\nnpm ci --ignore-scripts --no-audit --no-fund\nnode repro.js\n```\n\nExpected successful result on Node.js 24+ includes:\n\n```json\n{\n  \"vm2Version\": \"3.11.5\",\n  \"nodeVersion\": \"v24.18.0\",\n  \"markerExists\": true,\n  \"childIsDistinctProcess\": true,\n  \"marker\": {\n    \"hostCodeExecution\": true\n  }\n}\n```\n\nThe PoC writes only `host-rce-marker.json` in its own directory and does not invoke a shell, contact a network service, or access third-party data.\n\n## Impact\n\nAn attacker who is intentionally permitted to execute untrusted JavaScript in the affected `NodeVM` configuration can escape the sandbox and execute arbitrary JavaScript under the embedder's operating-system identity.\n\nThis provides the spawned process with the host user's filesystem, environment, network, and process-execution permissions. It can therefore result in complete confidentiality, integrity, and availability impact for the hosting service.\n\n## Suggested remediation\n\nTreat the normalized `test` builtin family as dangerous before wildcard expansion and explicit builtin registration.\n\nFor example, add `test` to `DANGEROUS_BUILTINS` so the existing prefix and family checks reject both `node:test` and `node:test/reporters`:\n\n```js\nconst DANGEROUS_BUILTINS = new Set([\n  // existing entries\n  'test'\n]);\n```\n\nIf test helpers must be exposed, provide a sandbox-local wrapper through `mock` or `override` that does not expose `run()`, process isolation, `execArgv`, or other host process controls.\n\nRecommended regression cases:\n\n- explicit `builtin: ['node:test']`\n- wildcard builtin configurations\n- `require('node:test')`\n- `require('node:node:test')`\n- `node:test/reporters` and prefixed variants\n- direct low-level builtin registration\n- attempts to pass `--eval`, `--require`, or `--import` through test-runner process options\n[vm2-node-test-ghsa-submission.zip](https://github.com/user-attachments/files/29930119/vm2-node-test-ghsa-submission.zip)","categories":["Libraries & packages"],"product":"vm2","vendor":"patriksimek","ecosystem":"npm","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-qhwx-74w5-xhxq","source_modified_at":"2026-10-01T15:41:32Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vm2"},"range":">= 3.9.6, <= 3.11.6"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-92948","source_modified_at":"2026-09-18T19:31:54.395Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"vm2","vendor":"patriksimek","versions":[{"version":"3.9.6","status":"affected","versionType":"semver","lessThan":"3.11.7"},{"version":"3.11.7","status":"unaffected","versionType":"semver"}],"defaultStatus":"unaffected"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-qhwx-74w5-xhxq","source_modified_at":"2026-10-01T16:00:04.787115953Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vm2","ecosystem":"npm","purl":"pkg:npm/vm2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"3.9.6"},{"fixed":"3.11.7"}]}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-qhwx-74w5-xhxq","source_modified_at":"2026-10-01T15:41:32Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"vm2"},"version":"3.11.7"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-92948","source_modified_at":"2026-09-18T19:31:54.395Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vm2","ecosystem":null},"version":"3.11.7","basis":"first unaffected version stated in the CVE record"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-qhwx-74w5-xhxq","source_modified_at":"2026-10-01T16:00:04.787115953Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"vm2","ecosystem":"npm","purl":"pkg:npm/vm2"},"version":"3.11.7"}],"cvss":9.9,"severity":"Critical","kev":{"listed":false},"epss":{"probability":0.00654,"percentile":0.50731,"score_date":"2026-10-23"},"published_at":"2026-09-17T13:46:08.814Z","modified_at":"2026-10-01T16:00:04.787115953Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-qhwx-74w5-xhxq","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-01T15:41:32Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-92948","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-09-18T19:31:54.395Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-qhwx-74w5-xhxq","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-01T16:00:04.787115953Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-92948","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-qhwx-74w5-xhxq","tags":["exploit"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-92948","source":"GitHub Advisory Database"},{"url":"https://github.com/patriksimek/vm2/commit/415339f698f0d52d3c5ad358b12b79c8072d5b4b","source":"GitHub Advisory Database"},{"url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.7","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://www.vulncheck.com/advisories/vm2-3.9.6-through-3.11.5-sandbox-escape-via-node-test","tags":["third-party-advisory"]},{"url":"https://github.com/advisories/GHSA-qhwx-74w5-xhxq","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"no","technical_impact":"total","assessed_at":"2026-09-18T19:31:46.698017Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-01T16:00:04.787115953Z","content_hash":"e931be482c8922a56d77a1661a5119a9e46ab15ff50ff6de5a1dbcbbbba1d05c"}}
