From b11e59f3a36ef6f03f153de31304afdb80f1b407 Mon Sep 17 00:00:00 2001 From: apshada <49001649+apshada@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:11:02 +0530 Subject: [PATCH] feat(ui): gate "Default Credentials" hint on /ui/login behind env flag (#30234) Adds LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT (and an equivalent general_settings.hide_default_credentials_hint) that suppresses the "By default, Username is admin and Password is your set LiteLLM Proxy MASTER_KEY" info card rendered on /ui/login and /fallback/login. Motivation: in production deployments operators set UI_USERNAME / UI_PASSWORD (or SSO), and the hardcoded hint becomes factually incorrect and is flagged by security scanners (Tenable WAS plugin 114625) as information disclosure. There is currently no way to suppress it without forking the dashboard. Behaviour: - Default is unchanged (hint shown), so existing deployments are unaffected. - New field hide_default_credentials_hint on the well-known UI config endpoint, populated from the env var or general_settings. - LoginPage.tsx conditionally renders the Alert based on the flag. Refs: BerriAI/litellm#30232 --- .../ui_discovery_endpoints.py | 5 ++ .../ui_discovery_endpoints.py | 1 + .../test_ui_discovery_endpoints.py | 73 +++++++++++++++++++ .../src/app/login/LoginPage.tsx | 46 ++++++------ .../src/components/networking.tsx | 1 + 5 files changed, 104 insertions(+), 22 deletions(-) diff --git a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py index 233df5c6c57..41d0bbc618f 100644 --- a/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py +++ b/litellm/proxy/discovery_endpoints/ui_discovery_endpoints.py @@ -25,6 +25,10 @@ async def get_ui_config(): or general_settings.get("auto_redirect_ui_login_to_sso", False) is True ) admin_ui_disabled = os.getenv("DISABLE_ADMIN_UI", "false").lower() == "true" + hide_default_credentials_hint = ( + os.getenv("LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT", "false").lower() == "true" + or general_settings.get("hide_default_credentials_hint", False) is True + ) sso_configured = _has_user_setup_sso() @@ -38,6 +42,7 @@ async def get_ui_config(): auto_redirect_to_sso=sso_configured and auto_redirect_ui_login_to_sso, admin_ui_disabled=admin_ui_disabled, sso_configured=sso_configured, + hide_default_credentials_hint=hide_default_credentials_hint, is_control_plane=is_control_plane, workers=proxy_config.worker_registry if is_control_plane else [], ) diff --git a/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py b/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py index 46cd3f49f1a..5498474ea9f 100644 --- a/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py +++ b/litellm/types/proxy/discovery_endpoints/ui_discovery_endpoints.py @@ -11,5 +11,6 @@ class UiDiscoveryEndpoints(BaseModel): auto_redirect_to_sso: bool admin_ui_disabled: bool sso_configured: bool + hide_default_credentials_hint: bool = False is_control_plane: bool = False workers: List[WorkerRegistryEntry] = [] 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 9199286e6fc..b3c3957548b 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 @@ -352,6 +352,79 @@ def test_ui_discovery_endpoints_is_control_plane_true_when_workers_configured(): assert data["workers"][0]["url"] == "https://worker-1:4001" +def test_ui_discovery_endpoints_hide_default_credentials_hint_default_false(): + """Default credentials hint is shown by default (flag false).""" + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with ( + patch("litellm.proxy.utils.get_server_root_path", return_value="/"), + patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), + patch.dict(os.environ, {"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 + data = response.json() + assert data["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() + app.include_router(router) + client = TestClient(app) + + with ( + patch("litellm.proxy.utils.get_server_root_path", return_value="/"), + patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), + patch.dict( + os.environ, + { + "LITELLM_HIDE_DEFAULT_CREDENTIALS_HINT": "true", + "DISABLE_ADMIN_UI": "false", + }, + clear=False, + ), + ): + + response = client.get("/.well-known/litellm-ui-config") + + assert response.status_code == 200 + data = response.json() + assert data["hide_default_credentials_hint"] is True + + +def test_ui_discovery_endpoints_hide_default_credentials_hint_via_general_settings(): + """general_settings.hide_default_credentials_hint=true also hides the card.""" + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + with ( + patch("litellm.proxy.utils.get_server_root_path", return_value="/"), + patch("litellm.proxy.utils.get_proxy_base_url", return_value=None), + patch("litellm.proxy.auth.auth_utils._has_user_setup_sso", return_value=False), + patch( + "litellm.proxy.proxy_server.general_settings", + {"hide_default_credentials_hint": True}, + ), + patch.dict(os.environ, {"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 + data = response.json() + assert data["hide_default_credentials_hint"] is True + + def test_ui_discovery_endpoints_is_control_plane_false_when_no_workers(): app = FastAPI() app.include_router(router) diff --git a/ui/litellm-dashboard/src/app/login/LoginPage.tsx b/ui/litellm-dashboard/src/app/login/LoginPage.tsx index db3a069902a..6cf86dc0c5a 100644 --- a/ui/litellm-dashboard/src/app/login/LoginPage.tsx +++ b/ui/litellm-dashboard/src/app/login/LoginPage.tsx @@ -188,28 +188,30 @@ function LoginPageContent() { Access your LiteLLM Admin UI. - - - By default, Username is admin and - Password is your set LiteLLM Proxy - MASTER_KEY. - - - Need to set UI credentials or SSO?{" "} - - Check the documentation - - . - - - } - type="info" - icon={} - showIcon - /> + {!uiConfig?.hide_default_credentials_hint && ( + + + By default, Username is admin and + Password is your set LiteLLM Proxy + MASTER_KEY. + + + Need to set UI credentials or SSO?{" "} + + Check the documentation + + . + + + } + type="info" + icon={} + showIcon + /> + )} {error && } diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index b41ff073cb7..7f575a913db 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -285,6 +285,7 @@ export interface LiteLLMWellKnownUiConfig { auto_redirect_to_sso: boolean; admin_ui_disabled: boolean; sso_configured: boolean; + hide_default_credentials_hint?: boolean; is_control_plane?: boolean; workers?: WorkerInfo[]; }