From ac5adafaed51940692d906fecbad956cc4875774 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 6 May 2026 15:37:53 -0700 Subject: [PATCH] test(agent_session_endpoints): regression tests for required JWT secret Asserts: * _get_signing_secret raises AgentJWTSecretNotConfiguredError when LITELLM_AGENT_JWT_SECRET is unset, even with LITELLM_MASTER_KEY set (no silent fallback). * is_agent_jwt_secret_configured returns False for unset / empty, True for any non-empty value. * mint_daemon_token and decode_daemon_token both surface the same error when the env var is missing. Greptile P1 SECURITY regression coverage (validation #15). --- .../test_jwt_secret_required.py | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/test_litellm/proxy/agent_session_endpoints/test_jwt_secret_required.py diff --git a/tests/test_litellm/proxy/agent_session_endpoints/test_jwt_secret_required.py b/tests/test_litellm/proxy/agent_session_endpoints/test_jwt_secret_required.py new file mode 100644 index 00000000000..235fec41c83 --- /dev/null +++ b/tests/test_litellm/proxy/agent_session_endpoints/test_jwt_secret_required.py @@ -0,0 +1,97 @@ +""" +Validation #15 — daemon JWT secret is REQUIRED, no master-key fallback. + +The daemon JWT secret (``LITELLM_AGENT_JWT_SECRET``) MUST be a separate +credential from the proxy master key. A prior version of +``auth._get_signing_secret`` fell back to ``LITELLM_MASTER_KEY`` when +the dedicated env var was unset — which conflated two distinct auth +surfaces and meant a captured daemon JWT could be used to mint regular +API keys with master-key authority. + +This test file enforces: + 1. ``_get_signing_secret`` raises when ``LITELLM_AGENT_JWT_SECRET`` is + unset, even when ``LITELLM_MASTER_KEY`` IS set (no silent fallback). + 2. ``is_agent_jwt_secret_configured()`` returns False in that case so + ``proxy_server.py`` knows to skip mounting the routers. + 3. ``mint_daemon_token`` and ``decode_daemon_token`` both surface the + same error — every caller is gated by the env var. +""" + +import pytest + +from litellm.proxy.agent_session_endpoints.auth import ( + AgentJWTSecretNotConfiguredError, + _get_signing_secret, + decode_daemon_token, + is_agent_jwt_secret_configured, + mint_daemon_token, +) + + +def test_signing_secret_raises_without_dedicated_env_var(monkeypatch): + """The dedicated env var is the ONLY source of the secret. + + Setting LITELLM_MASTER_KEY (and clearing LITELLM_AGENT_JWT_SECRET) + must NOT silently provide a fallback. + """ + monkeypatch.delenv("LITELLM_AGENT_JWT_SECRET", raising=False) + monkeypatch.setenv("LITELLM_MASTER_KEY", "sk-master-key-which-must-not-be-used") + + with pytest.raises(AgentJWTSecretNotConfiguredError): + _get_signing_secret() + + +def test_is_agent_jwt_secret_configured_reports_unset(monkeypatch): + """Used at startup to decide whether to mount the routers. + + Returns False when the env var is unset, even if the master key is + set — preventing the proxy from silently exposing an + auth-conflated /v2/agents surface. + """ + monkeypatch.delenv("LITELLM_AGENT_JWT_SECRET", raising=False) + monkeypatch.setenv("LITELLM_MASTER_KEY", "sk-1234") + assert is_agent_jwt_secret_configured() is False + + +def test_is_agent_jwt_secret_configured_reports_set(monkeypatch): + monkeypatch.setenv("LITELLM_AGENT_JWT_SECRET", "dedicated-jwt-secret-value") + assert is_agent_jwt_secret_configured() is True + + +def test_is_agent_jwt_secret_configured_rejects_empty_string(monkeypatch): + """Empty string should be treated as unset — same as ``os.environ.get`` semantics.""" + monkeypatch.setenv("LITELLM_AGENT_JWT_SECRET", "") + assert is_agent_jwt_secret_configured() is False + + +def test_mint_token_raises_without_dedicated_env_var(monkeypatch): + monkeypatch.delenv("LITELLM_AGENT_JWT_SECRET", raising=False) + monkeypatch.setenv("LITELLM_MASTER_KEY", "sk-1234") + with pytest.raises(AgentJWTSecretNotConfiguredError): + mint_daemon_token( + session_id="sess_abc", + agent_id="agt_abc", + expires_at_epoch=10**10, + ) + + +def test_decode_token_raises_without_dedicated_env_var(monkeypatch): + """Even decoding (which the daemon-auth dependency calls on every + request) must refuse to operate without the dedicated secret.""" + monkeypatch.delenv("LITELLM_AGENT_JWT_SECRET", raising=False) + monkeypatch.setenv("LITELLM_MASTER_KEY", "sk-1234") + with pytest.raises(AgentJWTSecretNotConfiguredError): + decode_daemon_token("any-token-bytes") + + +def test_proxy_server_skips_mounting_when_secret_missing(monkeypatch): + """End-to-end: simulate the startup check ``proxy_server.py`` runs. + + When the env var is unset, ``is_agent_jwt_secret_configured`` returns + False — and that is the function ``proxy_server.py`` inspects to + decide whether to mount the routers. + """ + monkeypatch.delenv("LITELLM_AGENT_JWT_SECRET", raising=False) + # No master key fallback even when LITELLM_MASTER_KEY is set. + monkeypatch.setenv("LITELLM_MASTER_KEY", "sk-1234") + assert is_agent_jwt_secret_configured() is False