diff --git a/litellm/proxy/auth/master_key_policy.py b/litellm/proxy/auth/master_key_policy.py index 318b722b0ff..eeb7d3ee72a 100644 --- a/litellm/proxy/auth/master_key_policy.py +++ b/litellm/proxy/auth/master_key_policy.py @@ -3,13 +3,20 @@ from typing import Final INSECURE_MASTER_KEYS: Final = frozenset({"sk-1234"}) -def insecure_master_key_warning(master_key: str | None) -> str | None: - if master_key not in INSECURE_MASTER_KEYS: - return None - return ( - "LITELLM_MASTER_KEY is set to the example key 'sk-1234' from the docs. " - "Anyone who has read the docs can administer this gateway, and publicly reachable " - "gateways using this key have been compromised. Set a strong random master key " - "(e.g. `python -c \"import secrets; print('sk-' + secrets.token_urlsafe(32))\"`). " - "A future release will refuse to start with this key." - ) +def insecure_master_key_warning(master_key: str | None, alternative_auth_enabled: bool) -> str | None: + if master_key in INSECURE_MASTER_KEYS: + return ( + "LITELLM_MASTER_KEY is set to the example key 'sk-1234' from the docs. " + "Anyone who has read the docs can administer this gateway, and publicly reachable " + "gateways using this key have been compromised. Set a strong random master key " + "(e.g. `python -c \"import secrets; print('sk-' + secrets.token_urlsafe(32))\"`). " + "A future release will refuse to start with this key." + ) + if (master_key is None or master_key == "") and not alternative_auth_enabled: + return ( + "No master key is set (LITELLM_MASTER_KEY or general_settings.master_key). " + "Every request to this proxy is accepted without authentication, including " + 'admin routes. Set a strong random master key (e.g. `python -c "import secrets; ' + "print('sk-' + secrets.token_urlsafe(32))\"`) before exposing it to a network." + ) + return None diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 5aa75e360f2..805c61d4697 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1160,7 +1160,12 @@ async def proxy_startup_event(app: FastAPI) -> AsyncGenerator[None, None]: if isinstance(worker_config, dict): await initialize(**worker_config) - _insecure_master_key_warning: Final = insecure_master_key_warning(master_key) + _alternative_auth_enabled: Final = any( + general_settings.get(k, False) for k in ("enable_jwt_auth", "enable_oauth2_auth", "enable_oauth2_proxy_auth") + ) + _insecure_master_key_warning: Final = insecure_master_key_warning( + master_key, alternative_auth_enabled=_alternative_auth_enabled + ) if _insecure_master_key_warning is not None: verbose_proxy_logger.warning(_insecure_master_key_warning) diff --git a/tests/test_litellm/proxy/auth/test_master_key_policy.py b/tests/test_litellm/proxy/auth/test_master_key_policy.py index 6c949fc3783..6b118994cba 100644 --- a/tests/test_litellm/proxy/auth/test_master_key_policy.py +++ b/tests/test_litellm/proxy/auth/test_master_key_policy.py @@ -3,22 +3,36 @@ from litellm.proxy.auth.master_key_policy import insecure_master_key_warning def test_insecure_master_key_warning_returned_for_example_key(): - warning = insecure_master_key_warning("sk-1234") + warning = insecure_master_key_warning("sk-1234", alternative_auth_enabled=False) assert warning is not None assert "sk-1234" in warning +def test_insecure_master_key_warning_for_missing_key(): + warning = insecure_master_key_warning(None, alternative_auth_enabled=False) + + assert warning is not None + assert "No master key" in warning + + +def test_insecure_master_key_warning_for_empty_key(): + warning = insecure_master_key_warning("", alternative_auth_enabled=False) + + assert warning is not None + assert "No master key" in warning + + +def test_insecure_master_key_warning_none_for_missing_key_with_alt_auth(): + assert insecure_master_key_warning(None, alternative_auth_enabled=True) is None + + def test_insecure_master_key_warning_none_for_strong_key(): - assert insecure_master_key_warning("sk-strong-random-key") is None - - -def test_insecure_master_key_warning_none_for_none(): - assert insecure_master_key_warning(None) is None + assert insecure_master_key_warning("sk-strong-random-key", alternative_auth_enabled=False) is None def test_insecure_master_key_warning_survives_redaction(): - warning = insecure_master_key_warning("sk-1234") + warning = insecure_master_key_warning("sk-1234", alternative_auth_enabled=False) assert warning is not None assert "secrets.token_urlsafe" in redact_string(warning) diff --git a/tests/test_litellm/proxy/proxy_server/test_lifecycle.py b/tests/test_litellm/proxy/proxy_server/test_lifecycle.py index 775e9443653..d472e3935de 100644 --- a/tests/test_litellm/proxy/proxy_server/test_lifecycle.py +++ b/tests/test_litellm/proxy/proxy_server/test_lifecycle.py @@ -1090,3 +1090,20 @@ async def test_proxy_startup_event_warns_but_does_not_raise_for_docs_example_mas pass assert "sk-1234" in caplog.text, "startup should log the insecure master key warning" + + +@pytest.mark.asyncio +async def test_proxy_startup_event_warns_but_does_not_raise_for_missing_master_key(monkeypatch, caplog): + monkeypatch.delenv("LITELLM_MASTER_KEY", raising=False) + + try: + with caplog.at_level(logging.WARNING, logger="LiteLLM Proxy"): + async with proxy_startup_event(app=None): + pass + except ValueError as e: + if "master key" in str(e).lower(): + pytest.fail("proxy_startup_event refused to boot without a master key") + except Exception: + pass + + assert "No master key" in caplog.text, "startup should log the missing master key warning"