feat(mcp): identity-only session tokens for the gateway DCR front door - #33182
Conversation
Greptile SummaryThis PR adds the identity-only session token module for the gateway-level DCR front door (LIT-3637, PR 2 of the stack). It is a pure, unwired module that nothing imports yet — the token-endpoint and admission wiring land in later stacked PRs.
Confidence Score: 5/5Safe to merge. This is a self-contained, pure module with no call sites yet; its security properties are enforced by tests and the code itself is consistent with the existing envelope.py pattern. The two new modules are pure, unwired additions with no side effects on any existing path. The token design is sound: KDF domain separation is verified, cross-kind replay is blocked at the signed-claims level, the Pydantic model is the strict total gate for every claim, and all error paths return values rather than raising. Tests cover all documented edge cases. No issues introduced into any existing code path. No files require special attention. All four files are new additions with no modifications to existing code.
|
| Filename | Overview |
|---|---|
| litellm/proxy/_experimental/mcp_server/outbound_credentials/session_token.py | New pure module for identity-only session tokens: prefix routing, HS256 JWT with strict Pydantic gate, kind-claim cross-replay protection, and total error-value semantics over hostile input. Closely mirrors envelope.py; no issues found. |
| litellm/proxy/_experimental/mcp_server/outbound_credentials/session_credentials.py | New KDF + edge/token-endpoint resolver layer; scrypt domain label distinct from bridge_credentials, lru_cache safe for a fixed process master key, resolver logic correctly fails closed on refresh-at-tool-edge and wrong-client-id. No issues found. |
| tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_session_token.py | Comprehensive new tests: round-trips, TTL boundary, kind cross-replay, tamper, key rotation, alg=none rejection, hostile input totality (surrogates, oversized, multibyte), and mint guard for oversized client_id. No network calls; all mocked. |
| tests/test_litellm/proxy/_experimental/mcp_server/outbound_credentials/test_session_credentials.py | New tests covering KDF determinism, key length, domain separation from envelope keys, Bearer-scheme stripping for resolve_session_bearer, expired/tampered/wrong-key/wrong-client failure paths. No network calls. |
Reviews (2): Last reviewed commit: "feat(mcp): add jti claim for per-mint se..." | Re-trigger Greptile
| iss: str | ||
| iat: int | ||
| exp: int |
There was a problem hiding this comment.
_SessionClaims.iss is typed as str rather than Literal["litellm-mcp-gateway"]. PyJWT's issuer=SESSION_ISSUER parameter is the primary enforcer, but if that check ever regresses (e.g., a future PyJWT API change silently skips issuer validation), the Pydantic model would pass any well-formed string in iss. Making it a Literal adds a second gate at zero cost.
| iss: str | |
| iat: int | |
| exp: int | |
| iss: Literal["litellm-mcp-gateway"] | |
| iat: int | |
| exp: int |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
1a2977b to
8f310c1
Compare
8eb4219 to
4e0da77
Compare
c67006c to
8a57067
Compare
186c862 to
a22182f
Compare
|
bugbot run |
|
@greptileai rereview |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a22182f. Configure here.
Relevant issues
Stacked on #33174 (aggregate discovery front door); the base of this PR is
litellm_lit3637_aggregate_dcr, not staging. Review and merge after #33174; the diff against that branch is the session-token module onlyLinear ticket
Part of LIT-3637 (PR 2 of the stack: the identity-only session credential; pure module, unwired until the token endpoint and admission PRs land on top)
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
This is a pure, unwired module (nothing imports it yet), so the contract is pinned by unit tests rather than a live flow; the interactive proof lands with the token-endpoint and admission PRs stacked above, which will exercise these mints and openers on a live proxy end to end
Type
🆕 New Feature
Changes
PR 2 of the LIT-3637 aggregate DCR track: the client-held credential for the custody model. A DCR client that signs in through LiteLLM SSO holds ONE bearer carrying only a litellm identity; upstream tokens are vaulted server-side in
LiteLLM_MCPUserCredentialsand resolved by user at egress, so unlike the LIT-4338 bridge envelope there is nothing to seal. The token is a stable reference, not an authorization: admission (PR 4) reloads the live user record and policy on every request, so deactivating a user or their team kills outstanding sessions immediately without a revocation store. This is the same sealed-reference pattern the envelope admission arm settled on after security review of #32824session_token.pyis the pure module, mirroringenvelope.pyexactly:llm_session_access andllm_srefresh_refresh prefixes with a signedkindclaim so a prefix swap cannot cross kinds; HS256 with a strict,extra="forbid"claims model as the sole type gate; PyJWT'siat/nbf/expvalidators disabled for the reasons documented there (they raise on hostile claim types and compare against the wall clock rather than the injected clock); openers total over hostile input, returning tagged error values, never raising; 1h access and 14d refresh TTL caps matching the envelope bounds; a 4KB size cap enforced O(1)-cheaply before any JWT parsing. Claims areiss/iat/exp/kind/user_id/client_id;client_idbinds the refresh token to the DCR client it was issued to per RFC 6749 section 6session_credentials.pyis the wired mirror ofbridge_credentials.py: a memory-hard scrypt KDF from the proxy master key under a session-specific domain label, so session tokens and bridge envelopes never share key material (on top of distinct issuers, prefixes, and claim shapes);resolve_session_bearerfor the admission edge (refresh tokens presented at the tool edge fail closed, expired is distinguished from tampered for logging only);open_session_refresh_bearerfor the token endpoint with the client binding check insideTests cover round-trips and TTL caps, kind cross-replay via prefix swap, expiry boundary, tampered signatures, key rotation, alg=none rejection, signed-but-malformed claims (wrong issuer, coerced types, empty identity, extra
nbf, missing claims), lone-surrogate and oversize hostile input including the multibyte char-vs-byte cap edge, the oversized-client_id mint guard, KDF domain separation from the envelope keys, Bearer-scheme stripping, and refresh client bindingQA runbook
Unit-only PR; run the two test files above. For a by-hand sanity check in a REPL: derive keys with
session_keys_from_master_key("sk-1234"), mint withmint_session_token(SessionPrincipal(user_id="u", client_id="c"), keys, datetime.now(timezone.utc)), and confirm the minted value round-trips throughresolve_session_bearerand fails closed after editing any characterFinal Attestation
Note
Medium Risk
New authentication token and KDF code with careful fail-closed design, but not yet wired into live admission; mistakes in follow-up PRs could affect gateway auth.
Overview
Adds a pure, unwired credential layer for the aggregate
/mcpDCR custody model: client-held bearers carry onlyuser_idandclient_id, not upstream secrets.session_token.pymints and validatesllm_session_/llm_srefresh_HS256 JWTs (1h access, 14d refresh) with strict claims (kind,jti, issuer separation from bridge envelopes), a 4KB cap, and openers that return typed errors instead of raising on hostile input.session_credentials.pyderives signing keys from the proxymaster_keyvia scrypt with a session-specific domain label (separate from envelope keys), plusresolve_session_bearerfor MCP admission (refresh tokens fail closed at the tool edge) andopen_session_refresh_bearerfor token-endpoint refresh withclient_idbinding.Unit tests cover round-trips, KDF separation, kind/prefix replay, expiry, tampering, and malformed JWT edge cases.
Reviewed by Cursor Bugbot for commit a22182f. Bugbot is set up for automated code reviews on this repo. Configure here.