Skip to content

fix(proxy): preserve master_key set by initialize() during lifespan startup - #22396

Closed
giulio-leone wants to merge 1 commit into
BerriAI:mainfrom
giulio-leone:fix/issue-22330-mcp-auth-context
Closed

fix(proxy): preserve master_key set by initialize() during lifespan startup#22396
giulio-leone wants to merge 1 commit into
BerriAI:mainfrom
giulio-leone:fix/issue-22330-mcp-auth-context

Conversation

@giulio-leone

Copy link
Copy Markdown
Contributor

Summary

Fixes #22330

Root Cause

proxy_startup_event() unconditionally overwrites master_key with get_secret_str("LITELLM_MASTER_KEY"), discarding a valid master_key already configured by initialize() from the YAML config.

When neither CONFIG_FILE_PATH nor WORKER_CONFIG env vars are set (e.g. programmatic startup or test harnesses), the config is not reloaded during the ASGI lifespan, so master_key stays None.

This causes user_api_key_auth() to return INTERNAL_USER for every request — including the admin key — resulting in 403 errors for MCP tool calls and list_tools.

Fix

Only overwrite master_key from the environment variable when it has a truthy value, preserving any previously configured value from initialize().

# Before (always overwrites, even with None)
master_key = get_secret_str("LITELLM_MASTER_KEY")

# After (preserves config-provided value)
_env_master_key = get_secret_str("LITELLM_MASTER_KEY")
if _env_master_key:
    master_key = _env_master_key

How I found it

The issue description attributes the 403 errors to ContextVar propagation failing when StreamableHTTPSessionManager spawns tasks. I proved this is incorrect — ContextVar propagation works correctly (anyio 4.x on asyncio backend copies context for create_task). The actual root cause is the master_key being wiped during lifespan startup.

Testing

  • The 3 MCP e2e tests that exercise streamable HTTP now pass (they all fail with 403 on main)
  • The 4th test (test_independent_clients_no_shared_session) also fails on main due to a separate stdio connection issue — not affected by this change

@vercel

vercel Bot commented Feb 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Feb 28, 2026 2:42pm

Request Review

@greptile-apps

greptile-apps Bot commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where proxy_startup_event() unconditionally overwrote the global master_key with the result of get_secret_str("LITELLM_MASTER_KEY"), which returns None when the env var is absent. This discarded any master_key previously set by initialize() from a YAML config during programmatic startup (when neither CONFIG_FILE_PATH nor WORKER_CONFIG env vars are set). The consequence was that user_api_key_auth() treated every request as INTERNAL_USER, causing 403 errors for MCP tool calls.

  • Adds a truthiness check before overwriting master_key, so that a config-provided value is preserved when the env var is not set
  • The fix is minimal (6 lines changed) and well-scoped to the root cause
  • MCP e2e tests that previously failed with 403 on main now pass

Confidence Score: 5/5

  • This PR is safe to merge — it is a small, well-reasoned guard that prevents an existing value from being overwritten with None.
  • The change is minimal (one conditional guard around an existing assignment), the root cause analysis is thorough, and the fix correctly preserves backward compatibility: when the env var IS set, behavior is unchanged; when it is NOT set, a previously configured master_key is preserved instead of being wiped.
  • No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/proxy_server.py Adds a guard so master_key is only overwritten from the LITELLM_MASTER_KEY env var when it has a truthy value, preserving any value previously set by initialize(). Correct and minimal fix.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["initialize() called programmatically"] --> B["master_key set from YAML config"]
    B --> C["proxy_startup_event() runs"]
    C --> D{"LITELLM_MASTER_KEY\nenv var set?"}
    D -->|Yes| E["master_key = env var value"]
    D -->|No - Before fix| F["master_key = None ❌\n(config value lost)"]
    D -->|No - After fix| G["master_key preserved ✅\n(config value kept)"]
    E --> H["user_api_key_auth() works correctly"]
    F --> I["All requests treated as INTERNAL_USER\n→ 403 errors"]
    G --> H
Loading

Last reviewed commit: 2f98969

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, no comments

Edit Code Review Agent Settings | Greptile

…tartup

proxy_startup_event() unconditionally overwrote master_key with the value
of the LITELLM_MASTER_KEY env var, discarding a valid master_key already
configured by initialize() from the YAML config.  When neither
CONFIG_FILE_PATH nor WORKER_CONFIG env vars are set (e.g. programmatic
startup or test harnesses), the config is not reloaded during lifespan, so
master_key stayed None.  This caused user_api_key_auth() to return
INTERNAL_USER for every request — including the admin key — resulting in
403 errors for MCP tool calls and list_tools.

Only overwrite master_key from the env var when it has a truthy value,
preserving any previously configured value.

Fixes #22330
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: MCP e2e tests fail — auth ContextVar not propagated into SessionManager task groups

1 participant