Security controls bypass or absence In vm2
Description
vm2's Transformer Fast-Path Bypass Exposes Internal State Variable
Summary
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:
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
with statement instrumentation is bypassed: with() statements are not wrapped with wrapWith(), enabling scope manipulation
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(",") `);...
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(\",\")"}' # Control test — blocked when catch keyword is present curl -s -X POST http://localhost:3000/api/execute \ -H "Content-Type: application/json" \...
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.
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Component | Affected version | Patched versions |
|---|---|---|---|
npm | 3.11.0 |
Aliases
References