[PR #24372] [CLOSED] security: harden file serving endpoints against stored XSS #66479

Closed
opened 2026-05-06 12:52:01 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/open-webui/open-webui/pull/24372
Author: @SebTardif
Created: 5/5/2026
Status: Closed

Base: mainHead: fix/file-serving-xss-hardening


📝 Commits (2)

  • f5278f0 security: harden file serving endpoints against stored XSS
  • c0f2d50 security: 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.py pass through the uploader-controlled Content-Type header directly to the response. An attacker can:

  1. GET /{id}/content — Upload a file with Content-Type: text/html containing 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).

  2. GET /{id}/content/html — This endpoint serves files with their OS-inferred content type and no Content-Security-Policy. An admin-uploaded .html file 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 to Content-Disposition: attachment with media_type=application/octet-stream, preventing browser execution.

Notable exclusion: image/svg+xml is deliberately not in the allowlist because SVGs served as direct document responses can execute JavaScript via <script> tags and onload handlers. 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/html

The 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: nosniff

Added 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

File type Before After
PDF, images, text/plain, audio, video Inline Inline (unchanged)
text/html, application/javascript, etc. Inline with attacker-controlled type Forced download as octet-stream
SVG Inline (XSS risk) Forced download
HTML preview (/content/html) Unrestricted execution CSP sandboxed (no scripts)

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.

## 📋 Pull Request Information **Original PR:** https://github.com/open-webui/open-webui/pull/24372 **Author:** [@SebTardif](https://github.com/SebTardif) **Created:** 5/5/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/file-serving-xss-hardening` --- ### 📝 Commits (2) - [`f5278f0`](https://github.com/open-webui/open-webui/commit/f5278f0baa4402053efcdfa1d5dd7826869fe156) security: harden file serving endpoints against stored XSS - [`c0f2d50`](https://github.com/open-webui/open-webui/commit/c0f2d503bf36ec67a7943eece17bd13aae2dca72) security: address review feedback on file serving XSS hardening ### 📊 Changes **1 file changed** (+62 additions, -14 deletions) <details> <summary>View changed files</summary> 📝 `backend/open_webui/routers/files.py` (+62 -14) </details> ### 📄 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.py` pass through the uploader-controlled `Content-Type` header directly to the response. An attacker can: 1. **`GET /{id}/content`** — Upload a file with `Content-Type: text/html` containing 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). 2. **`GET /{id}/content/html`** — This endpoint serves files with their OS-inferred content type and no Content-Security-Policy. An admin-uploaded `.html` file 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 to `Content-Disposition: attachment` with `media_type=application/octet-stream`, preventing browser execution. Notable exclusion: `image/svg+xml` is deliberately **not** in the allowlist because SVGs served as direct document responses can execute JavaScript via `<script>` tags and `onload` handlers. 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/html` The 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: nosniff` Added 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 | File type | Before | After | |---|---|---| | PDF, images, text/plain, audio, video | Inline | Inline (unchanged) | | text/html, application/javascript, etc. | Inline with attacker-controlled type | Forced download as octet-stream | | SVG | Inline (XSS risk) | Forced download | | HTML preview (`/content/html`) | Unrestricted execution | CSP sandboxed (no scripts) | No changes to upload logic, storage, or access control. Only the response headers and content type on file-serving responses are affected. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-05-06 12:52:01 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#66479