mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
[Fix] UI logo not showing when UI_LOGO_PATH is a local file path
- Fix /get_image to use mimetypes.guess_type() for correct Content-Type instead of hardcoding image/jpeg for all file types (breaks PNG logos in strict enterprise proxy environments) - Fix get_ui_theme_settings to null out logo_url when it is a local filesystem path: the browser cannot load local paths as image URLs, so the UI must fall back to /get_image which serves local files via the UI_LOGO_PATH env var - Add test covering the local-path nulling behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
335c4d4946
commit
1f6f16d035
3 changed files with 37 additions and 3 deletions
|
|
@ -3,6 +3,7 @@ import copy
|
|||
import enum
|
||||
import inspect
|
||||
import io
|
||||
import mimetypes
|
||||
import os
|
||||
import random
|
||||
import secrets
|
||||
|
|
@ -11061,10 +11062,16 @@ async def get_image():
|
|||
logo_path = os.getenv("UI_LOGO_PATH", default_logo)
|
||||
verbose_proxy_logger.debug("Reading logo from path: %s", logo_path)
|
||||
|
||||
def _get_image_media_type(path: str) -> str:
|
||||
media_type, _ = mimetypes.guess_type(path)
|
||||
if media_type and media_type.startswith("image/"):
|
||||
return media_type
|
||||
return "image/jpeg"
|
||||
|
||||
# If UI_LOGO_PATH points to a local file, serve it directly (skip cache)
|
||||
if logo_path != default_logo and not logo_path.startswith(("http://", "https://")):
|
||||
if os.path.exists(logo_path):
|
||||
return FileResponse(logo_path, media_type="image/jpeg")
|
||||
return FileResponse(logo_path, media_type=_get_image_media_type(logo_path))
|
||||
# Custom path doesn't exist — fall back to default
|
||||
verbose_proxy_logger.warning(
|
||||
f"UI_LOGO_PATH '{logo_path}' does not exist, falling back to default logo"
|
||||
|
|
@ -11103,7 +11110,7 @@ async def get_image():
|
|||
return FileResponse(default_logo, media_type="image/jpeg")
|
||||
else:
|
||||
# Return the local image file if the logo path is not an HTTP/HTTPS URL
|
||||
return FileResponse(logo_path, media_type="image/jpeg")
|
||||
return FileResponse(logo_path, media_type=_get_image_media_type(logo_path))
|
||||
|
||||
|
||||
@app.get("/get_favicon", include_in_schema=False)
|
||||
|
|
|
|||
|
|
@ -734,17 +734,28 @@ async def get_ui_theme_settings():
|
|||
Note: This endpoint is public (no authentication required) so all users can see custom branding.
|
||||
Only the /update/ui_theme_settings endpoint requires authentication for admins to change settings.
|
||||
"""
|
||||
import os
|
||||
|
||||
from litellm.proxy.proxy_server import proxy_config
|
||||
|
||||
# Load existing config
|
||||
config = await proxy_config.get_config()
|
||||
|
||||
return await _get_settings_with_schema(
|
||||
result = await _get_settings_with_schema(
|
||||
settings_key="ui_theme_config",
|
||||
settings_class=UIThemeConfig,
|
||||
config=config,
|
||||
)
|
||||
|
||||
# If logo_url is a local filesystem path (not an HTTP/HTTPS URL), the browser
|
||||
# cannot load it directly as an image src. Null it out so the UI falls back to
|
||||
# calling the /get_image endpoint, which reads UI_LOGO_PATH and serves the file.
|
||||
logo_url = result["values"].get("logo_url")
|
||||
if logo_url and not logo_url.startswith(("http://", "https://")):
|
||||
result["values"]["logo_url"] = None
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/update/ui_theme_settings",
|
||||
|
|
|
|||
|
|
@ -776,6 +776,22 @@ class TestProxySettingEndpoints:
|
|||
== "https://example.com/favicon.ico"
|
||||
)
|
||||
|
||||
def test_get_ui_theme_settings_local_path_is_nulled(self, mock_proxy_config):
|
||||
"""
|
||||
Local file paths stored in logo_url cannot be loaded by the browser as an image URL.
|
||||
The endpoint should null them out so the UI falls back to /get_image, which reads
|
||||
UI_LOGO_PATH from the environment and serves the file directly.
|
||||
"""
|
||||
mock_proxy_config["config"]["litellm_settings"]["ui_theme_config"] = {
|
||||
"logo_url": "/app/company_logo.png",
|
||||
}
|
||||
|
||||
response = client.get("/get/ui_theme_settings")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["values"]["logo_url"] is None
|
||||
|
||||
def test_get_ui_settings(self, mock_auth, monkeypatch):
|
||||
"""Test retrieving UI settings with allowlist sanitization"""
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue