CVE-2026-27199
NoneCVSS
—
EPSS
0.6%
Published
Feb 21, 2026
CISA due date
—
Cloudanix analysis
A flaw in a common Python web library can let someone make certain Windows-hosted websites hang by requesting specially named files.
- Exploitation
- Remote Unauthenticated
- Attacker needs
- None
- Complexity
- Moderate
- Impact
- Denial Of Service
- Component
- Library
- Class
- Improper Input Validation
- Remediation
- Upgrade
- Effort
- Low
- Patch risk
- Low
What it actually does
Werkzeug versions 3.1.5 and earlier do not fully reject Windows reserved device names when safe_join receives multiple path segments, such as example/NUL. Applications that use safe_join directly, or indirectly through send_from_directory, to serve user-selected files on Windows can be made to open a reserved device name as if it were a normal file. The open succeeds, but the subsequent read can hang indefinitely, tying up the request handler or worker. The impact is availability degradation or denial of service rather than data disclosure or code execution.
Why it matters to the business
Public or internal Python web applications running on Windows and serving files through Werkzeug may become slow or unavailable if an attacker sends repeated requests for crafted filenames. The current exploitation signal is low: EPSS is 0.00556, exploit maturity is listed as none, and it is not in the CISA Known Exploited Vulnerabilities catalog or known ransomware use. However, because exploitation is unauthenticated and network-reachable when an affected file-serving route exists, internet-facing Windows deployments should still be remediated during normal patch cycles or sooner for high-availability services.
How an attack unfolds
An attacker first identifies a Python web application running on Windows that exposes a route backed by Werkzeug safe_join or send_from_directory, such as a static-file, upload-download, attachment, or user-content endpoint. The attacker then sends a request where the user-controlled path contains at least one ordinary directory segment followed by a Windows device filename, for example a path ending in /NUL, /CON, /PRN, /AUX, /COM1, or /LPT1. In vulnerable versions, the path validation permits the multi-segment value. Windows resolves the final reserved device name specially; Werkzeug opens it successfully and then attempts to read from it. The read does not complete, leaving the application thread, process, or worker occupied. Repeating the request can exhaust the available worker pool, connection capacity, or reverse-proxy timeouts, causing legitimate users to experience stalled downloads, elevated latency, or service unavailability.
How to fix it
Upgrade Werkzeug to version 3.1.6 or later. If consuming directly from the pallets/werkzeug Git repository, use a revision that includes commit 04da1b5221b7a7b57e82246e4b5741d37a6b2e56 or a later fixed commit. Rebuild and redeploy all applications and container images that bundle vulnerable Werkzeug versions, including transitive dependencies from Flask-based applications. If immediate upgrade is not possible, add application- or reverse-proxy-level filtering for Windows reserved device names in every user-controlled path segment, not only the final full path. Reject case-insensitive basenames matching CON, PRN, AUX, NUL, COM1 through COM9, or LPT1 through LPT9, including names with extensions such as NUL.txt. Also enforce conservative request timeouts, worker limits, and download timeouts to reduce the blast radius of hanging reads. Avoid exposing arbitrary user-supplied paths to send_from_directory without strict allowlisting.
How to confirm the fix worked
Confirm that deployed applications no longer use Werkzeug 3.1.5 or earlier by checking package metadata, lockfiles, SBOMs, and runtime environments, for example with pip show Werkzeug, pip freeze, poetry.lock, requirements.txt, Pipfile.lock, or importlib.metadata.version('werkzeug'). For Git-pinned builds, confirm the pinned commit includes 04da1b5221b7a7b57e82246e4b5741d37a6b2e56 or a later fix. On a non-production Windows test instance, request a file-serving route with paths such as example/NUL, example/CON, example/AUX.txt, example/COM1, and example/LPT1. A fixed deployment should promptly reject the request, usually with 400 or 404 behavior depending on the application, and must not hang the worker or leave the connection open indefinitely. Monitor worker counts, request duration, and error logs during the test to verify that no request handler remains stuck.
Typical attack path
- Find a Flask/Werkzeug-based web application or API endpoint that serves files from a user-supplied path.
- Confirm the target is likely running on Windows or probe for Windows-specific behavior using reserved device-name filenames.
- Send a crafted file request with multiple path segments where the final segment is a Windows reserved device name, such as example/NUL.
- Observe that the request hangs or consumes server-side processing time instead of returning a normal not-found or bad-request response.
- Repeat the crafted requests concurrently to consume application workers, threads, or request slots.
- Maintain pressure until the service becomes degraded or unavailable for legitimate users.
How to detect it
Look for evidence of requests to file-serving routes where the URL path ends with or contains Windows reserved device names after another path segment. Primary log sources are web server access logs, reverse-proxy logs, WAF logs, application request logs, APM traces, and load balancer logs. Pay special attention to requests with long durations, upstream timeout responses, gateway timeout responses, client disconnects, incomplete responses, or a sudden increase in concurrent requests from the same source. In application telemetry, look for Flask/Werkzeug endpoints that serve files and show long-running requests without corresponding bytes sent. On Windows hosts, correlate these requests with Python worker processes that remain busy, thread dumps showing file read/send operations, elevated worker utilization, or repeated worker recycling due to timeouts. Because exploitation does not require successful authentication and may not create files or persistent artifacts, detection is primarily log- and telemetry-based rather than filesystem-based.
- /NUL
- /CON
- /PRN
- /AUX
- /COM1
- /COM2
- /COM3
- /COM4
- /COM5
- /COM6
- /COM7
- /COM8
- /COM9
- /LPT1
- /LPT2
- /LPT3
- /LPT4
- /LPT5
- /LPT6
- /LPT7
- /LPT8
- /LPT9
- %2fNUL
- %2fCON
- %2fPRN
- %2fAUX
- %2fCOM1
- %2fLPT1
- \NUL
- \CON
- \PRN
- \AUX
- \COM1
- \LPT1
- NUL.txt
- CON.txt
- PRN.txt
- AUX.txt
- COM1.txt
- LPT1.txt
- upstream timed out
- gateway timeout
- client timed out
- request timed out
What your security team should do
- Inventory Python web applications that run on Windows and use Werkzeug, Flask static-file serving, send_from_directory, or direct safe_join calls.
- Prioritize upgrade to Werkzeug 3.1.6 or later for internet-facing Windows deployments and high-availability services.
- Add temporary WAF, reverse-proxy, or application rules blocking path segments that are Windows reserved device names: CON, PRN, AUX, NUL, COM1-COM9, and LPT1-LPT9, including variants with extensions.
- Review access logs for historical requests ending in reserved device names, especially requests with unusually long duration, client disconnects, gateway timeouts, or upstream timeouts.
- Tune request, upstream, and worker timeouts so a single hanging file read cannot indefinitely consume an application worker.
- Add regression tests for file-serving endpoints to ensure Windows device names are rejected in every path segment.
- Ensure dependency scanners and SBOM tooling flag Werkzeug versions 3.1.5 and earlier in application repositories, containers, and build artifacts.
The record
Werkzeug is a comprehensive WSGI web application library. Versions 3.1.5 and below, the safe_join function allows Windows device names as filenames if preceded by other path segments. This was previously reported as GHSA-hgf8-39gv-g3f2, but the added filtering failed to account for the fact that safe_join accepts paths with multiple segments, such as example/NUL. The function send_from_directory uses safe_join to safely serve files at user-specified paths under a directory. If the application is running on Windows, and the requested path ends with a special device name, the file will be opened successfully, but reading will hang indefinitely. This issue has been fixed in version 3.1.6.
Affected packages
| Ecosystem | Package | Introduced | Fixed |
|---|---|---|---|
| PyPI | werkzeug | 0 | 3.1.6 |
| Chainguard | airflow-2 | 0 | 2.11.1-r0 |
| Chainguard | airflow-core-2 | 0 | 2.11.1-r0 |
| Chainguard | kubeflow-pipelines-visualization-server | 0 | 2.16.0-r1 |
| Chainguard | kubeflow-volumes-web-app | 0 | 1.10.0-r11 |
| Chainguard | litellm | 0 | 1.81.12.1-r0 |
| Chainguard | localstack | 0 | 4.13.1-r2 |
| Chainguard | mlflow | 0 | 3.9.0-r1 |
| Chainguard | mlflow-iamguarded-compat | 0 | 3.9.0-r1 |
| Chainguard | open-webui | 0 | 0.8.3-r1 |
| Chainguard | py3.10-ambassador | 0 | 3.10.0-r20 |
| Chainguard | py3.11-ambassador | 0 | 3.10.0-r20 |
| Chainguard | py3.11-azure-functions-worker | 0 | 4.43.0-r0 |
| Chainguard | py3.12-ambassador | 0 | 3.10.0-r20 |
| Chainguard | py3.12-azure-functions-worker | 0 | 4.43.0-r0 |
| Chainguard | py3.13-ambassador | 0 | 3.10.0-r20 |
| Chainguard | py3.13-azure-functions-worker | 0 | 4.43.0-r0 |
| Chainguard | superset-5.0 | 0 | 5.0.0-r19 |
| Chainguard | superset-6.0 | 0 | 6.0.0-r3 |
| Chainguard | tensorflow-cpu-jupyter | 0 | 2.20.0-r12 |
| Chainguard | tensorflow-gpu-jupyter | 0 | 2.20.0-r11 |
| Wolfi | kubeflow-pipelines-visualization-server | 0 | 2.16.0-r1 |
| Wolfi | kubeflow-volumes-web-app | 0 | 1.10.0-r11 |
| Wolfi | mlflow | 0 | 3.9.0-r1 |
| Wolfi | mlflow-iamguarded-compat | 0 | 3.9.0-r1 |
| Wolfi | open-webui | 0 | 0.8.3-r1 |
| Wolfi | py3.10-ambassador | 0 | 3.10.0-r20 |
| Wolfi | py3.11-ambassador | 0 | 3.10.0-r20 |
| Wolfi | py3.12-ambassador | 0 | 3.10.0-r20 |
| Wolfi | py3.13-ambassador | 0 | 3.10.0-r20 |
| Wolfi | superset-5.0 | 0 | 5.0.0-r19 |
| Wolfi | superset-6.0 | 0 | 6.0.0-r3 |
| Wolfi | tensorflow-cpu-jupyter | 0 | 2.20.0-r12 |
Related vulnerabilities
- CVE-2026-21860 Werkzeug is a comprehensive WSGI web application library. Also affects werkzeug
- CVE-2025-66221 Werkzeug safe_join() allows Windows special device names Also affects werkzeug
- CVE-2024-49766 Werkzeug is a Web Server Gateway Interface web application library. Also affects werkzeug
- CVE-2024-49767 Werkzeug is a Web Server Gateway Interface web application library. Also affects werkzeug
- CVE-2023-46136 Werkzeug is a comprehensive WSGI web application library. Also affects werkzeug
- CVE-2016-10516 Cross-site scripting (XSS) vulnerability in the render_full function in debug/tbtools.py… Also affects werkzeug
- CVE-2026-4539 A security flaw has been discovered in pygments up to 2.19.2. Also affects airflow-2
- CVE-2026-30922 pyasn1 is a generic ASN.1 library for Python. Also affects airflow-2