fix: address Greptile round 3 feedback

- 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
This commit is contained in:
Ishaan Jaffer 2026-03-17 14:56:45 -07:00
parent 9cceff757d
commit 8acb06dc68
3 changed files with 10 additions and 2 deletions

View file

@ -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)

View file

@ -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]

View file

@ -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: