CVE-2023-29159 is a high-severity (CVSS 7.5) Path Traversal vulnerability in starlette. 1 public exploit or proof-of-concept reference is indexed, so weaponization risk is real. A fix is available for starlette — see the affected versions and patch details below.
Starlette has Path Traversal vulnerability in StaticFiles
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
Exploitation and automatability from CISA’s SSVC triage for CVE-2023-29159.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
Where this sits among everything scored
Real-World Exposure
starletteReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
Summary
When using StaticFiles, if there's a file or directory that starts with the same name as the StaticFiles directory, that file or directory is also exposed via StaticFiles which is a path traversal vulnerability.
Details
The root cause of this issue is the usage of os.path.commonprefix():
https://github.com/encode/starlette/blob/4bab981d9e870f6cee1bd4cd59b87ddaf355b2dc/starlette/staticfiles.py#L172-L174
As stated in the Python documentation (https://docs.python.org/3/library/os.path.html#os.path.commonprefix) this function returns the longest prefix common to paths.
When passing a path like /static/../static1.txt, os.path.commonprefix([full_path, directory]) returns ./static which is the common part of ./static1.txt and ./static, It refers to /static/../static1.txt because it is considered in the staticfiles directory. As a result, it becomes possible to view files that should not be open to the public.
The solution is to use os.path.commonpath as the Python documentation explains that os.path.commonprefix works a character at a time, it does not treat the arguments as paths.
PoC
In order to reproduce the issue, you need to create the following structure:
├── static
│ ├── index.html
├── static_disallow
│ ├── index.html
└── static1.txt
And run the Starlette app with:
import uvicorn
from starlette.applications import Starlette
from starlette.routing import Mount
from starlette.staticfiles import StaticFiles
routes = [
Mount("/static", app=StaticFiles(directory="static", html=True), name="static"),
]
app = Starlette(routes=routes)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
And running the commands:
curl --path-as-is 'localhost:8000/static/../static_disallow/'
curl --path-as-is 'localhost:8000/static/../static1.txt'
The static1.txt and the directory static_disallow are exposed.
Impact
Confidentiality is breached: An attacker may obtain files that should not be open to the public.
Credits
Security researcher Masashi Yamane of LAC Co., Ltd reported this vulnerability to JPCERT/CC Vulnerability Coordination Group and they contacted us to coordinate a patch for the security issue.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | starlette | ≥ 0.13.5&&< 0.27.0 | 0.27.0pip install --upgrade 'starlette==0.27.0' |
Affected Products
starletteencodeResearch use only. For defensive security, authorized penetration testing, and academic research only. Never execute exploit code against systems without explicit written authorization.
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for starlette, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update starlette to 0.27.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2023-29159 is resolved across your whole dependency graph.
Workarounds
Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.
Frequently Asked Questions
Is CVE-2023-29159 in your dependencies?
Find it across PyPI, including transitive dependencies.