From 81384290545eb3f969396c7af8805b85d51a6504 Mon Sep 17 00:00:00 2001 From: giulio-leone Date: Wed, 4 Mar 2026 05:31:34 +0100 Subject: [PATCH 1/4] fix(proxy): preserve master_key set by initialize() during lifespan startup --- litellm/proxy/proxy_server.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 33d84cd70789..37e942bd0a0a 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: + 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") From cc9bd50328d24feb23daf6ff8d5b494cfa2f1b7f Mon Sep 17 00:00:00 2001 From: giulio-leone Date: Thu, 5 Mar 2026 18:00:21 +0100 Subject: [PATCH 2/4] test(proxy): add regression test for master_key preservation (#22330) Verifies that a master_key set by initialize() from config is not overwritten when LITELLM_MASTER_KEY env var is absent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../proxy/test_preserve_master_key.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/test_litellm/proxy/test_preserve_master_key.py 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..f64304e75013 --- /dev/null +++ b/tests/test_litellm/proxy/test_preserve_master_key.py @@ -0,0 +1,47 @@ +""" +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" + ): + # Run only the master_key portion of startup by calling and + # immediately stopping (the rest of startup needs DB, etc.) + try: + await proxy_server.proxy_startup_event(app=AsyncMock()) + 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 From e0ef8e82eb8a2c4ba512d04ba32d6d4d30048763 Mon Sep 17 00:00:00 2001 From: giulio-leone <6887247+giulio-leone@users.noreply.github.com> Date: Thu, 5 Mar 2026 23:49:40 +0100 Subject: [PATCH 3/4] fix: correct asynccontextmanager usage in test + is-not-None guard - Test used 'await' on @asynccontextmanager which never enters the body; changed to 'async with' so the startup code actually executes. - Changed truthiness check to 'is not None' for master_key guard to correctly handle empty-string edge case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- litellm/proxy/proxy_server.py | 2 +- tests/test_litellm/proxy/test_preserve_master_key.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 37e942bd0a0a..34a8e5a89fd6 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -777,7 +777,7 @@ async def proxy_startup_event(app: FastAPI): # noqa: PLR0915 # 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: + 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 diff --git a/tests/test_litellm/proxy/test_preserve_master_key.py b/tests/test_litellm/proxy/test_preserve_master_key.py index f64304e75013..acb17c38f0d7 100644 --- a/tests/test_litellm/proxy/test_preserve_master_key.py +++ b/tests/test_litellm/proxy/test_preserve_master_key.py @@ -30,10 +30,12 @@ async def test_master_key_preserved_when_env_var_absent(): ), patch( "litellm.proxy.proxy_server._license_check" ): - # Run only the master_key portion of startup by calling and - # immediately stopping (the rest of startup needs DB, etc.) + # 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: - await proxy_server.proxy_startup_event(app=AsyncMock()) + 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. From cd6241cf278e2af0edfae0c4003c8ce77affb5a4 Mon Sep 17 00:00:00 2001 From: giulio-leone <6887247+giulio-leone@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:26:05 +0100 Subject: [PATCH 4/4] chore: trigger re-review Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>