From 8acb06dc68eb840aa2f8d8f966801be0bef20032 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Tue, 17 Mar 2026 14:56:45 -0700 Subject: [PATCH] fix: address Greptile round 3 feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - initialize_guardrail: validate mode='pre_mcp_call' at init time — misconfigured mode silently bypasses JWT injection, which is a zero-trust bypass - _build_claims: remove duplicate inline 'import re' (module-level import already present) - _types.py: add TODO comment explaining jwt_claims is forward-compat plumbing for a follow-up PR that will forward upstream IdP claims into outbound MCP JWTs --- litellm/proxy/_types.py | 3 +++ .../guardrails/guardrail_hooks/mcp_jwt_signer/__init__.py | 7 +++++++ .../guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py | 2 -- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 55cb1c9c43c..240123ba6df 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2471,6 +2471,9 @@ class UserAPIKeyAuth( Any ] = None # Expanded created_by user when expand=user is used end_user_object_permission: Optional[LiteLLM_ObjectPermissionTable] = None + # TODO: jwt_claims carries decoded upstream IdP claims (groups, roles, etc.) so + # guardrails can forward them into outbound tokens (e.g. MCPJWTSigner). Currently + # populated but not yet consumed — forward-compat hook for a follow-up PR. jwt_claims: Optional[Dict] = None model_config = ConfigDict(arbitrary_types_allowed=True) diff --git a/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/__init__.py b/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/__init__.py index 230edaec855..81364448997 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/__init__.py +++ b/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/__init__.py @@ -19,6 +19,13 @@ def initialize_guardrail( if not guardrail_name: raise ValueError("MCPJWTSigner guardrail requires a guardrail_name") + mode = litellm_params.mode + if mode != "pre_mcp_call": + raise ValueError( + f"MCPJWTSigner guardrail '{guardrail_name}' has mode='{mode}' but must use " + "mode='pre_mcp_call'. JWT injection only fires for MCP tool calls." + ) + optional_params = getattr(litellm_params, "optional_params", None) def _get(key): # type: ignore[no-untyped-def] diff --git a/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py b/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py index 4fe51ba2fbc..3321515d0ff 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py +++ b/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py @@ -277,8 +277,6 @@ class MCPJWTSigner(CustomGuardrail): # tool call JWTs should not carry enumeration permissions. # Tool names are sanitized (alphanumeric + _ and -) before embedding # so path-traversal or malformed scope values cannot be injected. - import re - raw_tool_name: str = data.get("mcp_tool_name", "") tool_name = re.sub(r"[^a-zA-Z0-9_\-]", "_", raw_tool_name) if raw_tool_name else "" if tool_name: