CVE-2026-102598 Overview
CVE-2026-102598 is a denial-of-service vulnerability in Werkzeug, the WSGI web application library used by Flask and other Python web frameworks. Versions prior to 3.1.9 fail to properly validate Windows special device names in the safe_join function used by send_from_directory. An attacker can request a path ending in a Windows reserved device name such as NUL: with an empty NTFS Alternate Data Stream (ADS) marker. The check does not strip the empty ADS marker before comparing against reserved device names, allowing the special device to be opened. The resulting file read hangs indefinitely, tying up the serving thread or worker. This issue is fixed in Werkzeug 3.1.9 and is tracked as [CWE-67: Improper Handling of Windows Device Names].
Critical Impact
Remote unauthenticated attackers can trigger indefinite hangs in Werkzeug applications running on Windows with NTFS, exhausting worker capacity and causing denial of service.
Affected Products
- Werkzeug versions prior to 3.1.9
- Python web applications using send_from_directory on Windows with NTFS
- Flask and other frameworks that depend on the vulnerable Werkzeug safe_join implementation
Discovery Timeline
- 2026-09-29 - CVE-2026-102598 published to NVD
- 2026-09-30 - Last updated in NVD database
- Werkzeug 3.1.9 released with the fix referenced in GitHub Security Advisory GHSA-g6x2-hccm-hh4m
Technical Details for CVE-2026-102598
Vulnerability Analysis
The safe_join function in src/werkzeug/security.py normalizes and validates user-supplied path segments before joining them with a base directory. On Windows, it also rejects reserved device names such as CON, PRN, AUX, and NUL. The check compares the final path component against this reserved list but does not strip an empty NTFS Alternate Data Stream marker first. A path ending in NUL: retains the trailing : and slips past the device-name check. When send_from_directory subsequently opens the resulting path, Windows resolves it to the NUL device. The read call then blocks indefinitely, holding the worker thread. Repeated requests exhaust the application's concurrency budget and produce a full denial of service.
Root Cause
The root cause is incomplete input canonicalization. safe_join inspects the raw final segment against a static list of Windows device names but does not remove the ADS suffix (: followed by an optional stream name) before comparison. The empty ADS marker (NUL:) is a legal NTFS syntax that still resolves to the NUL device when opened, but the validator treats NUL: as a distinct, non-reserved string.
Attack Vector
Exploitation requires only a network request to any endpoint that passes user-controlled path input into send_from_directory on a Windows NTFS host. The attacker submits a filename ending in a reserved device name with an empty ADS marker, for example NUL:, CON:, or PRN:. Werkzeug accepts the path, opens the device, and the response handler blocks on a read that never returns. No authentication, credentials, or user interaction are required.
# Patch excerpt from CHANGES.rst
Unreleased
- ``safe_join`` on Windows does not allow special devices names with empty ADS
markers on NTFS. :ghsa:`g6x2-hccm-hh4m`
- ``ProfilerMiddleware`` uses ``profiling.tracing`` on Python 3.15.
:issue:`3207`
# Source: https://github.com/pallets/werkzeug/commit/8d77320bcdf3a34941ec06dcf16b03c065cd21b6
# Patch excerpt from src/werkzeug/security.py docstring
base directory.
:return: A safe path, otherwise ``None``.
.. versionchanged:: 3.1.9
Special device names with empty ADS stream markers are not allowed on
Windows.
.. versionchanged:: 3.1.6
Special device names in multi-segment paths are not allowed on Windows.
# Source: https://github.com/pallets/werkzeug/commit/8d77320bcdf3a34941ec06dcf16b03c065cd21b6
Detection Methods for CVE-2026-102598
Indicators of Compromise
- HTTP request paths or query parameters containing Windows reserved device names followed by a colon, such as NUL:, CON:, PRN:, AUX:, or COM1:.
- Werkzeug or Flask worker processes on Windows hosts stuck in a read state with abnormally long request durations.
- Growing counts of in-flight requests to file-serving endpoints without corresponding response completions.
Detection Strategies
- Inspect web server access logs for requests targeting send_from_directory-backed routes with paths ending in a reserved device name and trailing colon.
- Monitor application worker pools for threads or processes blocked on file I/O against handles resolving to \\.\NUL or similar device paths.
- Deploy a WAF or reverse-proxy rule that flags URL segments matching the pattern (CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9]): in any position.
Monitoring Recommendations
- Alert on sustained increases in request latency or request-timeout counts against file-download endpoints on Windows-hosted Python applications.
- Track the Werkzeug version deployed across your inventory and flag any instance older than 3.1.9 running on Windows with NTFS.
- Correlate repeated failed or hung requests from a single source IP against endpoints known to invoke send_from_directory.
How to Mitigate CVE-2026-102598
Immediate Actions Required
- Upgrade Werkzeug to version 3.1.9 or later on all Windows hosts serving user-controlled file paths.
- Audit application code for calls to send_from_directory and safe_join that accept untrusted input.
- Add request-timeout limits at the WSGI server or reverse proxy layer to bound the impact of any hung request.
Patch Information
The fix is included in Werkzeug 3.1.9. The change updates safe_join to strip empty ADS markers before comparing final path segments against the Windows reserved device name list. See the GitHub Release Version 3.1.9, the GitHub Pull Request Discussion, and the GitHub Commit Update for the complete patch.
Workarounds
- Reject requests whose path segments match Windows reserved device names, with or without a trailing colon or ADS marker, at a WAF or middleware layer.
- Deploy the affected application on Linux or another non-Windows platform where the NTFS device semantics do not apply.
- Wrap file-serving handlers with a strict allow-list of filenames instead of passing arbitrary user input to send_from_directory.
# Upgrade Werkzeug to the patched release
pip install --upgrade "werkzeug>=3.1.9"
# Verify the installed version
python -c "import werkzeug; print(werkzeug.__version__)"
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

