From 5037026d754b67a3ddb46b964f979c6fc5e2245d Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 6 May 2026 15:36:58 -0700 Subject: [PATCH] fix(agent_session_endpoints): require LITELLM_AGENT_JWT_SECRET, no master-key fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The daemon JWT secret must be a SEPARATE credential from the proxy master key. The previous fallback to LITELLM_MASTER_KEY conflated two distinct auth surfaces — a captured daemon JWT could be used to mint regular API keys with master-key authority. Replace _get_signing_secret with a strict check that raises AgentJWTSecretNotConfiguredError if the dedicated env var is unset. Add is_agent_jwt_secret_configured() so proxy_server.py can refuse to mount the routers when the secret is missing. Greptile P1 SECURITY (review #PRR_kwDOKALCgc78uM7F). --- litellm/proxy/agent_session_endpoints/auth.py | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/agent_session_endpoints/auth.py b/litellm/proxy/agent_session_endpoints/auth.py index 433844caa86..36a393f1cde 100644 --- a/litellm/proxy/agent_session_endpoints/auth.py +++ b/litellm/proxy/agent_session_endpoints/auth.py @@ -26,20 +26,41 @@ class AgentDaemonTokenError(Exception): """Raised when a daemon token is invalid (used internally by validators).""" +class AgentJWTSecretNotConfiguredError(RuntimeError): + """Raised when ``LITELLM_AGENT_JWT_SECRET`` is unset. + + The daemon JWT secret MUST be a separate credential from the proxy + master key. Falling back to ``LITELLM_MASTER_KEY`` would conflate two + distinct auth surfaces — a captured daemon JWT could then be used to + mint regular API keys with master-key authority. + """ + + +def is_agent_jwt_secret_configured() -> bool: + """Return True iff a non-empty ``LITELLM_AGENT_JWT_SECRET`` is present. + + Used at startup by ``proxy_server.py`` to decide whether to mount the + agent_session_endpoints routers. Mounting the routers when this is + False would silently expose an unsigned auth surface — refuse instead. + """ + return bool(os.environ.get(AGENT_JWT_SECRET_ENV)) + + def _get_signing_secret() -> str: """Resolve the JWT signing secret. - Falls back to ``LITELLM_MASTER_KEY`` for local dev so a fresh proxy - boot doesn't blow up; production deploys MUST set - ``LITELLM_AGENT_JWT_SECRET``. + The daemon JWT secret is a SEPARATE credential from the proxy master + key. There is no fallback — if ``LITELLM_AGENT_JWT_SECRET`` is unset, + we refuse to mint or validate any token. Mounting the agent_session + routers without this env var is itself a startup error (see + ``proxy_server.py``). """ secret = os.environ.get(AGENT_JWT_SECRET_ENV) if not secret: - secret = os.environ.get("LITELLM_MASTER_KEY") - if not secret: - raise RuntimeError( - f"Neither {AGENT_JWT_SECRET_ENV} nor LITELLM_MASTER_KEY is set; " - "cannot mint or validate daemon JWTs." + raise AgentJWTSecretNotConfiguredError( + f"{AGENT_JWT_SECRET_ENV} is not set; cannot mint or validate " + "daemon JWTs. This env var must be a dedicated random secret, " + "distinct from LITELLM_MASTER_KEY." ) return secret