[PR #24373] [CLOSED] fix: harden file serving endpoints against stored XSS #66480

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

📋 Pull Request Information

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

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


📝 Commits (2)

  • fd83612 security: harden file serving endpoints against stored XSS
  • addc526 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

Pull Request Checklist

Before submitting, make sure you've checked the following:

  • Target branch: Verify that the pull request targets the dev branch.
  • Description: Provided below.
  • Changelog: Included below.
  • Documentation: No user-facing config changes; no docs needed.
  • Dependencies: No new dependencies.
  • Testing: Manually verified that PDFs, images, and text/plain files still serve inline. HTML and SVG files are now forced to download. The /content/html endpoint still renders HTML but with CSP sandbox blocking script execution.
  • Agentic AI Code: This PR has gone through human review and manual testing.
  • Code review: Self-reviewed; also ran an agent code review that caught SVG in the allowlist (fixed) and dead code (cleaned up).
  • Design & Architecture: No new settings or UI changes.
  • Git Hygiene: Atomic PR, rebased on dev, two clean commits.
  • Title Prefix: fix:

Changelog Entry

Description

Harden file-serving endpoints against stored XSS by validating content types against a safe allowlist and adding security headers. Previously, an attacker could upload a file with Content-Type: text/html containing malicious scripts, and any authenticated user viewing the file would execute the scripts under the app's origin.

Added

  • SAFE_INLINE_CONTENT_TYPES allowlist for content types safe to serve inline (images, PDF, plain text, audio, video)
  • _safe_content_type() helper that validates and normalizes content types against the allowlist

Changed

  • GET /{id}/content now validates content type against allowlist; unsafe types forced to application/octet-stream + attachment download
  • GET /{id}/content/html now includes Content-Security-Policy: sandbox header blocking script execution
  • GET /{id}/content/{file_name} now explicitly sets media_type=application/octet-stream
  • All three endpoints now include X-Content-Type-Options: nosniff

Deprecated

  • N/A

Removed

  • Removed duplicate variable assignments (dead code cleanup)

Fixed

  • Stored XSS via user-controlled Content-Type: uploading files with Content-Type: text/html no longer results in inline HTML rendering
  • Stored XSS via HTML preview endpoint: admin-uploaded HTML files can no longer execute scripts when viewed by other users
  • SVG script execution: SVGs served as direct document responses can no longer execute JavaScript (excluded from inline allowlist)

Security

  • Content-type allowlist prevents browsers from executing uploaded files as HTML/JavaScript
  • CSP sandbox on HTML preview endpoint blocks script execution
  • X-Content-Type-Options: nosniff prevents MIME-sniffing attacks
  • MIME parameters stripped before allowlist comparison to prevent bypasses

Breaking Changes

  • SVG files served via /{id}/content will now download instead of displaying inline. SVGs loaded via <img> tags in the frontend are unaffected since browsers disable scripting in that context.
  • Files with exotic content types (e.g. text/html, application/javascript) will now force-download instead of rendering inline.

Additional Information

  • The /content/html endpoint is already restricted to files owned by admin-role users (line 724 check), but in multi-admin deployments a compromised admin could target other admins. The CSP sandbox provides defense-in-depth.
  • The users.py and channels.py profile image endpoints also decode data URIs with user-controlled media types — these are out of scope for this PR but could be addressed separately.

Contributor License Agreement

Note

Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.


🔄 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/24373 **Author:** [@SebTardif](https://github.com/SebTardif) **Created:** 5/5/2026 **Status:** ❌ Closed **Base:** `dev` ← **Head:** `fix/file-serving-xss-hardening` --- ### 📝 Commits (2) - [`fd83612`](https://github.com/open-webui/open-webui/commit/fd8361217fd72c0d2209cb9bfba5ed611f7fbb2d) security: harden file serving endpoints against stored XSS - [`addc526`](https://github.com/open-webui/open-webui/commit/addc526be0fc314c792011361fd8c017394f196f) 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 <!-- ⚠️ CRITICAL CHECKS FOR CONTRIBUTORS (READ, DON'T DELETE) ⚠️ 1. Target the `dev` branch. PRs targeting `main` will be automatically closed. 2. Do NOT delete the CLA section at the bottom. It is required for the bot to accept your PR. --> # Pull Request Checklist **Before submitting, make sure you've checked the following:** - [x] **Target branch:** Verify that the pull request targets the `dev` branch. - [x] **Description:** Provided below. - [x] **Changelog:** Included below. - [ ] **Documentation:** No user-facing config changes; no docs needed. - [x] **Dependencies:** No new dependencies. - [x] **Testing:** Manually verified that PDFs, images, and text/plain files still serve inline. HTML and SVG files are now forced to download. The `/content/html` endpoint still renders HTML but with CSP sandbox blocking script execution. - [x] **Agentic AI Code:** This PR has gone through human review and manual testing. - [x] **Code review:** Self-reviewed; also ran an agent code review that caught SVG in the allowlist (fixed) and dead code (cleaned up). - [x] **Design & Architecture:** No new settings or UI changes. - [x] **Git Hygiene:** Atomic PR, rebased on `dev`, two clean commits. - [x] **Title Prefix:** `fix:` # Changelog Entry ### Description Harden file-serving endpoints against stored XSS by validating content types against a safe allowlist and adding security headers. Previously, an attacker could upload a file with `Content-Type: text/html` containing malicious scripts, and any authenticated user viewing the file would execute the scripts under the app's origin. ### Added - `SAFE_INLINE_CONTENT_TYPES` allowlist for content types safe to serve inline (images, PDF, plain text, audio, video) - `_safe_content_type()` helper that validates and normalizes content types against the allowlist ### Changed - `GET /{id}/content` now validates content type against allowlist; unsafe types forced to `application/octet-stream` + attachment download - `GET /{id}/content/html` now includes `Content-Security-Policy: sandbox` header blocking script execution - `GET /{id}/content/{file_name}` now explicitly sets `media_type=application/octet-stream` - All three endpoints now include `X-Content-Type-Options: nosniff` ### Deprecated - N/A ### Removed - Removed duplicate variable assignments (dead code cleanup) ### Fixed - **Stored XSS via user-controlled Content-Type**: uploading files with `Content-Type: text/html` no longer results in inline HTML rendering - **Stored XSS via HTML preview endpoint**: admin-uploaded HTML files can no longer execute scripts when viewed by other users - **SVG script execution**: SVGs served as direct document responses can no longer execute JavaScript (excluded from inline allowlist) ### Security - Content-type allowlist prevents browsers from executing uploaded files as HTML/JavaScript - CSP sandbox on HTML preview endpoint blocks script execution - `X-Content-Type-Options: nosniff` prevents MIME-sniffing attacks - MIME parameters stripped before allowlist comparison to prevent bypasses ### Breaking Changes - SVG files served via `/{id}/content` will now download instead of displaying inline. SVGs loaded via `<img>` tags in the frontend are unaffected since browsers disable scripting in that context. - Files with exotic content types (e.g. `text/html`, `application/javascript`) will now force-download instead of rendering inline. --- ### Additional Information - The `/content/html` endpoint is already restricted to files owned by admin-role users (line 724 check), but in multi-admin deployments a compromised admin could target other admins. The CSP sandbox provides defense-in-depth. - The `users.py` and `channels.py` profile image endpoints also decode data URIs with user-controlled media types — these are out of scope for this PR but could be addressed separately. ### Contributor License Agreement <!-- 🚨 DO NOT DELETE THE TEXT BELOW 🚨 Keep the "Contributor License Agreement" confirmation text intact. Deleting it will trigger the CLA-Bot to INVALIDATE your PR. Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA. --> - [x] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms. > [!NOTE] > Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in. --- <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:08 -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#66480