GHSA-46pr-c5wc-xffx CVE-2026-92939
vm2 crypto builtin loads attacker native code through setEngine
- Severity
- 9.4 / 10
- Critical · CVSS 4.0
- Exploit chance (EPSS)
- 0.62%
- Chance of exploitation in the next 30 days. Higher than 49.55% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-114 Process Control
- Fix
3.11.7- Upgrade vm2 to this version or later
Description
Summary
vm2 3.11.6 exposes the host crypto module to a NodeVM when that single builtin is allowed. The module is presented through a read-only bridge, but its functions still execute with host-process authority. crypto.setEngine() accepts a filesystem path and asks OpenSSL to dynamically load the referenced native library.
An attacker whose untrusted plugin package contains a native library can therefore load that library into the host process by calling crypto.setEngine() from sandboxed JavaScript. The native library's constructor executes before OpenSSL finishes validating whether the file is a usable engine. Consequently, even the expected ERR_CRYPTO_ENGINE_UNKNOWN exception occurs only after arbitrary native code has already run.
The exploit requires only the crypto builtin. It does not require fs, process, module, child_process, worker_threads, vm, inspector, unrestricted builtins, or vm2 nesting.
Details
The vulnerable boundary is the generic builtin loader. Builtins that are not specially wrapped or classified as dangerous are imported in the host realm and exposed through a recursive read-only proxy:
builtins.set(key, special ? special : vm => vm.readonly(hostRequire(key)));
Read-only prevents sandbox code from assigning properties on the module object. It does not reduce the authority of callable exports. Calls are forwarded to the original host function with bridge values converted back to host values.
The crypto module includes this callable export:
crypto.setEngine(enginePath)
When enginePath names a dynamic library, OpenSSL loads that file into the current process. Operating-system dynamic loaders execute library constructors as part of loading. Engine-symbol validation happens afterward. A file does not have to become a functional cryptographic engine for its constructor to execute.
The resulting exploit flow is:
attacker supplies an untrusted plugin package containing a native library
-> host executes the plugin in NodeVM with only crypto allowed
-> plugin calls crypto.setEngine(pathToBundledLibrary)
-> vm2 forwards the call to the host crypto module
-> OpenSSL asks the operating-system loader to load the library
-> the library constructor executes native code in the host process
-> OpenSSL may then reject the file, but host compromise has already occurred
This is different from merely granting JavaScript filesystem access. A plugin system commonly has to place an untrusted package on disk before running its JavaScript. The native file can therefore already exist inside that package even when the sandbox denies fs and all process-execution builtins. Allowing crypto for hashing or signature verification unexpectedly turns that inert package file into a native-code execution primitive.
PoC
The attached PoC is local and harmless. Its native library constructor creates a marker file; it does not spawn a process, open a network connection, or modify any other file.
-
Install Node.js with OpenSSL engine support, a C compiler, and npm.
-
In the attached
pocdirectory, install the exact affected package:npm install --ignore-scripts -
Build the marker library.
On macOS:
cc -dynamiclib -O2 -o probe-engine.dylib engine_probe.c node poc.js ./probe-engine.dylibOn Linux:
cc -shared -fPIC -O2 -o probe-engine.so engine_probe.c node poc.js ./probe-engine.so -
A vulnerable result contains all of the following:
- the sandbox configuration lists only
cryptoas an allowed builtin; crypto.setEngine()reportsERR_CRYPTO_ENGINE_UNKNOWNor returns;vm2-setengine-native-marker.txtexists afterward;- the marker contains
VM2_SETENGINE_NATIVE_CODE_EXECUTED.
- the sandbox configuration lists only
The exception is not a negative result. The marker proves that the native constructor ran before engine validation failed.
Impact
This is a sandbox escape to arbitrary native code execution. The code runs with the operating-system identity and privileges of the Node.js host process, outside all vm2 language and module restrictions.
An attacker can replace the marker-only constructor with native code that:
- reads application secrets, environment variables, credentials, and files available to the host user;
- modifies application data or executable files and establishes persistence;
- accesses internal services using the host's network identity;
- steals other tenants' data from the same process;
- terminates or corrupts the host process; and
- executes arbitrary operating-system actions permitted to the host account.
The realistic affected workflow is a plugin, automation, notebook, or multi-tenant code runner that stores attacker-supplied package contents on disk and runs the package's JavaScript in NodeVM while allowing crypto. The attacker does not need the sandbox to expose a file-write or command-execution module.
Affected versions
npm
| Package | Vulnerable | Fixed in |
|---|---|---|
| vm2 | >= 3.11.3, < 3.11.7 |
3.11.7 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
vm2 npm → 3.11.7
- npm
npm install vm2@3.11.7- yarn
yarn add vm2@3.11.7- pnpm
pnpm add vm2@3.11.7
Severity breakdown
CVSS 4.0 9.4
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
- Attack vector
- Network
- Attack complexity
- Low
- Attack requirements
- None
- Privileges required
- Low
- User interaction
- None
- Confidentiality (vulnerable system)
- High
- Integrity (vulnerable system)
- High
- Availability (vulnerable system)
- High
- Confidentiality (subsequent systems)
- High
- Integrity (subsequent systems)
- High
- Availability (subsequent systems)
- High
- Exploit maturity
- Not defined
- Scope
- X
CVSS 3.1 9.9
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- Low
- User interaction
- None
- Scope
- Changed
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Weakness
CWE-114: Process Control
Executing commands or loading libraries from an untrusted source or in an untrusted environment can cause an application to execute malicious commands (and payloads) on behalf of an attacker.
How to prevent it
- Architecture and Design: Libraries that are loaded should be well understood and come from a trusted source. The application can execute code contained in the native libraries, which often contain calls that are susceptible to other security problems, such as buffer overflows or command injection. All native libraries should be validated to determine if the application requires the use of the library. It is very difficult to determine what these native libraries actually do, and the potential for malicious code is high. In addition, the potential for an inadvertent mistake in these native libraries is also high, as many are written in C or C++ and may be susceptible to buffer overflow or race condition problems. To help prevent buffer overflow attacks, validate all input to native calls for content and length. If the native library does not come from a trusted source, review the source code of the library. The library should be built from the reviewed source before using it.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-489w-w794-jq94 vm2: NodeVM zlib Buffers expose pooled host memory across the VM boundary | High | Also affects vm2 |
| GHSA-2v2p-6j97-cjg9 vm2: Host Promise rejection from an exposed constructor can terminate the vm2 host process | Medium | Also affects vm2 |
| GHSA-5h3f-q97h-ccvc vm2: NodeVM custom resolution bypasses external path boundaries | Critical | Also affects vm2 |
| GHSA-pq68-rvw4-xp4r vm2 contains a sandbox escape vulnerability | Critical | Also affects vm2 |
| GHSA-f8gf-w286-fmq2 vm2: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM | High | Also affects vm2 |
| GHSA-6rh5-qq4q-97xh vm2: NodeVM builtin denylist bypass via fs/promises despite -fs, allowing host filesystem writes | High | Also affects vm2 |
| GHSA-8686-vhfx-7r3j vm2: NodeVM node:-prefixed negative builtin deny bypass exposes child_process | Critical | Also affects vm2 |
| GHSA-wjwh-qqvp-g4p4 vm2 sandbox escape via WebAssembly.compileStreaming Promise species bypass | Critical | Also affects vm2 |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor