Node.js Trust Falls: Dangerous Module Resolution on Windows
Strategische Zusammenfassung
Node.js auf Windows durchsucht bei der Modulauflösung standardmäßig C:\node_modules, ein Verzeichnis, das von Benutzern mit geringen Rechten erstellt werden kann. Angreifer können dort bösartige Module platzieren und eine lokale Rechteausweitung erreichen, wenn Anwendungen optionale oder fehlende Abhängigkeiten aufweisen. Obwohl es sich um eine bekannte Schwachstelle handelt, betrachtet das Node.js-Team dies nicht als Sicherheitslücke und verweist auf die Verantwortung der Entwickler. Fallstudien wie npm CLI und Discord belegen die konkrete Ausnutzbarkeit in der Praxis.
Key Findings
- Die standardmäßige Modulauflösung von Node.js unter Windows durchläuft C:\node_modules, das für jeden Benutzer beschreibbar ist und das Einschleusen von Schadcode ermöglicht.
- Dies führt zu lokaler Rechteausweitung, wenn Anwendungen optionale oder fehlende Abhängigkeiten haben und die Auflösung bis zum Wurzelverzeichnis fortschreitet.
- Das empfohlene Muster für optionale Abhängigkeiten (try, require, catch) verschleiert das Problem: Fehlende Module werden stillschweigend ignoriert, aber die Suche erreicht dennoch C:\node_modules.
- Drittanbieter-Bibliotheken tief im Abhängigkeitsbaum können dieses Verhalten unbeabsichtigt auslösen, ohne dass Entwickler es bemerken.
- Das Node.js-Sicherheitsteam akzeptiert CWE-427 nicht als Schwachstelle und wälzt die Verantwortung auf Anwendungsentwickler ab, was zu einer Vielzahl angreifbarer Anwendungen führt.
Relevanz für dich
Ein systemisches Design-Problem in Node.js' Modulauflösung auf Windows ermöglicht Privilege Escalation durch lokale Dateisystem-Manipulation, bleibt aber von Discord und Node.js nicht als kritisch eingestuft.
Volltext
[Node.js Trust Falls: Dangerous Module Resolution on Windows]
Zero Day Initiative — Node.js Trust Falls: Dangerous Module Resolution on Windows
April 08, 2026 | Bobby Gould and Michael DePlante
In September of 2024, ZDI received a vulnerability submission from an anonymous researcher affecting npm CLI that revealed a fundamental design issue in Node.js. This blog details how it continues to expose applications to local privilege escalation (LPE) attacks on Windows systems, including the Discord desktop app (CVE-2026-0776 0-Day), which remains unpatched and vulnerable.
The issue is straightforward: when Node.js resolves modules, the runtime searches for packages in `C:\node_modules` as part of its default behavior. Since low-privileged Windows users can create this directory and plant malicious modules there, any Node.js application with missing or optional dependencies becomes vulnerable to privilege escalation.
"Node.js trusts the file system."
They do not treat CWE-427 (Uncontrolled Search Path Element) as a vulnerability, pushing responsibility onto application developers.
_Figure 1: The vendor’s security policy stance on CWE-427 as a non-issue_
As the case studies below demonstrate, this stance has dangerous consequences. Developers are largely unaware of this attack surface, and the result is a proliferation of exploitable applications. We will show examples in npm CLI and Discord, but there are likely many more applications that are impacted by this.
The root cause lies in the way Node.js performs module resolution. This is documented here. Although UNIX paths are used in the documentation provided by Node.js, the same logic is applied on Windows.
When a Node.js application calls require(‘bar’), the runtime searches for the module in the following order:
1. C:\Users\Administrator\projects\node_modules\bar.js 2. C:\Users\Administrator\node_modules\bar.js 3. C:\Users\node_modules\bar.js 4. C:\node_modules\bar.js <-- The problem
If the legitimate package is missing, whether due to optional dependencies, development packages removed in production, or installation failures, the resolution search will eventually reach the root of the drive. Any user can create `C:\node_modules` and place a malicious package there. Once the low-privileged user has populated `C:\node_modules\bar.js`, Node.js will load and execute it in the context of the current user. In the following case studies, we will provide evidence of how, despite properly following NPM’s guidelines, third-party dependencies end up triggering this vulnerability anytime you launch the application.
**Case Studies: Real-World Manifestations**
The Optional Dependency Pattern: npm supports optional dependencies to be specified in the project’s package.json file. The recommended pattern for checking for these dependencies is as follows:
_Figure 2: npm Docs showing optionalDependencies example code_
This pattern silently catches errors when optional packages are missing, allowing execution to continue. So what’s the problem? On Windows, Node.js will search all the way up to `C:\node_modules` where an attacker may have planted a malicious replacement. This search behavior mirrors UNIX conventions where `/node_modules` at the filesystem root is typically only writable by root. Windows systems by default allow any user to create `C:\node_modules`. Once `require` is called, Node.js will traverse the search path and execute any matching module it finds.
Important things to note:
1. This pattern can be found in third party libraries deep in a dependency tree, as we will see in the following examples. 2. There is no runtime indication to either the developers or the end users that such a vulnerability exists without looking at the filesystem logs with Procmon. 3. The optional dependency pattern itself would not be dangerous if Node.js did not search for packages in `C:\node_modules`.
Let’s take a deeper look at both cases and see why this is so dangerous.
**Case 1: npm CLI (ZDI-26-043 / ZDI-CAN-25430 / CVE-2026-0775)**.
Prior to version 11.2.0, npm CLI used a library called “promise-inflight”, which contained an optional dependency on a package called “bluebird”.
When Node.js is installed on the system, npm is included by default without the `bluebird` package. This vulnerability was introduced when bluebird was removed through a well-intentioned pull request (https://github.com/npm/cli/pull/1438/changes), demonstrating how easy it is for developers to unknowingly create this attack surface.
We can see Node’s package resolution logic at work in the screenshot below:
_Figure 4: Procmon log showing the package resolution behavior of Node.js via CVE-2026-0775_
First, the application looks for the `bluebird.js` package in the Node.js installation directory. Node.js sequentially searches back to the system root until it finds the package. If an attacker has placed `C:\node_modules\bluebird.js`, the `require` call will find, read, and execute the malicious payload in the context of any user running npm on the system.
This vulnerability is especially dangerous because it is triggered when many `npm *` cli commands are used. Common development commands such as `npm install`, `npm –l`, and `npm prune` will all execute the malicious `bluebird.js`package.
**Case 2: Discord (ZDI-26-040/ ZDI-CAN-27057 / CVE-2026-0776/ UNPATCHED)**
On April 22, 2025, ZDI received a report for a similar vulnerability in Discord reported by T. Doğa Gelişli. Discord uses the ws WebSocket library, which contains an optional dependency on utf-8-validate for compatibility with older Node.js versions:
Figure 5: websockets library repo snippet showing require call for missing utf-8-validate package dependency
Discord does not ship with the utf-8-validate package. As a result, the following Procmon logs show the same behavior as Case 1. Anytime Discord is launched, the attacker controlled `C:\node_modules\utf-8-validate.js` is executed.
Figure 6: Procmon log showing the package resolution behavior of Node.js via CVE-2026-0776
The ws library does support disabling this check via the `WS_NO_UTF_8_VALIDATE` environment variable, but this requires the consuming application (Discord) to set it explicitly. Here’s a quick video demonstrating the bug by popping the calc app when opening Discord:
Speed Go back to previous menu
0.5x 0.75x Normal 1.25x 1.5x 1.75x 2x
Exit fullscreen Enter fullscreen
Discord automatically opens on login by default, so in practice code execution happens immediately without any user interaction. Strangely, the Discord Security team made it clear to us in their responses that they do not consider local attack vectors as valid security issues.
The cases above represent only a few of the applications affected by this pattern. During our investigation we found many other independent reports. These issues in Mongo DB Compass and Mongo DB Shell are just two other examples.
Every Windows application built on Node.js with missing or optional dependencies is potentially vulnerable. This includes desktop applications that utilize Electron as well as popular web frameworks such as Next.js and React.
Each vendor has clearly stated that they will not treat these issues as vulnerabilities:
NPM’s response to our report:
_“exploits that require local access to a machine are considered ineligible for npm CLI_
Discord’s response to our report:
_“We do not consider physical/local attacks as valid security issues”_
_“Node.js trusts the file system in the environment accessible to it. Therefore, it is not a vulnerability if it accesses/loads files from any path that is accessible to it.”_
The vulnerability pattern described in this blog stems from a deliberate design decision by Node.js maintainers. While Node.js's position that “applications should trust their filesystem” may hold true on properly administered UNIX systems, it creates a systemic vulnerability on Windows where low-privileged users can write to `C:\node_modules`. Without a fix from Node.js, the burden silently falls on application developers.
Making matters worse, the vulnerable code may not live in the application code itself. The optional dependencies that trigger this behavior could come from third-party libraries buried in the dependency tree as we saw with both Discord and npm CLI.
NPM CLI:
2024-11-13 – ZDI submitted the report to the vendor
2024-11-13 – The vendor acknowledged the receipt of the report
2024-11-13 – The vendor communicated that the reported behavior was by design and they do not consider local attacks as valid security issues
2025-08-05 – ZDI encouraged the vendor to re-assess the issue
2025-12-18 – ZDI notified the vendor of the intention to publish the case as a 0-day advisory
DISCORD:
2025-07-08 – ZDI notified vendor
2025-09-11 – ZDI followed up with vendor
2025-09-15 – Vendor stated they do not consider local attacks as valid security issues
2025-12-01 – ZDI explained why we believe the issue is still valid
2025-12-10 – Vendor replied that the vulnerability is still out of scope
2025-12-11 – ZDI informed vendor of intent to publish 0-day
[[email protected]](mailto:[email protected])
Find us on X
Find us on Mastodon
[[email protected]](mailto:[email protected])
Erwähnte CVEs
Risk Score
- cvss base
- 73.00
- kev bonus
- 0.00
- epss bonus
- 0.00
- poc bonus
- 15.00
- raw before weight
- 88.00
- industry weight
- 1.10
- freshness factor
- 0.50
- days old
- 53.00
- vendor mismatch penalty
- -10.00
Pfad: operational
MITRE ATT&CK Mapping
4 TTPsProcedure-Details
| Technik | Tactic | Procedure | Conf. | Quelle |
|---|---|---|---|---|
| T1574.008 Path Interception by Search Order Hijacking | Privilege Escalation | Low-privileged Windows users exploit Node.js module resolution order by creating C:\node_modules and planting malicious packages there, causing Node.js applications to load the attacker-controlled module instead of the legitimate one when the legitimate package is missing or optional | high | llm |
| T1574.001 DLL Search Order Hijacking | Defense Evasion | Malicious Node.js modules are placed in C:\node_modules to intercept the runtime's module resolution search path, causing applications like Discord to execute attacker-controlled JavaScript code in the context of the application process | high | llm |
| T1068 Exploitation for Privilege Escalation | Privilege Escalation | CVE-2026-0776 and CVE-2026-0775 are exploited by low-privileged users on Windows to achieve local privilege escalation by hijacking Node.js module resolution in applications such as Discord and npm CLI that run with higher privileges | high | llm |
| T1036.005 Match Legitimate Name or Location | Defense Evasion | Attacker-controlled malicious packages are named to match legitimate optional or missing dependency package names and placed in C:\node_modules, causing them to be transparently loaded by the target Node.js application without detection | medium | llm |