GHSA-vxqx-rh46-q2pg
MEDIUMGHSA-vxqx-rh46-q2pg is a medium-severity (CVSS 6.5) CWE-176 vulnerability in litestar. O3 Security confirms whether GHSA-vxqx-rh46-q2pg is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
Litestar's FileStore key canonicalization collisions allow response cache mixup/poisoning (ASCII ord + Unicode NFKD)
Blast Radius
litestarReal-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
FileStore maps cache keys to filenames using Unicode NFKD normalization and ord() substitution without separators, creating key collisions. When FileStore is used as response-cache backend, an unauthenticated remote attacker can trigger cache key collisions via crafted paths, causing one URL to serve cached responses of another (cache poisoning/mixup)
Details
litestar.stores.file._safe_file_name() normalizes input with unicodedata.normalize("NFKD", name) and builds the filename by concatenating c if alphanumeric else str(ord(c)) (no delimiter). This transformation is not injective, e.g.:
- "k-" and "k45" both become "k45" (because - ord('-') == 45)
- "k/\n" becomes "k4710", colliding with "k4710"
- "K" (Kelvin sign) normalizes to "K", colliding with "K"
When used in response caching, the default cache key includes request path and sorted query params, which are attacker-controlled.
PoC
import asyncio, tempfile
from litestar.stores.file import FileStore
async def main():
d = tempfile.mkdtemp(prefix="ls_filestore_poc_")
store = FileStore(d, create_directories=True)
await store.__aenter__()
# 1) ASCII ord-collision: "-" -> 45
await store.set("k-", b"A")
v = await store.get("k45")
print("k- ->", v)
print("k45 ->", await store.get("k45"))
if v == b"A":
print("VULNERABLE: 'k-' collides with 'k45'")
# 2) NFKD collision: Kelvin sign -> K
await store.set("K", b"B") # U+212A
v2 = await store.get("K")
print("K ->", await store.get("K"))
print("K ->", v2)
if v2 == b"B":
print("VULNERABLE: 'K' collides with 'K' (NFKD)")
if __name__ == "__main__":
asyncio.run(main())
Impact
Vulnerability type: cache poisoning / cache key collision. Impacted deployments: applications using Litestar response caching with FileStore backend (or any attacker-influenced keying into FileStore). Possible impact: serving incorrect cached content across distinct URLs, potential confidentiality/integrity issues depending on what endpoints are cached.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | litestar | ≥ 2.19.0&&< 2.20.0 | 2.20.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for litestar. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update litestar to 2.20.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-vxqx-rh46-q2pg is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-vxqx-rh46-q2pg is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-vxqx-rh46-q2pg. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-vxqx-rh46-q2pg in your dependencies?
O3 detects GHSA-vxqx-rh46-q2pg across PyPI dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.