CVE-2026-22709: Critical vm2 Sandbox Escape Vulnerability Enabling Arbitrary Code Execution in Node.js | UTOFA

CVE-2026-22709: Critical vm2 Sandbox Escape Vulnerability Enabling Arbitrary Code Execution in Node.js

CVE-2026-22709 is a critical-severity sandbox escape vulnerability in vm2, a widely used Node.js library for executing untrusted JavaScript code in an isolated environment. With a CVSS v3.1 score of 9.8 out of 10, this flaw allows a remote attacker to bypass the library's Promise callback sanitization mechanism, escape the sandbox boundary, and execute arbitrary code directly on the host system. Any organization running vm2 to process user-supplied or third-party JavaScript code is directly at risk.

The vulnerability was publicly disclosed on January 26, 2026, and a full fix was released in vm2 version 3.10.2. Given that vm2 is listed as a direct dependency by nearly 900 npm packages and receives more than one million downloads per week, the blast radius of this vulnerability extends well beyond individual projects. Security teams should treat this as an urgent, high-priority finding.

CVE-2026-22709 At a Glance

The table below summarizes the key technical attributes of CVE-2026-22709 based on the official CVSS v3.1 scoring vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H.

AttributeValuePlain-Language Meaning
CVSS Score9.8 (Critical)Near-maximum severity
Attack VectorNetwork (AV:N)Exploitable remotely over the internet
Attack ComplexityLow (AC:L)No special conditions required
Privileges RequiredNone (PR:N)No authentication needed
User InteractionNone (UI:N)Victim action not required
Confidentiality ImpactHigh (C:H)Full data exposure possible
Integrity ImpactHigh (I:H)Host data can be modified
Availability ImpactHigh (A:H)Service can be disrupted
CWE ClassificationCWE-94Code Injection
GitHub AdvisoryGHSA-99p7-6v5w-7xg8Official security advisory

The combination of network accessibility, zero authentication requirements, and high impact across all three security pillars (confidentiality, integrity, and availability) makes this one of the most severe findings ever assigned to the vm2 project.

Affected vm2 Versions and Fixed Releases

vm2 VersionStatusNotes
3.10.0VulnerableVersion where the flaw was identified
Prior to 3.10.0VulnerableAll earlier versions affected
3.10.1Partially FixedInitial patch, later found incomplete
3.10.2FixedFull fix via Reflect.apply replacement

The vulnerability was introduced in the Promise handling logic of version 3.10.0. The maintainer released an initial fix in 3.10.1, but that patch was incomplete and could be bypassed. Version 3.10.2 contains the definitive remediation. All users running any version below 3.10.2 should upgrade immediately.

vm2's History and the Supply Chain Risk

Understanding the context around vm2 is essential for assessing the true scope of CVE-2026-22709. The library was originally created to give developers a safe way to run untrusted JavaScript, a requirement common in template engines, plugin systems, code playgrounds, and automated testing frameworks.

In July 2023, following a series of critical sandbox escape vulnerabilities, the project's creator, Patrik Simek, officially deprecated vm2. The README was updated to warn that the library contained critical security issues and should not be used in production. Despite this, developers continued relying on it because no mature, drop-in replacement existed. The result was a paradox: a deprecated library with critical security warnings continued to accumulate over one million weekly downloads.

In October 2025, Simek reversed course, resurrecting the project with a clean slate, patching all prior vulnerabilities, and announcing plans to rewrite the codebase in TypeScript. CVE-2026-22709 is the first critical vulnerability to surface in this revived iteration, demonstrating that sandboxing JavaScript is a deeply difficult engineering problem. With 885 direct dependents on npm and countless indirect consumers, a single unpatched vm2 instance can compromise entire software supply chains.

What Happened to vm2: CVE-2026-22709 Discovery Timeline

The sequence of events surrounding CVE-2026-22709 unfolded over a narrow window in January 2026.

  • January 24, 2026: Vulnerability reported to the vm2 project through GitHub's private security advisory process.
  • January 26, 2026: CVE-2026-22709 published to the GitHub Security Advisory database and NVD.
  • January 26, 2026: vm2 version 3.10.1 released with an initial, incomplete patch.
  • January 26, 2026: vm2 version 3.10.2 released with the complete fix, replacing Function.prototype.call() with Reflect.apply() in all Promise handler invocations.
  • January 27, 2026: NVD record updated; public reporting begins from multiple security vendors.

The maintainer also published a proof-of-concept exploit alongside the advisory, a practice intended to help defenders understand the attack surface while also underscoring the urgency of patching.

Technical Analysis of the CVE-2026-22709 vm2 Sandbox Escape

Root Cause: The Promise Sanitization Gap

The vm2 library isolates untrusted code by maintaining two separate Promise prototype objects: localPromise for the sandboxed context and globalPromise for the host context. The library wraps callbacks passed to these Promise handlers using an ensureThis() function, which is meant to prevent unsanitized host objects from leaking into or out of the sandbox.

The root cause of CVE-2026-22709 is an asymmetric sanitization implementation inside lib/setup-sandbox.js. The callbacks registered on localPromise.prototype.then are correctly sanitized. However, the corresponding callbacks on globalPromise.prototype.then and globalPromise.prototype.catch are not given the same treatment. This gap matters because JavaScript's native async functions always return globalPromise objects, not localPromise objects. Any sandboxed code that uses async/await patterns will therefore operate on the unprotected global Promise prototype.

The Attack Vector

The attack vector is fully network-accessible and requires no prior authentication or user privileges. The only prerequisite is that the attacker can supply JavaScript code that the target application passes to vm2's run() method. This is a common pattern in plugin systems, online code execution platforms, rule engines, and API gateways that evaluate user-defined logic.

The underlying mechanism relies on a property of Function.prototype.call: it can be overridden by code running inside the sandbox. In the vulnerable code path, after the ensureThis() sanitization wrapper is created, the final invocation of globalPromiseCatch.call(this, onRejected) uses Function.prototype.call() to dispatch the callback. An attacker who overrides Function.prototype.call within the sandbox can intercept this dispatch before ensureThis() has a chance to sanitize the error object, giving them direct access to an unsanitized host Error object.

Step-by-Step Exploit Chain

The exploit demonstrated in the public proof-of-concept follows a precise sequence:

  1. Create a specially crafted Error object inside the sandbox with error.name = Symbol(). Assigning a Symbol as the name causes the JavaScript engine to throw a TypeError when error.stack is accessed, because Symbols cannot be implicitly converted to strings.
  2. Define an async function that accesses error.stack. Because it is an async function, its return value is a globalPromise instance, which is subject to the unsanitized code path.
  3. Attach a .catch() handler to the returned globalPromise. This triggers globalPromise.prototype.catch, which in the vulnerable version dispatches via Function.prototype.call.
  4. Intercept Function.prototype.call from within the sandbox. When the intercepted call fires, the error object e passed to the callback is an unsanitized host Error object that retains a direct reference to the host Error constructor.
  5. Walk the prototype chain from the host Error object: e.constructor yields the host Error function, and Error.constructor yields the host Function constructor.
  6. Instantiate a new host-context Function using new Function(...) and execute arbitrary operating system commands via process.mainModule.require('child_process').execSync(...), completely outside any sandbox restriction.

This chain is trivial to execute for anyone familiar with JavaScript prototype mechanics, which is why the CVSS attack complexity is rated Low.

CVE-2026-22709 vm2 Sandbox Escape Exploit Chain Diagram

The Patch: What Changed in Version 3.10.2

The fix introduced in commit 4b009c2d4b1131c01810c1205e641d614c322a29 is precise and targeted. The vulnerable lines that used Function.prototype.call() for dispatching Promise handlers were replaced with calls to Reflect.apply(), referenced internally as the apply function imported from localReflect.

The critical difference is that Reflect.apply is a built-in JavaScript intrinsic that cannot be overridden or intercepted by code running inside the sandbox. When the dispatch mechanism is non-interceptable, the attacker loses the ability to bypass ensureThis() sanitization, closing the escape vector.

Version 3.10.1 was an intermediate attempt that patched globalPromise.prototype.then but left globalPromise.prototype.catch unaddressed, creating a residual bypass. Version 3.10.2 applies the Reflect.apply replacement consistently across both then and catch handlers, providing complete protection against this specific attack pattern.

Assessing Your Exploitability

Not every application that imports vm2 is equally exposed. Use the following framework to evaluate your actual risk before prioritizing the patch.

Step 1: Check your installed version

Run npm list vm2 in your project root. If the output shows any version below 3.10.2, you are running vulnerable code.

Step 2: Search your codebase for vm2 invocations

grep -rE "new\s+VM\(|require\(['\"]vm2['\"]\)|from\s+['\"]vm2['\"]" \
--include="*.js" --include="*.ts" ./src

For every match, trace what code is passed to VM().run() and identify its origin.

Step 3: Classify each usage

Usage PatternRisk LevelExample
Hardcoded, static code stringsLowvm.run('console.log("hello")')
Trusted configuration filesLow-MediumInternal admin-controlled scripts
User-submitted code via API bodyCriticalvm.run(req.body.code)
Database-stored scripts editable by usersCriticalvm.run(await db.getUserScript(id))
Code constructed from URL parametersCriticalvm.run(`${req.query.template}`)

Step 4: Apply SCA with reachability

Software composition analysis tools that support call-graph reachability can determine whether any execution path in your application reaches vm2's run() method with attacker-influenced input. If no such path exists, exploitation is effectively not possible even on a vulnerable version.

Detection Methods for CVE-2026-22709 Exploitation

Indicators of Compromise

Security teams should look for the following behavioral signals in applications that use vm2:

  • Unexpected spawning of child processes (e.g., sh, bash, cmd.exe) from Node.js worker processes.
  • Anomalous file system reads or writes outside the application's expected working directory.
  • Outbound network connections initiated by processes that should not require external access.
  • Error log entries containing unexpected TypeError or Promise-related exceptions within vm2 execution contexts.
  • Presence of child_process, execSync, or require strings within code submitted to sandbox endpoints.
  • Signs of privilege escalation or lateral movement originating from the application server.

Detection Strategies

  • Deploy application-level logging to capture the full content of every code snippet submitted for sandbox execution, along with the requesting identity and timestamp.
  • Implement runtime monitoring for child process creation events from Node.js processes. Tools like Falco, osquery, or auditd can alert on unexpected execve syscalls.
  • Use a Web Application Firewall (WAF) or API gateway rule to flag or block request payloads containing patterns such as async, Promise, Function.prototype, or child_process.
  • Integrate static analysis into your CI/CD pipeline to detect changes that expose vm2's run() method to user-controlled data.

Monitoring Recommendations

  • Enable verbose Node.js application logging (NODE_DEBUG=*) in staging environments to understand normal vm2 execution behavior before deploying detection rules to production.
  • Set up egress network monitoring with alerts for unexpected DNS lookups or TCP connections originating from the application process.
  • Deploy runtime application self-protection (RASP) instrumentation designed for Node.js, which can intercept and block dangerous operations like child_process execution at the runtime level regardless of whether the sandbox has been escaped.
  • Retain sandbox execution logs for at least 90 days to support forensic investigation if an incident is discovered after the fact.

Mitigation and Remediation Steps for CVE-2026-22709 in vm2

Immediate Actions Required

  1. Upgrade vm2 to version 3.10.2 across all environments (development, staging, production).
  2. Audit all applications for vm2 usage using the grep command provided in the Exploitability section.
  3. Temporarily disable user-facing code execution features if immediate patching is not operationally feasible, to prevent exploitation while a maintenance window is scheduled.
  4. Review application logs for the indicators of compromise listed above to determine whether exploitation attempts have already occurred.
  5. Notify dependent teams if vm2 is included in a shared library or internal SDK used by other engineering groups.

Patch Installation

Upgrading vm2 is straightforward using the standard npm workflow:

# Upgrade to the patched version
npm install vm2@3.10.2

# Pin the exact version in package.json to prevent accidental downgrade
npm install vm2@3.10.2 --save-exact

# Verify the installed version
npm list vm2

After installation, redeploy all affected services and confirm the running version matches 3.10.2.

Workarounds (When Immediate Patching Is Not Possible)

If you cannot upgrade immediately, the following controls reduce but do not eliminate risk:

  • Restrict or block user access to any endpoint that passes input to vm2.run().
  • Add input validation that rejects code payloads containing async, await, Promise, or Function.prototype patterns before they reach the sandbox.
  • Run vm2 inside a Docker container with --cap-drop=ALL, --no-new-privileges, and a restrictive seccomp profile to limit what an escaped process can do on the host.
  • Use network policies to block all outbound traffic from the container running vm2, reducing the attacker's ability to exfiltrate data or download additional payloads after escaping the sandbox.

What Can Go Wrong After Patching

Upgrading to vm2 3.10.2 addresses the specific Promise callback sanitization bypass described in CVE-2026-22709, but it does not make vm2 inherently safe. Security teams should be aware of the following residual risks.

The patch is narrowly scoped. The fix closes this one vector via Reflect.apply. vm2 has accumulated over 20 documented sandbox escape vulnerabilities across its lifetime. A new escape path may surface in future versions. Treating 3.10.2 as a permanently secure baseline would be a mistake.

Overly permissive execution contexts remain dangerous. If your application upgrades vm2 but continues to pass raw, unsanitized user input to run() without additional controls, the next discovered vulnerability will be just as immediately exploitable as this one.

Defense-in-depth is not optional. Even with a fully patched vm2, sandboxed code execution should always run inside process-level or container-level isolation with minimal OS privileges, restricted file system access, and blocked outbound networking. The sandbox library is one layer of protection, not the only layer.

Alternative Sandboxing Approaches

Given vm2's extensive vulnerability history, security-conscious teams should evaluate whether it is the right tool for their use case. The table below compares common Node.js sandboxing and code isolation approaches.

ApproachIsolation StrengthComplexityBest Use Case
vm2 (patched)MediumLowTrusted plugin systems with hardcoded code
Node.js vm module (built-in)LowVery LowSimple expression evaluation with no untrusted input
Isolated-vmHighMediumHigh-performance untrusted code execution
Worker Threads + vm moduleMediumMediumCPU-bound tasks with partial isolation
Docker containers (per execution)Very HighHighFully untrusted arbitrary code execution
WebAssembly (WASM) sandboxesHighHighPerformance-sensitive, language-agnostic isolation
Domain-specific languages (DSL)Very HighHighRule engines, template logic, policy evaluation

If your use case involves executing completely arbitrary, attacker-controlled JavaScript, container-level isolation or a mature WASM sandbox offers substantially stronger security guarantees than any JavaScript-based library. For more limited use cases such as evaluating user-defined business rules or formulas, replacing vm2 with a narrowly scoped DSL interpreter eliminates the attack surface entirely.

Sandboxing as a Persistent Security Challenge

The history of vm2 illustrates a fundamental challenge in software security: sandbox escape vulnerabilities in JavaScript environments are extraordinarily difficult to prevent. JavaScript's prototype chain, its dynamic Function constructor, reflection APIs, and the sheer number of built-in global objects create an enormous attack surface that any sandbox implementation must continuously defend.

Every new JavaScript language feature, including async/await, Proxy objects, WeakRefs, and structured clone algorithms, introduces potential new escape vectors. This is why vm2 has accumulated more than 20 documented breakouts over its lifetime, and why its original maintainer eventually chose to deprecate it entirely. Security researchers have compared this dynamic to an arms race: defenders patch one escape mechanism, and researchers find another route through a different part of the language specification.

The lesson for engineering teams is not that sandboxing is futile, but that no single JavaScript sandbox library should be treated as a hard security boundary against motivated attackers. Layered defenses at the process, container, and network level are essential complements to any library-level sandbox. Organizations that run untrusted code as a core product feature should treat their sandbox infrastructure with the same rigor as a cryptographic boundary, subject to regular penetration testing and adversarial review.

Conclusion

CVE-2026-22709 is a stark reminder that executing untrusted JavaScript is a high-risk operation that demands far more than a single library dependency. The Promise callback sanitization bypass at the heart of this vulnerability is technically sophisticated but operationally simple to exploit, requiring no authentication, no user interaction, and no special conditions.

The immediate action is clear: upgrade vm2 to version 3.10.2 without delay. Beyond that, engineering and security teams should audit every code path that delivers attacker-influenced input to vm2, layer process-level and container-level isolation around any sandboxed execution context, and seriously evaluate whether alternative isolation approaches better match their long-term security requirements. Sandboxing JavaScript at scale is hard, and CVE-2026-22709 is not the last vulnerability that will test this assumption.

Frequently Asked Questions

What is CVE-2026-22709?
CVE-2026-22709 is a critical sandbox escape vulnerability in the vm2 Node.js library. It allows attackers who can submit JavaScript code to a vm2-sandboxed environment to bypass Promise callback sanitization and execute arbitrary code on the host system. The vulnerability carries a CVSS v3.1 score of 9.8.

Which versions of vm2 are affected?
All versions of vm2 prior to 3.10.2 are vulnerable. The flaw was specifically identified in version 3.10.0. Version 3.10.1 contains an incomplete fix. Version 3.10.2 is the first release with a complete remediation.

How does the Promise callback bypass work?
The vulnerability exists because globalPromise.prototype.then and globalPromise.prototype.catch callbacks are not sanitized the same way as localPromise callbacks. Since async functions return globalPromise objects, attackers can intercept Function.prototype.call during Promise dispatch, bypassing the ensureThis() sanitization and gaining access to unsanitized host objects.

Is my application vulnerable if it uses vm2 but does not execute user-provided code?
If your application passes only hardcoded, developer-controlled strings to vm2.run() and no attacker-influenced data can reach that call, the exploitation risk is significantly reduced. However, you should still upgrade to 3.10.2 to maintain a clean security posture and guard against future code changes.

Was a proof-of-concept exploit publicly released?
Yes. The vm2 maintainer published a working proof-of-concept alongside the security advisory on January 26, 2026. The exploit is concise and trivial to adapt, which is why the CVSS attack complexity is rated Low and immediate patching is strongly advised.

How do I patch CVE-2026-22709?
Run npm install vm2@3.10.2 --save-exact to upgrade to the patched version, then redeploy all affected services. Use npm list vm2 to confirm the installed version before and after the upgrade.

Does upgrading to 3.10.2 fully protect me against all vm2 vulnerabilities?
No. Version 3.10.2 closes the specific Promise callback bypass described in CVE-2026-22709. It does not protect against undiscovered future vulnerabilities in vm2. Given the library's history of more than 20 sandbox escape disclosures, upgrading should be combined with process-level isolation and defense-in-depth controls.

Are there safer alternatives to vm2?
Yes. For high-trust requirements, Docker containers with restricted capabilities offer strong process-level isolation. The isolated-vm library is designed specifically for secure multi-tenant JavaScript execution. For constrained use cases like business rule evaluation, a domain-specific language eliminates the attack surface entirely.

Your Business Cannot Afford to Wait

Security threats like CVE-2026-22709 remind every C-suite leader that digital risks are now business risks. UTOFA helps you build smarter, safer AI solutions that protect your operations and keep your growth on track. Reach out today and let us show you a clear path forward.

  • Secure AI systems designed to keep your data and business logic protected from code-level threats.
  • Faster, confident decisions backed by AI tools built for business leaders, not just developers.
  • Real business results through solutions that balance innovation with stability and control.

Never miss a story

Stay updated about UTOFA news as it happens

Scroll to Top