From a8026154ab43624bdad8f9789a8e4af01c24904f Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 19 Feb 2026 18:32:48 -0800 Subject: [PATCH 1/3] [Fix] /get_image returns stale cached logo instead of custom UI_LOGO_PATH The /get_image endpoint checked for cached_logo.jpg before reading the UI_LOGO_PATH env var, so a pre-existing cache (e.g. baked into the base Docker image) would always be served, ignoring the user's custom logo. Move the UI_LOGO_PATH read before the cache check and serve local file paths directly, bypassing the cache. The cache optimization is preserved for HTTP URLs and the default logo where it is actually needed. Co-Authored-By: Claude Opus 4.6 (1M context) --- litellm/proxy/proxy_server.py | 12 ++-- tests/test_litellm/proxy/test_proxy_server.py | 70 +++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 0e702abfc6c..45d1af06525 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -10694,13 +10694,17 @@ async def get_image(): cache_dir = assets_dir if os.access(assets_dir, os.W_OK) else current_dir cache_path = os.path.join(cache_dir, "cached_logo.jpg") - # [OPTIMIZATION] Check if the cached image exists first - if os.path.exists(cache_path): - return FileResponse(cache_path, media_type="image/jpeg") - logo_path = os.getenv("UI_LOGO_PATH", default_logo) verbose_proxy_logger.debug("Reading logo from path: %s", logo_path) + # 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://")): + return FileResponse(logo_path, media_type="image/jpeg") + + # [OPTIMIZATION] For HTTP URLs and default logo, check if the cached image exists + if os.path.exists(cache_path): + return FileResponse(cache_path, media_type="image/jpeg") + # Check if the logo path is an HTTP/HTTPS URL if logo_path.startswith(("http://", "https://")): try: diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 79b5e34022f..532e11e70f1 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -3154,6 +3154,76 @@ async def test_get_image_root_case_uses_current_dir(monkeypatch): assert mock_file_response.called, "FileResponse should be called" +@pytest.mark.asyncio +async def test_get_image_custom_local_logo_bypasses_cache(monkeypatch): + """ + Test that when UI_LOGO_PATH is set to a local file, get_image serves it + directly and does not return a stale cached_logo.jpg. + + Regression test: previously the cache check ran before reading UI_LOGO_PATH, + so a pre-existing cached_logo.jpg (e.g. from the base Docker image) would + always be returned, ignoring the user's custom logo. + """ + from unittest.mock import patch + + from litellm.proxy.proxy_server import get_image + + monkeypatch.setenv("UI_LOGO_PATH", "/app/custom_logo.jpg") + monkeypatch.delenv("LITELLM_NON_ROOT", raising=False) + monkeypatch.delenv("LITELLM_ASSETS_PATH", raising=False) + + calls_to_file_response = [] + + def fake_file_response(path, **kwargs): + calls_to_file_response.append(path) + return MagicMock() + + with patch("litellm.proxy.proxy_server.os.path.exists", return_value=True), \ + patch("litellm.proxy.proxy_server.os.access", return_value=True), \ + patch("litellm.proxy.proxy_server.FileResponse", side_effect=fake_file_response): + + await get_image() + + assert len(calls_to_file_response) == 1, "FileResponse should be called exactly once" + assert calls_to_file_response[0] == "/app/custom_logo.jpg", ( + f"Expected custom logo path, got {calls_to_file_response[0]}. " + "A stale cached_logo.jpg may have been returned instead." + ) + + +@pytest.mark.asyncio +async def test_get_image_default_logo_still_uses_cache(monkeypatch): + """ + Test that when UI_LOGO_PATH is NOT set (default logo), the cache + optimization still works — cached_logo.jpg is returned if it exists. + """ + from unittest.mock import patch + + from litellm.proxy.proxy_server import get_image + + monkeypatch.delenv("UI_LOGO_PATH", raising=False) + monkeypatch.delenv("LITELLM_NON_ROOT", raising=False) + monkeypatch.delenv("LITELLM_ASSETS_PATH", raising=False) + + calls_to_file_response = [] + + def fake_file_response(path, **kwargs): + calls_to_file_response.append(path) + return MagicMock() + + with patch("litellm.proxy.proxy_server.os.path.exists", return_value=True), \ + patch("litellm.proxy.proxy_server.os.access", return_value=True), \ + patch("litellm.proxy.proxy_server.FileResponse", side_effect=fake_file_response): + + await get_image() + + assert len(calls_to_file_response) == 1, "FileResponse should be called exactly once" + served_path = calls_to_file_response[0] + assert served_path.endswith("cached_logo.jpg"), ( + f"Expected cached_logo.jpg for default logo, got {served_path}" + ) + + def test_get_config_normalizes_string_callbacks(monkeypatch): """ Test that /get/config/callbacks normalizes string callbacks to lists. From 145efe2267b6e7bd39d2014e1e5fd67294476c45 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 19 Feb 2026 20:09:42 -0800 Subject: [PATCH 2/3] address greptile review feedback (greploop iteration 1) Add os.path.exists check before serving custom local logo so that a non-existent UI_LOGO_PATH gracefully falls through to the cache/default instead of causing a FileResponse error. Co-Authored-By: Claude Opus 4.6 (1M context) --- litellm/proxy/proxy_server.py | 4 +- tests/test_litellm/proxy/test_proxy_server.py | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 45d1af06525..c022261bcea 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -10699,7 +10699,9 @@ async def get_image(): # 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://")): - return FileResponse(logo_path, media_type="image/jpeg") + if os.path.exists(logo_path): + return FileResponse(logo_path, media_type="image/jpeg") + # Fall through to cache or default if custom path doesn't exist # [OPTIMIZATION] For HTTP URLs and default logo, check if the cached image exists if os.path.exists(cache_path): diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 532e11e70f1..e68dfedab54 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -3224,6 +3224,48 @@ async def test_get_image_default_logo_still_uses_cache(monkeypatch): ) +@pytest.mark.asyncio +async def test_get_image_custom_logo_missing_falls_through_to_default(monkeypatch): + """ + Test that when UI_LOGO_PATH points to a non-existent local file, + get_image falls through to the cache/default logo instead of failing. + """ + from unittest.mock import patch + + from litellm.proxy.proxy_server import get_image + + monkeypatch.setenv("UI_LOGO_PATH", "/app/nonexistent_logo.jpg") + monkeypatch.delenv("LITELLM_NON_ROOT", raising=False) + monkeypatch.delenv("LITELLM_ASSETS_PATH", raising=False) + + calls_to_file_response = [] + + def fake_file_response(path, **kwargs): + calls_to_file_response.append(path) + return MagicMock() + + def exists_side_effect(path): + # The custom logo does NOT exist; cache and default DO exist + if path == "/app/nonexistent_logo.jpg": + return False + return True + + with patch("litellm.proxy.proxy_server.os.path.exists", side_effect=exists_side_effect), \ + patch("litellm.proxy.proxy_server.os.access", return_value=True), \ + patch("litellm.proxy.proxy_server.FileResponse", side_effect=fake_file_response): + + await get_image() + + assert len(calls_to_file_response) == 1, "FileResponse should be called exactly once" + served_path = calls_to_file_response[0] + assert served_path != "/app/nonexistent_logo.jpg", ( + "Should not attempt to serve a non-existent custom logo" + ) + assert served_path.endswith("cached_logo.jpg"), ( + f"Expected fallback to cached_logo.jpg, got {served_path}" + ) + + def test_get_config_normalizes_string_callbacks(monkeypatch): """ Test that /get/config/callbacks normalizes string callbacks to lists. From 6bfab8acd456e1c6d702f567dc0caaf18ef597f3 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 19 Feb 2026 20:25:00 -0800 Subject: [PATCH 3/3] address greptile review feedback (greploop iteration 2) Reset logo_path to default_logo when custom UI_LOGO_PATH file doesn't exist, so the else branch at the bottom of get_image serves the default logo instead of the non-existent custom path. Co-Authored-By: Claude Opus 4.6 (1M context) --- litellm/proxy/proxy_server.py | 6 ++- tests/test_litellm/proxy/test_proxy_server.py | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index c022261bcea..1fa0107469b 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -10701,7 +10701,11 @@ async def get_image(): 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") - # Fall through to cache or default if custom path doesn't exist + # 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" + ) + logo_path = default_logo # [OPTIMIZATION] For HTTP URLs and default logo, check if the cached image exists if os.path.exists(cache_path): diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index e68dfedab54..ab414db3569 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -3266,6 +3266,51 @@ async def test_get_image_custom_logo_missing_falls_through_to_default(monkeypatc ) +@pytest.mark.asyncio +async def test_get_image_custom_logo_missing_no_cache_serves_default(monkeypatch): + """ + Test that when UI_LOGO_PATH points to a non-existent file AND there is no + cached_logo.jpg, get_image serves the default logo instead of the + non-existent custom path. + """ + from unittest.mock import patch + + from litellm.proxy.proxy_server import get_image + + monkeypatch.setenv("UI_LOGO_PATH", "/app/nonexistent_logo.jpg") + monkeypatch.delenv("LITELLM_NON_ROOT", raising=False) + monkeypatch.delenv("LITELLM_ASSETS_PATH", raising=False) + + calls_to_file_response = [] + + def fake_file_response(path, **kwargs): + calls_to_file_response.append(path) + return MagicMock() + + def exists_side_effect(path): + # Neither the custom logo nor the cache exist + if path == "/app/nonexistent_logo.jpg": + return False + if "cached_logo.jpg" in path: + return False + return True + + with patch("litellm.proxy.proxy_server.os.path.exists", side_effect=exists_side_effect), \ + patch("litellm.proxy.proxy_server.os.access", return_value=True), \ + patch("litellm.proxy.proxy_server.FileResponse", side_effect=fake_file_response): + + await get_image() + + assert len(calls_to_file_response) == 1, "FileResponse should be called exactly once" + served_path = calls_to_file_response[0] + assert served_path != "/app/nonexistent_logo.jpg", ( + "Should not attempt to serve a non-existent custom logo" + ) + assert served_path.endswith("logo.jpg"), ( + f"Expected fallback to default logo.jpg, got {served_path}" + ) + + def test_get_config_normalizes_string_callbacks(monkeypatch): """ Test that /get/config/callbacks normalizes string callbacks to lists.