Werkzeug safe_join() allows Windows special device names
Description
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
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
werkzeugPyPI | < 3.1.6 | 3.1.6 |
Affected products
1Patches
13 files changed · +32 −16
CHANGES.rst+3 −0 modified@@ -5,6 +5,9 @@ Version 3.1.6 Unreleased +- ``safe_join`` on Windows does not allow special devices names in + multi-segment paths. :ghsa:`29vq-49wr-vm6x` + Version 3.1.5 -------------
src/werkzeug/security.py+28 −15 modified@@ -10,7 +10,7 @@ DEFAULT_PBKDF2_ITERATIONS = 1_000_000 _os_alt_seps: list[str] = list( - sep for sep in [os.sep, os.path.altsep] if sep is not None and sep != "/" + sep for sep in [os.sep, os.altsep] if sep is not None and sep != "/" ) # https://chrisdenton.github.io/omnipath/Special%20Dos%20Device%20Names.html _windows_device_files = { @@ -142,15 +142,23 @@ def check_password_hash(pwhash: str, password: str) -> bool: return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval) -def safe_join(directory: str, *pathnames: str) -> str | None: - """Safely join zero or more untrusted path components to a base +def safe_join(directory: str, *untrusted: str) -> str | None: + """Safely join zero or more untrusted path components to a trusted base directory to avoid escaping the base directory. + The untrusted path is assumed to be from/for a URL, such as for serving + files. Therefore, it should only use the forward slash ``/`` path separator, + and will be joined using that separator. On Windows, the backslash ``\\`` + separator is not allowed. + :param directory: The trusted base directory. - :param pathnames: The untrusted path components relative to the + :param untrusted: The untrusted path components relative to the base directory. :return: A safe path, otherwise ``None``. + .. versionchanged:: 3.1.6 + Special device names in multi-segment paths are not allowed on Windows. + .. versionchanged:: 3.1.5 More special device names, regardless of extension or trailing spaces, are not allowed on Windows. @@ -165,24 +173,29 @@ def safe_join(directory: str, *pathnames: str) -> str | None: parts = [directory] - for filename in pathnames: - if filename != "": - filename = posixpath.normpath(filename) + for part in untrusted: + if not part: + continue + + part = posixpath.normpath(part) if ( - any(sep in filename for sep in _os_alt_seps) + os.path.isabs(part) + # ntpath.isabs doesn't catch this + or part.startswith("/") + or part == ".." + or part.startswith("../") + or any(sep in part for sep in _os_alt_seps) or ( os.name == "nt" - and filename.partition(".")[0].strip().upper() in _windows_device_files + and any( + p.partition(".")[0].strip().upper() in _windows_device_files + for p in part.split("/") + ) ) - or os.path.isabs(filename) - # ntpath.isabs doesn't catch this on Python < 3.11 - or filename.startswith("/") - or filename == ".." - or filename.startswith("../") ): return None - parts.append(filename) + parts.append(part) return posixpath.join(*parts)
tests/test_security.py+1 −1 modified@@ -73,7 +73,7 @@ def test_safe_join_empty_trusted(): @pytest.mark.parametrize( - "name", ["CON", "CON.txt", "CON.txt.html", "CON ", "CON . txt"] + "name", ["CON", "CON.txt", "CON.txt.html", "CON ", "CON . txt", "b/CON"] ) def test_safe_join_windows_special(monkeypatch: pytest.MonkeyPatch, name: str) -> None: """Windows special device name is not allowed on Windows."""
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-29vq-49wr-vm6xghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-27199ghsaADVISORY
- github.com/pallets/werkzeug/commit/f407712fdc60a09c2b3f4fe7db557703e5d9338dghsax_refsource_MISCWEB
- github.com/pallets/werkzeug/releases/tag/3.1.6ghsax_refsource_MISCWEB
- github.com/pallets/werkzeug/security/advisories/GHSA-29vq-49wr-vm6xghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.