CVE-2025-66221 Overview
CVE-2025-66221 affects Werkzeug, the WSGI web application library used by Flask and other Python frameworks. Versions prior to 3.1.4 contain a flaw in the safe_join function that fails to reject Windows reserved device names such as CON, AUX, PRN, and NUL. When send_from_directory serves a file whose path terminates in one of these device names, the operating system opens the device successfully but the subsequent read call hangs indefinitely. The result is a resource exhaustion condition on Windows-hosted Werkzeug applications. The Pallets project patched the issue in Werkzeug 3.1.4.
Critical Impact
Remote unauthenticated attackers can trigger indefinite worker hangs on Windows-hosted Werkzeug applications by requesting paths ending in reserved device names, exhausting server threads and causing denial of service.
Affected Products
- Palletsprojects Werkzeug versions prior to 3.1.4
- Microsoft Windows (all supported versions running affected Werkzeug builds)
- Downstream frameworks depending on vulnerable Werkzeug releases, including Flask deployments on Windows
Discovery Timeline
- 2025-11-29 - CVE-2025-66221 published to NVD
- 2026-06-17 - Last updated in NVD database
Technical Details for CVE-2025-66221
Vulnerability Analysis
Werkzeug's safe_join helper normalizes user-supplied path segments to prevent directory traversal before they are handed to file-serving utilities. The pre-3.1.4 implementation only checked for absolute paths and alternate directory separators. It did not reject the DOS legacy device names that Windows treats as implicit files in every directory. When send_from_directory opens C:\app\static\CON or a similar reserved name, the Windows filesystem redirects the handle to the corresponding device driver. Subsequent read() calls on CON block waiting for console input that never arrives, tying up the serving worker until forced termination. The issue is tracked under CWE-67: Improper Handling of Windows Device Names.
Root Cause
The root cause is missing validation for Windows-reserved DOS device identifiers in safe_join. Windows reserves names including CON, PRN, AUX, NUL, COM0–COM9, and LPT0–LPT9. The companion function secure_filename already stripped these names for upload paths, but the read path via safe_join did not. The patch adds a _windows_device_files set to src/werkzeug/security.py and rejects any segment whose upper-cased stem matches an entry in that set.
Attack Vector
An unauthenticated remote attacker crafts an HTTP request whose path parameter resolves through send_from_directory to a Windows device name. Because the request only requires network access and no authentication, any exposed endpoint that streams user-controlled filenames is reachable. Each malicious request consumes one worker thread indefinitely, so a small number of requests can stall the application.
# Patch excerpt: src/werkzeug/security.py
_os_alt_seps: list[str] = list(
sep for sep in [os.sep, os.path.altsep] if sep is not None and sep != "/"
)
_windows_device_files = {
"CON",
"PRN",
"AUX",
"NUL",
*(f"COM{i}" for i in range(10)),
*(f"LPT{i}" for i in range(10)),
}
def gen_salt(length: int) -> str:
Source: Pallets Werkzeug commit 4b83337
Detection Methods for CVE-2025-66221
Indicators of Compromise
- HTTP request logs containing path segments matching CON, PRN, AUX, NUL, COM0–COM9, or LPT0–LPT9, with or without file extensions
- Werkzeug or Flask worker processes on Windows hosts that remain in a blocked state on file I/O without returning a response
- Sudden increases in worker pool saturation, request queue depth, or 5xx error rates on Windows application servers
Detection Strategies
- Inspect access logs for request URIs matching the regex (?i)/(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])(\.|$|/) targeting endpoints backed by send_from_directory
- Correlate long-running requests against process-level file handle telemetry to identify handles opened against Windows device paths
- Add a WAF or reverse-proxy rule that flags path segments matching reserved device names and route matches to a monitoring pipeline
Monitoring Recommendations
- Track worker thread lifetime and alert when request handling exceeds expected timeouts on Windows Werkzeug hosts
- Ingest IIS, Nginx, or Apache access logs into a centralized store and query for reserved-name paths using OCSF-normalized URL fields
- Monitor application dependency inventories for Werkzeug versions below 3.1.4 and flag Windows deployments as elevated priority
How to Mitigate CVE-2025-66221
Immediate Actions Required
- Upgrade Werkzeug to version 3.1.4 or later across all Windows-hosted applications and rebuild dependent images
- Audit application code for calls to send_from_directory, safe_join, or any handler that concatenates user input into filesystem paths
- Deploy a temporary WAF rule to block requests whose final path segment matches a Windows reserved device name
Patch Information
The Pallets team fixed the flaw in Werkzeug 3.1.4 by adding a _windows_device_files denylist to safe_join. Details are documented in the GitHub Security Advisory GHSA-hgf8-39gv-g3f2 and the upstream commit 4b83337. Update using pip install --upgrade werkzeug>=3.1.4 and redeploy dependent Flask or Werkzeug services.
Workarounds
- Migrate the affected service off Windows to a Linux host where the reserved device names have no filesystem meaning
- Wrap file-serving routes with request validation that rejects any path segment whose stem, when upper-cased, matches CON, PRN, AUX, NUL, COM0–COM9, or LPT0–LPT9
- Enforce a strict request timeout on the WSGI server or reverse proxy so hung workers are terminated instead of stalling indefinitely
# Upgrade Werkzeug to a patched release
pip install --upgrade "werkzeug>=3.1.4"
# Verify 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.

