perf: redirect default model profile image to canonical static URL (#24015)

- Return 302 to /static/favicon.png instead of streaming the same PNG per
  model id so browsers can cache one asset for default avatars.
- Validate stored /static/ paths with decode, normpath, and /static
  prefix checks; invalid paths fall back to favicon.

Made-with: Cursor
This commit is contained in:
Jacob Leksan
2026-04-24 02:45:10 -04:00
committed by GitHub
parent a76a779c01
commit f2cb63140c
+49 -4
View File
@@ -4,6 +4,8 @@ import base64
import json
import asyncio
import logging
import posixpath
from urllib.parse import unquote
from open_webui.models.groups import Groups
from open_webui.models.models import (
@@ -29,12 +31,12 @@ from fastapi import (
status,
Response,
)
from fastapi.responses import FileResponse, StreamingResponse
from fastapi.responses import RedirectResponse, StreamingResponse
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL, STATIC_DIR
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
@@ -43,6 +45,34 @@ log = logging.getLogger(__name__)
router = APIRouter()
def _safe_static_redirect_path(url: str) -> Optional[str]:
"""
If url is a same-origin static asset path, return a normalized path safe for
RedirectResponse Location. Otherwise None (caller should fall back to default).
Rejects traversal (..), encoded dots, query/fragment, and non-/static targets.
"""
if not url or not isinstance(url, str):
return None
path = url.split('?', 1)[0].split('#', 1)[0].strip()
for _ in range(2):
decoded = unquote(path)
if decoded == path:
break
path = decoded
if '\x00' in path or '\\' in path:
return None
if not path.startswith('/'):
return None
normalized = posixpath.normpath(path)
if normalized in ('.', '/'):
return None
if not (normalized == '/static' or normalized.startswith('/static/')):
return None
if normalized == '/static':
return '/static/'
return normalized
def is_valid_model_id(model_id: str) -> bool:
return model_id and len(model_id) <= 256
@@ -465,10 +495,25 @@ async def get_model_profile_image(
)
except Exception as e:
pass
else:
safe_static = _safe_static_redirect_path(model.meta.profile_image_url)
if safe_static:
return RedirectResponse(
url=safe_static,
status_code=status.HTTP_302_FOUND,
)
return FileResponse(f'{STATIC_DIR}/favicon.png')
# Canonical URL so browsers cache one asset for all default model avatars
# (distinct /profile/image?id=... URLs would otherwise re-download the same bytes).
return RedirectResponse(
url='/static/favicon.png',
status_code=status.HTTP_302_FOUND,
)
else:
return FileResponse(f'{STATIC_DIR}/favicon.png')
return RedirectResponse(
url='/static/favicon.png',
status_code=status.HTTP_302_FOUND,
)
############################