Oct 2, 2026·6 min read·2 visits
A denylist bypass vulnerability in vm2 <= 3.11.6 allows sandboxed code to bypass security rules by importing unblocked subpath modules like fs/promises, enabling arbitrary write access to the host filesystem.
CVE-2026-92958 is a high-severity sandbox escape and denylist bypass vulnerability within the NodeVM subsystem of the vm2 sandboxing library. When configuring wildcards with negative deny entries, exact-string matches fail to block subpaths like fs/promises. Sandboxed code can import these subpaths to bypass isolation and execute arbitrary filesystem operations on the host.
The Node.js environment exposes various native APIs to execute processes, manipulate filesystems, and run network requests. The vm2 library was designed to run untrusted code within a sandboxed context, isolating execution from sensitive APIs. The NodeVM subclass provides options to explicitly control which built-in modules are accessible to the sandbox environment. Security teams and software developers configured this boundary to protect host systems from arbitrary code execution.
A critical flaw exists in the mechanism that matches requested modules against a configured negative deny list. The denylist validation routine checks only exact string equivalences. This routine fails to recognize when an unblocked flat module path belongs to a blocked family hierarchy. As a result, sandboxed code can import alternative subpath interfaces to execute unrestricted host operations.
The vulnerability is tracked as CVE-2026-92958 and GHSA-6rh5-qq4q-97xh. It allows an attacker with sandbox access to bypass policy enforcement. The issue affects all versions of vm2 up to and including 3.11.6 when negative entries are used alongside wildcard configurations.
To understand this security boundary failure, one must examine how the Node.js module loading system structures built-in capabilities. Node.js built-in modules representing subpaths (such as fs/promises or path/posix) are not properties or submodules of their primary modules. Instead, they exist as distinct, independent flat entries within the runtime's internal list of modules.
The vm2 policy parser in lib/builtin.js processes user-defined blocklists using an exact match paradigm. When an application configures a wildcard permit alongside a negative deny list, the library verifies names literally. For example, denying fs via the -fs token only blocks exact matches against the string fs.
Because fs/promises is evaluated as a separate flat entry, the verification check fails to match -fs. The string -fs/promises is not present in the exclude list, and the prefix checks fail to detect the hierarchy. The parser incorrectly concludes that fs/promises is a permitted module, subsequently loading and exposing it to the sandbox environment.
In addition to the subpath bypass, prefix parsing was inconsistently implemented. An exclusion like -node:fs/promises did not match fs/promises, allowing alternative namespace specifiers to bypass the filter. This discrepancy meant sandbox code could leverage different spellings of the same module path to subvert policy validation.
The vulnerable logic resides in the makeBuiltinsFromLegacyOptions function inside lib/builtin.js. During initial configuration parsing, vm2 loops through the internal list of built-in modules. For each module name, it evaluates whether that name is denied by comparing it against configured negations.
// Vulnerable implementation in vm2 <= 3.11.6
if (builtins.indexOf(`-${name}`) === -1 && builtins.indexOf(`-node:${name}`) === -1) {
addDefaultBuiltin(res, name, hostRequire);
}In this design, if the loop processes the module name fs/promises, the code checks builtins.indexOf('-fs/promises') and builtins.indexOf('-node:fs/promises'). If the developer specified -fs in their configuration array, both conditions evaluate to -1. Consequently, the check passes, and fs/promises is registered via addDefaultBuiltin.
The patched version introduced a dedicated helper, isBuiltinDenied, to handle spelling variations and subpath structures. The revised code strips the protocol prefix and checks the root family identifier if a slash is detected in the specifier string.
// Patched implementation in vm2 3.11.7
function stripNodePrefix(name) {
return name.startsWith('node:') ? name.slice(5) : name;
}
function isBuiltinDenied(builtins, name) {
const bare = stripNodePrefix(name);
// Verify exact name matching
if (builtins.indexOf(`-${bare}`) !== -1 || builtins.indexOf(`-node:${bare}`) !== -1) return true;
// Verify subpath members matching root family
const slash = bare.indexOf('/');
if (slash > 0) {
const family = bare.slice(0, slash);
if (builtins.indexOf(`-${family}`) !== -1 || builtins.indexOf(`-node:${family}`) !== -1) return true;
}
return false;
}The updated verification function blocks subpath execution effectively. If a developer declares -fs, any module name containing fs/ or node:fs/ is evaluated against the root family fs. This lookup identifies the matching negative token and prevents the module from loading.
Exploitation requires that the host application instantiates NodeVM with wildcard permissions and negative constraints. For example, a configuration setting builtin: ['*', '-fs'] intends to permit general system functions while specifically restricting file access. The attacker must possess capabilities to inject and execute arbitrary JavaScript code inside this sandbox instance.
Because the sandbox environment exposes the fs/promises namespace directly, the attacker bypassed the legacy protection layer. The execution code does not require complex sandbox evasion techniques or memory corruption payloads. Instead, standard ECMAScript modules are imported through the normal require mechanism.
The attacker loads fs/promises and uses standard asynchronous methods to interact with the host system. High-impact operations, such as file creation, deletion, and permission modification, can be executed using writeFile, rm, or chmod routines. The sandbox isolation boundary is broken because these actions are executed within the parent process context.
The security impact of CVE-2026-92958 is categorized as high, with a CVSS base score of 8.5. This score reflects the change of scope metric, as sandboxed execution rules are completely bypassed to interact directly with the parent operating system. This vulnerability allows arbitrary write and modification access to the host's filesystem.
Since host operations run with the privilege levels of the parent Node.js process, the impact depends on deployment practices. If the host application runs with administrative or root privileges, the sandboxed code can rewrite system binaries, insert SSH keys, or manipulate configuration files. This capability can be leveraged to establish persistent remote code execution on the server.
While the vulnerability does not directly provide a read channel for data exfiltration within the exploit itself, file writes can be used to redirect sensitive application outputs or inject web shells. Consequently, system integrity and system availability are severely compromised. This issue represents a significant risk for platforms running multi-tenant untrusted scripts.
The primary remediation strategy is upgrading the vm2 dependency to version 3.11.7 or higher. This update replaces the flat comparison checks with the hierarchical validation helper, neutralizing the subpath bypass. Software developers should update their package configurations and run verification tests to ensure the patch is applied.
If upgrading is not immediately possible, applications must implement temporary configuration workarounds. The NodeVM options must be modified to explicitly block all known subpath namespaces in addition to the root module name. A robust mitigation must explicitly list names such as -fs/promises, -path/posix, -path/win32, -stream/promises, and other potential bypass paths.
// Hardened options configuration for legacy setups
const vm = new NodeVM({
require: {
builtin: [
'*',
'-fs', '-fs/promises',
'-child_process',
'-path', '-path/posix', '-path/win32',
'-stream', '-stream/promises', '-stream/web'
]
}
});Due to the persistent discovery of sandbox escape vulnerabilities in vm2 and its official deprecation, security teams should plan a migration strategy. Transitioning to stronger isolation layers, such as isolated containers, WebAssembly sandboxes, or process-level boundaries, is recommended for long-term security.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
vm2 Patrik Šimek | <= 3.11.6 | 3.11.7 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-269 |
| Attack Vector | Network |
| CVSS v3.1 | 8.5 |
| EPSS Score | 0.00384 |
| Exploit Status | PoC |
| CISA KEV Status | Not Listed |
The software does not properly assign, modify, track, or check privileges, allowing actors to access restricted areas or perform unauthorized operations.
An incorrect authorization and directory traversal vulnerability in the vm2 library before version 3.11.7 allows remote attackers to bypass the sandbox's external package allowlist. This flaw permits sandboxed code to resolve and execute arbitrary packages available on the host filesystem under host privileges, leading to unauthenticated sandbox escape and host code execution.
A critical vulnerability in the Node.js sandbox library vm2 allows sandboxed code to retrieve the process-wide http.globalAgent and https.globalAgent singletons. By attaching event listeners to these host-realm emitters, sandboxed code can intercept host-realm network requests, capturing sensitive Authorization headers and hijacking active TLSSocket streams.
CVE-2026-92950 (GHSA-jxxv-8r27-vm4p) is a critical sandbox escape vulnerability in the command-line interface of the vm2 library prior to version 3.11.7. The flaw allows untrusted scripts to bypass sandbox constraints and execute arbitrary system commands with the privileges of the host process by exploiting insecure default configurations of the module resolver.
CVE-2026-92948 is a critical sandbox escape vulnerability in the vm2 library affecting versions 3.9.6 through 3.11.6 when executed on Node.js 24 and newer. The vulnerability allows an attacker to bypass built-in module blocking defenses by double-prefixing a restricted module name (such as node:node:test). This permits the loading of the node:test module, whose test runner execution can be leveraged to execute arbitrary shell commands outside the VM sandbox.
A high-severity sandbox escape and state corruption vulnerability exists in the vm2 library (versions 3.11.4 through 3.11.6). The vulnerability is caused by an incomplete blocklist of Node.js-internal registered symbols crossing the sandbox-host bridge boundary. Specifically, the filters in `lib/setup-sandbox.js` and write traps in `lib/bridge.js` omitted the symbols `nodejs.stream.disturbed` and `nodejs.stream.errored`, allowing untrusted code within the sandbox to corrupt host-realm stream state checks.
An architectural evaluation of CVE-2026-73607 in the SiYuan personal knowledge management system. This technical advisory details a Missing Authorization (CWE-862) vulnerability in the Go-based backend kernel, specifically within the outline storage API endpoint. Under certain configurations, authenticated low-privilege users can query metadata, heading structures, and block hierarchies of restricted documents.