diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 33d84cd70789..34a8e5a89fd6 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -772,7 +772,13 @@ async def proxy_startup_event(app: FastAPI): # noqa: PLR0915 premium_user = _license_check.is_premium() ## CHECK MASTER KEY IN ENVIRONMENT ## - master_key = get_secret_str("LITELLM_MASTER_KEY") + # Only overwrite master_key from env var if it is actually set. + # initialize() may have already configured master_key from a config file; + # unconditionally assigning None here would discard that value when the + # LITELLM_MASTER_KEY env var is absent (e.g. programmatic startup, tests). + _env_master_key = get_secret_str("LITELLM_MASTER_KEY") + if _env_master_key is not None: + master_key = _env_master_key ### LOAD CONFIG ### worker_config: Optional[Union[str, dict]] = get_secret("WORKER_CONFIG") # type: ignore env_config_yaml: Optional[str] = get_secret_str("CONFIG_FILE_PATH") diff --git a/tests/test_litellm/proxy/test_preserve_master_key.py b/tests/test_litellm/proxy/test_preserve_master_key.py new file mode 100644 index 000000000000..acb17c38f0d7 --- /dev/null +++ b/tests/test_litellm/proxy/test_preserve_master_key.py @@ -0,0 +1,49 @@ +""" +Regression test for #22330: +A master_key set by initialize() (via config file) must be preserved when +LITELLM_MASTER_KEY is not set as an environment variable. +""" + +from unittest.mock import AsyncMock, patch + +import pytest + +from litellm.proxy import proxy_server + + +@pytest.mark.asyncio +async def test_master_key_preserved_when_env_var_absent(): + """proxy_startup_event must NOT overwrite a config-provided master_key.""" + config_key = "sk-from-config-file-1234" + + # Simulate initialize() having set master_key from a config file + original_master_key = proxy_server.master_key + proxy_server.master_key = config_key + + try: + with patch( + "litellm.proxy.proxy_server.get_secret_str", return_value=None + ), patch( + "litellm.proxy.proxy_server.get_secret", return_value=None + ), patch( + "litellm.proxy.proxy_server.init_verbose_loggers" + ), patch( + "litellm.proxy.proxy_server._license_check" + ): + # proxy_startup_event is an @asynccontextmanager, so we must + # enter it with `async with` — a bare `await` would only create + # the generator object without executing the function body. + try: + async with proxy_server.proxy_startup_event(app=AsyncMock()): + pass + except Exception: + # Startup will fail on DB/router setup — we only care about + # the master_key guard at the top of the function. + pass + + assert proxy_server.master_key == config_key, ( + f"master_key was overwritten: expected {config_key!r}, " + f"got {proxy_server.master_key!r}" + ) + finally: + proxy_server.master_key = original_master_key