CVE-2023-29159
Description
Directory traversal in Starlette allows remote unauthenticated attackers to view arbitrary files in the web service.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Directory traversal in Starlette allows remote unauthenticated attackers to view arbitrary files in the web service.
Vulnerability
CVE-2023-29159 is a directory traversal vulnerability in Starlette versions 0.13.5 through 0.26.x. The issue arises from improper handling of file paths, enabling an attacker to read files outside the intended directory.
Exploitation
An unauthenticated remote attacker can exploit this by sending crafted requests that traverse directories (e.g., using ../ sequences). Successful exploitation requires a running Starlette application that serves static files or uses similar functionality.
Impact
An attacker can read arbitrary files on the server, potentially accessing sensitive data such as configuration files, source code, or credentials. The CVSS score is 3.7 (Low) due to high attack complexity and limited impact (confidentiality only).
Mitigation
The vulnerability is fixed in Starlette version 0.27.0. Users should upgrade immediately. There are no known workarounds. The issue was reported by JPCERT/CC and is tracked in the Starlette GitHub repository [1][2][3].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
starlettePyPI | >= 0.13.5, < 0.27.0 | 0.27.0 |
Affected products
2Patches
11797de464124Merge pull request from GHSA-v5gw-mw7f-84px
2 files changed · +38 −6
starlette/staticfiles.py+1 −1 modified@@ -169,7 +169,7 @@ def lookup_path( else: full_path = os.path.realpath(joined_path) directory = os.path.realpath(directory) - if os.path.commonprefix([full_path, directory]) != directory: + if os.path.commonpath([full_path, directory]) != directory: # Don't allow misbehaving clients to break out of the static files # directory. continue
tests/test_staticfiles.py+37 −5 modified@@ -1,8 +1,8 @@ import os -import pathlib import stat import tempfile import time +from pathlib import Path import anyio import pytest @@ -28,13 +28,12 @@ def test_staticfiles(tmpdir, test_client_factory): assert response.text == "<file content>" -def test_staticfiles_with_pathlib(tmpdir, test_client_factory): - base_dir = pathlib.Path(tmpdir) - path = base_dir / "example.txt" +def test_staticfiles_with_pathlib(tmp_path: Path, test_client_factory): + path = tmp_path / "example.txt" with open(path, "w") as file: file.write("<file content>") - app = StaticFiles(directory=base_dir) + app = StaticFiles(directory=tmp_path) client = test_client_factory(app) response = client.get("/example.txt") assert response.status_code == 200 @@ -516,3 +515,36 @@ def test_staticfiles_disallows_path_traversal_with_symlinks(tmpdir): assert exc_info.value.status_code == 404 assert exc_info.value.detail == "Not Found" + + +def test_staticfiles_avoids_path_traversal(tmp_path: Path): + statics_path = tmp_path / "static" + statics_disallow_path = tmp_path / "static_disallow" + + statics_path.mkdir() + statics_disallow_path.mkdir() + + static_index_file = statics_path / "index.html" + statics_disallow_path_index_file = statics_disallow_path / "index.html" + static_file = tmp_path / "static1.txt" + + static_index_file.write_text("<h1>Hello</h1>") + statics_disallow_path_index_file.write_text("<h1>Private</h1>") + static_file.write_text("Private") + + app = StaticFiles(directory=statics_path) + + # We can't test this with 'httpx', so we test the app directly here. + path = app.get_path({"path": "/../static1.txt"}) + with pytest.raises(HTTPException) as exc_info: + anyio.run(app.get_response, path, {"method": "GET"}) + + assert exc_info.value.status_code == 404 + assert exc_info.value.detail == "Not Found" + + path = app.get_path({"path": "/../static_disallow/index.html"}) + with pytest.raises(HTTPException) as exc_info: + anyio.run(app.get_response, path, {"method": "GET"}) + + assert exc_info.value.status_code == 404 + assert exc_info.value.detail == "Not Found"
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
9- github.com/advisories/GHSA-v5gw-mw7f-84pxghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-29159ghsaADVISORY
- github.com/encode/starlette/blob/4bab981d9e870f6cee1bd4cd59b87ddaf355b2dc/starlette/staticfiles.pyghsaWEB
- github.com/encode/starlette/commit/1797de464124b090f10cf570441e8292936d63e3ghsaWEB
- github.com/encode/starlette/releases/tag/0.27.0ghsaWEB
- github.com/encode/starlette/security/advisories/GHSA-v5gw-mw7f-84pxghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/starlette/PYSEC-2023-83.yamlghsaWEB
- jvn.jp/en/jp/JVN95981715ghsaWEB
- jvn.jp/en/jp/JVN95981715/mitre
News mentions
0No linked articles in our index yet.