Automated Threat Modeling

vm2 Sandbox Breakout via __lookupGetter__ Prototype Walk: Overview of CVE-2026-24118

A summary of CVE-2026-24118, a critical sandbox breakout in the vm2 Node.js package that lets an attacker run arbitrary commands on the host, with detection strategies and mitigation guidance.

CVE Analysis

10 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-05-04

vm2 Sandbox Breakout via __lookupGetter__ Prototype Walk: Overview of CVE-2026-24118
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A sandbox breakout in the vm2 Node.js package lets any attacker who can submit code to a vm2 sandbox escape it entirely and run arbitrary commands on the underlying host. vm2 gets over 1.3 million weekly downloads on npm and has 898 dependent packages, so it sits deep in the Node.js ecosystem. That makes CVE-2026-24118 a critical concern for any organization that runs untrusted code through this library.

vm2 is an open source virtual machine and sandbox environment for Node.js, built to let developers run untrusted JavaScript in an isolated context. It shows up in low code platforms, serverless function runners, template engines, and developer tooling, anywhere user supplied code needs to be evaluated without granting access to the host operating system. With 4,000 GitHub stars and broad adoption, it is one of the most prominent sandboxing solutions in the Node.js world.

Technical Information

Root Cause: Context Bridging and __lookupGetter__

The vulnerability comes from how vm2 bridges objects between the sandbox (guest) context and the host context. The __lookupGetter__ method behaves oddly in vm2: it switches between the host and sandbox versions when passed across context boundaries. That bridging inconsistency is the entry point for the whole exploit chain.

An attacker inside the sandbox can exploit this by using Buffer.apply to reach the host's apply method. From there, the attacker calls the host version of __lookupGetter__ with Buffer and __proto__ as arguments. This returns the prototype lookup method from the host context rather than the sandbox context.

Exploit Flow

The attack runs through the following stages:

  1. Host method access: The attacker uses Buffer.apply from within the sandbox to get a reference to the host's apply function.

  2. Prototype chain traversal: By invoking a.apply(g, [Buffer, ['__proto__']]) (where a is the host __lookupGetter__ and g is a crafted object), the attacker walks the prototype chain until reaching the host's Object.prototype.

  3. Constructor acquisition: From Object.prototype, the attacker accesses the .constructor property to get the host Object constructor, and then .constructor again to reach the host Function constructor.

  4. Arbitrary code execution: With the host Function constructor in hand, the attacker can create and run arbitrary functions in the host context. The publicly documented exploit uses this to call require('child_process') and run system commands.

Bypass of Prior Defenses

An earlier fix tried to block this class of attack by checking whether the descriptor value name was 'Function' in the bridge logic. It was not enough. Attackers bypassed it by using Object.getOwnPropertyDescriptor to obtain the constructor property directly, sidestepping the name check.

The fix in version 3.11.0 takes a broader approach with a two layer defense. It blocks Array species self-return sandbox escapes by making sure that when the bridge invokes a host function, no host realm array used as context or as an argument can expose an attacker controlled constructor property to internal V8 processes. This addresses the root cause instead of filtering specific property names.

CVSS Breakdown

MetricValue
Base Score9.8 Critical
Attack VectorNetwork
Attack ComplexityLow
Privileges RequiredNone
User InteractionNone
Confidentiality ImpactHigh
Integrity ImpactHigh
Availability ImpactHigh

The vulnerability is classified under CWE-94 (Improper Control of Generation of Code, or Code Injection) and CWE-693 (Protection Mechanism Failure).

Detection Methods

As of this writing, no formal Sigma, YARA, Suricata, or Snort detection signatures have been published specifically for CVE-2026-24118. Because this is an application layer sandbox breakout rather than a network protocol flaw, detection focuses on source code analysis, dependency auditing, and behavioral heuristics rather than traditional network IDS rules.

Version Based Detection (Software Composition Analysis)

The most immediate and reliable detection method is checking whether your environment uses a vulnerable version of the vm2 npm package. Any version prior to 3.11.0 is affected. Security teams should use SCA tools (Snyk, npm audit, Dependabot, Socket, etc.) to flag installations of vm2 below this version. Organizations should also watch their package-lock.json, yarn.lock, and pnpm-lock.yaml files for vm2 at versions below 3.11.0. GitHub's Dependabot and npm's built in audit system should surface this advisory automatically via GHSA-grj5-jjm8-h35p.

Heuristic Detection of Exploit Patterns in Sandbox Code

The advisory and the project's ATTACKS.md document give detailed detection heuristics for spotting exploitation attempts in code submitted to a vm2 sandbox. These fall into several recognizable pattern families.

The __lookupGetter__ + Buffer.apply Prototype Walk

The exploit's entry point uses a distinctive chain to walk from the sandbox realm to the host realm. Detection should flag any sandbox code that accesses ({}).__lookupGetter__ in combination with Buffer.apply. Any code accessing __lookupGetter__ or __lookupSetter__ on prototype objects, or using Buffer.apply with unexpected arguments, should be treated as highly suspicious.

Array Species Self Return Pattern (Category 18)

The core exploitation primitive is the Array species self return. The ATTACKS.md Category 18 detection rules call out these indicators:

  • A pattern of r.constructor = x where x has a Symbol.species property
  • Self referential species declarations like x[Symbol.species] = x
  • Functions used as constructors that return an existing object (e.g., function x() { return r; } serving as a species constructor)
  • Use of ho.assign(r, {constructor: ...}), the Object.assign variant that bypasses the proxy set trap by writing directly on the host object
  • Calls to .map(), .filter(), .slice(), .concat(), or .splice() on arrays whose constructor property has been tampered with, since these all invoke V8's internal ArraySpeciesCreate algorithm
  • ho.entries({}) or Object.entries() used to create host realm arrays destined for species manipulation

Constructor Chain Traversal (Category 1)

Since the goal is to reach the host Function constructor, monitoring should flag:

  • Any access to .constructor.constructor on any object
  • Patterns resembling Function("return process")() or equivalent dynamic code evaluation strings
  • Access to .constructor inside catch blocks where the error may carry host prototype references

Property Descriptor Value Extraction (Category 15)

Earlier exploit variants used Object.getOwnPropertyDescriptor and Object.entries() to extract the host Function constructor from descriptor values. Detection should flag:

  • Object.getOwnPropertyDescriptor(hostPrototype, 'constructor') extracting the descriptor for the constructor property
  • Chained getOwnPropertyDescriptors calls building deep nesting
  • Object.entries(descriptor) or Object.values(descriptor) used to unpack raw values from descriptors

Runtime Behavioral Monitoring

For environments running untrusted code in vm2 sandboxes, runtime monitoring can catch exploitation artifacts:

  • Unexpected child process spawning: If a vm2 sandbox escape succeeds, the attacker typically calls require('child_process').execSync(...). Watching for child process creation by the Node.js process running the sandbox (via audit logs, seccomp-bpf, or container level syscall filtering) is a strong post exploitation indicator.
  • Host process object access: EDR or APM tools that instrument Node.js process access can flag unexpected reads of process.mainModule or calls to process.mainModule.require() from within sandbox execution contexts.
  • VMError exceptions: The fix in v3.11.0 throws VMError with messages such as "Unsafe array constructor cannot be neutralized" or "Unsafe non-extensible array passed across bridge" when it detects an active exploitation attempt. Logging and alerting on these specific error messages in production is a strong signal that an attack was attempted and blocked by the patched version.

Affected Systems and Versions

All versions of the vm2 npm package prior to version 3.11.0 are affected, up to and including 3.10.4. The vulnerability was patched in version 3.11.0, published on May 1, 2026. The latest release at the time of writing is version 3.11.2.

Any application or service that uses vm2 to run user supplied or untrusted JavaScript is potentially vulnerable. This includes low code platforms, serverless function runners, template engines, and any custom tooling that relies on vm2 for code isolation.

Vendor Security History

The vm2 project has a long history of sandbox breakout vulnerabilities. Version 3.11.0 was a coordinated security release that closed 13 separate advisories, including multiple Remote Code Execution primitives and Denial of Service vectors. This pattern reflects how hard it is to build an airtight in process sandbox out of JavaScript's dynamic features.

The maintainers are candid about these limits. They warn users that new bypasses will likely turn up in the future and that vm2 should not be the only line of defense for untrusted code execution. For completely untrusted code, the vendor recommends stronger isolation alternatives:

SolutionApproachPerformanceTrade offs
isolated vmSeparate V8 isolatesFastIn maintenance mode; requires manual V8 updates
Separate process or Workerchild process or Worker threadsMediumHigher IPC overhead; data must be serialized
Containers or VMsDocker or gVisor or FirecrackerSlowStartup overhead; resource heavy
Managed servicesCloud based code executionVariableNetwork latency; external dependency

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization