diff --git a/litellm/proxy/common_utils/html_forms/default_credentials_hint.py b/litellm/proxy/common_utils/html_forms/default_credentials_hint.py
new file mode 100644
index 00000000000..e3ed948501b
--- /dev/null
+++ b/litellm/proxy/common_utils/html_forms/default_credentials_hint.py
@@ -0,0 +1,10 @@
+import os
+from collections.abc import Mapping
+
+
+def should_hide_default_credentials_hint(general_settings: Mapping[str, object]) -> bool:
+ return (
+ os.getenv("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", "false").lower() == "true"
+ or general_settings.get("hide_default_credentials_hint", False) is True
+ or bool(os.getenv("UI_PASSWORD"))
+ )
diff --git a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py
index c2053693f2e..8b042d18cd0 100644
--- a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py
+++ b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py
@@ -4,6 +4,7 @@ from typing import Final
from fastapi import APIRouter
+from litellm.proxy.common_utils.html_forms.default_credentials_hint import should_hide_default_credentials_hint
from litellm.types.proxy.discovery_endpoints.ui_discovery_endpoints import (
UiDiscoveryEndpoints,
)
@@ -23,10 +24,7 @@ async def get_ui_config():
or general_settings.get("auto_redirect_ui_login_to_sso", False) is True
)
admin_ui_disabled: Final = os.getenv("DISABLE_ADMIN_UI", "false").lower() == "true"
- hide_default_credentials_hint: Final = bool(
- os.getenv("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", "false").lower() == "true"
- or general_settings.get("hide_default_credentials_hint", False) is True
- )
+ hide_default_credentials_hint: Final = should_hide_default_credentials_hint(general_settings)
sso_configured: Final = has_user_setup_sso()
diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py
index 1ba90725eff..091dccf1433 100644
--- a/litellm/proxy/management_endpoints/ui_sso.py
+++ b/litellm/proxy/management_endpoints/ui_sso.py
@@ -101,6 +101,7 @@ from litellm.proxy.common_utils.admin_ui_utils import (
admin_ui_disabled,
show_missing_vars_in_env,
)
+from litellm.proxy.common_utils.html_forms.default_credentials_hint import should_hide_default_credentials_hint
from litellm.proxy.common_utils.html_forms.jwt_display_template import (
jwt_display_template,
)
@@ -1110,10 +1111,7 @@ async def google_login(
from fastapi.responses import HTMLResponse
- hide_default_credentials_hint: Final = (
- os.getenv("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", "false").lower() == "true"
- or general_settings.get("hide_default_credentials_hint", False) is True
- )
+ hide_default_credentials_hint: Final = should_hide_default_credentials_hint(general_settings)
form_response: Final = HTMLResponse(
content=build_ui_login_form(
show_deprecation_banner=True,
diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py
index 25274dec1c6..dd7a39eb94c 100644
--- a/litellm/proxy/proxy_server.py
+++ b/litellm/proxy/proxy_server.py
@@ -365,6 +365,7 @@ from litellm.proxy.common_utils.healthy_model_filter import (
get_hidden_unhealthy_model_names,
is_healthy_only_listing_default,
)
+from litellm.proxy.common_utils.html_forms.default_credentials_hint import should_hide_default_credentials_hint
from litellm.proxy.common_utils.html_forms.ui_login import build_ui_login_form
from litellm.proxy.common_utils.http_parsing_utils import (
_read_request_body,
@@ -15828,10 +15829,7 @@ async def fallback_login(request: Request):
from fastapi.responses import HTMLResponse
- hide_default_credentials_hint: Final = (
- os.getenv("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", "false").lower() == "true"
- or general_settings.get("hide_default_credentials_hint", False) is True
- )
+ hide_default_credentials_hint: Final = should_hide_default_credentials_hint(general_settings)
return HTMLResponse(
content=build_ui_login_form(
show_deprecation_banner=False,
diff --git a/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py b/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py
index c3f7b0100d8..64a2eb69325 100644
--- a/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py
+++ b/tests/test_litellm/proxy/discovery_endpoints/test_ui_discovery_endpoints.py
@@ -340,6 +340,7 @@ def test_ui_discovery_endpoints_hide_default_credentials_hint_default_false():
patch.dict(os.environ, {"DISABLE_ADMIN_UI": "false"}, clear=False),
):
os.environ.pop("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", None)
+ os.environ.pop("UI_PASSWORD", None)
response = client.get("/.well-known/litellm-ui-config")
@@ -348,6 +349,43 @@ def test_ui_discovery_endpoints_hide_default_credentials_hint_default_false():
assert data["hide_default_credentials_hint"] is False
+def test_ui_discovery_endpoints_hide_default_credentials_hint_when_ui_password_set():
+ app = FastAPI()
+ app.include_router(router)
+ client = TestClient(app)
+
+ with patch.dict(os.environ, {"UI_PASSWORD": "s3cret-pass", "DISABLE_ADMIN_UI": "false"}, clear=False):
+ os.environ.pop("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", None)
+
+ response = client.get("/.well-known/litellm-ui-config")
+
+ assert response.status_code == 200
+ assert response.json()["hide_default_credentials_hint"] is True
+
+
+@pytest.mark.parametrize(
+ "env_overrides",
+ [
+ pytest.param({"UI_USERNAME": "opsadmin"}, id="username_only_keeps_master_key_password"),
+ pytest.param({"UI_PASSWORD": ""}, id="empty_password_is_not_set"),
+ ],
+)
+def test_ui_discovery_endpoints_keeps_default_credentials_hint_without_real_ui_password(env_overrides):
+ app = FastAPI()
+ app.include_router(router)
+ client = TestClient(app)
+
+ with patch.dict(os.environ, {"DISABLE_ADMIN_UI": "false", **env_overrides}, clear=False):
+ os.environ.pop("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", None)
+ if "UI_PASSWORD" not in env_overrides:
+ os.environ.pop("UI_PASSWORD", None)
+
+ response = client.get("/.well-known/litellm-ui-config")
+
+ assert response.status_code == 200
+ assert response.json()["hide_default_credentials_hint"] is False
+
+
def test_ui_discovery_endpoints_hide_default_credentials_hint_via_env_var():
"""LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT=true hides the login-page credentials card."""
app = FastAPI()
diff --git a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py
index 2050e65d2a1..1230c548281 100644
--- a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py
+++ b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py
@@ -8334,6 +8334,7 @@ async def _render_legacy_login_page(env_overrides, general_settings):
"GOOGLE_CLIENT_ID",
"GENERIC_CLIENT_ID",
"LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT",
+ "UI_PASSWORD",
):
os.environ.pop(var, None)
os.environ.update(env_overrides)
@@ -8386,6 +8387,20 @@ async def test_legacy_login_page_hides_credentials_hint_via_general_settings():
assert "MASTER_KEY" not in body
+@pytest.mark.asyncio
+async def test_legacy_login_page_hides_credentials_hint_when_ui_password_set():
+ response = await _render_legacy_login_page(
+ env_overrides={"UI_PASSWORD": "s3cret-pass"},
+ general_settings={},
+ )
+
+ body = response.body.decode()
+ assert response.status_code == 200
+ assert "Default Credentials" not in body
+ assert "MASTER_KEY" not in body
+ assert 'name="username"' in body
+
+
@pytest.mark.asyncio
async def test_saml_callback_blocked_when_admin_ui_disabled():
"""An IdP-initiated assertion must not mint a UI session when the admin UI is
diff --git a/tests/test_litellm/proxy/proxy_server/test_routes_login_sso.py b/tests/test_litellm/proxy/proxy_server/test_routes_login_sso.py
index 45460dcecf1..79c23b11f3e 100644
--- a/tests/test_litellm/proxy/proxy_server/test_routes_login_sso.py
+++ b/tests/test_litellm/proxy/proxy_server/test_routes_login_sso.py
@@ -97,6 +97,7 @@ def test_fallback_login_returns_html_form_with_ui_username_set(client, monkeypat
def test_fallback_login_shows_credentials_hint_by_default(client, monkeypatch):
"""Control: without the flag, /fallback/login still renders the hint."""
monkeypatch.delenv("UI_USERNAME", raising=False)
+ monkeypatch.delenv("UI_PASSWORD", raising=False)
monkeypatch.delenv("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", raising=False)
response = client.get("/fallback/login")
assert response.status_code == 200
@@ -104,6 +105,16 @@ def test_fallback_login_shows_credentials_hint_by_default(client, monkeypatch):
assert "MASTER_KEY" in response.text
+def test_fallback_login_hides_credentials_hint_when_ui_password_set(client, monkeypatch):
+ monkeypatch.setenv("UI_PASSWORD", "s3cret-pass")
+ monkeypatch.delenv("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", raising=False)
+ response = client.get("/fallback/login")
+ assert response.status_code == 200
+ assert "Default Credentials" not in response.text
+ assert "MASTER_KEY" not in response.text
+ assert 'name="username"' in response.text
+
+
def test_fallback_login_hides_credentials_hint_via_env_flag(client, monkeypatch):
"""Pin: LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT removes the hint on /fallback/login."""
monkeypatch.delenv("UI_USERNAME", raising=False)