mirror of
https://github.com/open-webui/open-webui.git
synced 2026-05-07 11:28:35 -05:00
[PR #24372] [CLOSED] security: harden file serving endpoints against stored XSS #66479
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/open-webui/open-webui/pull/24372
Author: @SebTardif
Created: 5/5/2026
Status: ❌ Closed
Base:
main← Head:fix/file-serving-xss-hardening📝 Commits (2)
f5278f0security: harden file serving endpoints against stored XSSc0f2d50security: address review feedback on file serving XSS hardening📊 Changes
1 file changed (+62 additions, -14 deletions)
View changed files
📝
backend/open_webui/routers/files.py(+62 -14)📄 Description
Summary
Prevent stored XSS attacks through file-serving endpoints by validating content types against a safe allowlist and adding security headers.
Problem
The file-serving endpoints in
backend/open_webui/routers/files.pypass through the uploader-controlledContent-Typeheader directly to the response. An attacker can:GET /{id}/content— Upload a file withContent-Type: text/htmlcontaining malicious<script>tags. When any authenticated user with access visits the file URL, the browser renders it as HTML under the app's origin, enabling full same-origin script execution (cookie theft, API calls as victim, data exfiltration).GET /{id}/content/html— This endpoint serves files with their OS-inferred content type and no Content-Security-Policy. An admin-uploaded.htmlfile executes JavaScript freely when visited by any authenticated user.Both vectors result in stored XSS — the payload persists in the uploaded file and triggers on every visit.
Fix
Content-type allowlist (default-deny)
Added
SAFE_INLINE_CONTENT_TYPES— a set of content types known to be safe for inline rendering (images, PDF, plain text, audio, video). Files with content types not on the allowlist are forced toContent-Disposition: attachmentwithmedia_type=application/octet-stream, preventing browser execution.Notable exclusion:
image/svg+xmlis deliberately not in the allowlist because SVGs served as direct document responses can execute JavaScript via<script>tags andonloadhandlers. SVGs loaded via<img>tags (the normal frontend path) are safe since browsers disable scripting in that context.MIME parameters (e.g.
image/png; charset=utf-8) are stripped before comparison so legitimate files are not inadvertently blocked.CSP sandbox on
/content/htmlThe HTML preview endpoint now includes
Content-Security-Policy: sandbox; default-src 'none'; style-src 'unsafe-inline'; img-src data:which blocks script execution while still allowing the HTML to render for preview purposes.X-Content-Type-Options: nosniffAdded to all three file-serving endpoints (
/{id}/content,/{id}/content/html,/{id}/content/{file_name}) to prevent browsers from MIME-sniffing uploaded files as executable content.Behavioral impact
/content/html)No changes to upload logic, storage, or access control. Only the response headers and content type on file-serving responses are affected.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.