Skip to content

feat(mcp): dcr_bridge authorize and token relay redirect handling with mandatory S256 - #32747

Merged
tin-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_lit4337_dcr_bridge_authorize_relay
Jul 10, 2026
Merged

feat(mcp): dcr_bridge authorize and token relay redirect handling with mandatory S256#32747
tin-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_lit4337_dcr_bridge_authorize_relay

Conversation

@tin-berri

@tin-berri tin-berri commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Part of LIT-4337 (PR 2 of the stack, based on #32745 so the review diff is only this change; GitHub retargets to litellm_internal_staging when #32745 merges). The discovery facade and register relay that make these paths discoverable land in PR 3

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to 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

Live proxy from this branch on localhost:4010 (backed by real Postgres, with the dcr_bridge column from #32745 now on staging), a true_passthrough server with dcr_bridge on pointing at the real Linear MCP upstream. These curls drive the authorize and token relays this PR changes; the redirect target is Linear's real authorization server

  1. The authorize relay arm forwards the client's client_id, redirect_uri, state, and S256 PKCE to Linear verbatim, with no gateway callback injected
curl -si "http://localhost:4010/linear_bridge_b/authorize?client_id=dcr-client-xyz&redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback&state=b-proof&code_challenge=E9Mel4x0V4dJyfWkjClQGnMLLJFPZTs9zdVzdXJx6-k&code_challenge_method=S256&response_type=code"
HTTP/1.1 307 Temporary Redirect
location: https://mcp.linear.app/authorize?client_id=dcr-client-xyz&redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback&state=b-proof&response_type=code&code_challenge=E9Mel4x0V4dJyfWkjClQGnMLLJFPZTs9zdVzdXJx6-k&code_challenge_method=S256&scope=read+write+openid+email
  1. PKCE is mandatory on the bridge arms; a missing challenge and a plain (non-S256) method are both rejected
curl -s ".../authorize?client_id=dcr-client-xyz&redirect_uri=...&state=b-proof&response_type=code"
{"detail":"This server requires PKCE: send code_challenge with code_challenge_method=S256 on the authorization request"}   HTTP 400

curl -s ".../authorize?...&code_challenge=abc&code_challenge_method=plain&response_type=code"
HTTP 400
  1. The token relay arm requires the client's redirect_uri so it matches the authorize leg
curl -s -X POST "http://localhost:4010/linear_bridge_b/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=authorization_code&code=fake-code&client_id=dcr-client-xyz&code_verifier=v"
{"detail":"redirect_uri is required for the authorization_code grant on this server; send the same redirect_uri used on the authorization request"}   HTTP 400

The complete interactive flow (MCP Inspector and Claude Desktop finishing DCR sign-in through the gateway) exercises these same endpoints and is proven end to end in the discovery-facade PR that makes them discoverable. Unit coverage: 11 bridge-specific tests plus the full discoverable-endpoints file at 123 passing, with the flag-off relay behavior pinned byte-identical

Type

🆕 New Feature

Changes

This is the isolated security diff of the DCR-bridge stack: how the gateway's authorize and token relays handle the client redirect target for dcr_bridge servers. Keeping it to one reviewable change is deliberate because the redirect target is exactly the VERIA-57 code-theft surface

The design splits by whether the gateway holds an upstream client registration. In the relay arm (the server has a registration_url and no admin-configured client_id, so client registration is relayed to the upstream AS), the authorize relay passes the client's client_id, redirect_uri, state, and PKCE parameters through verbatim and sets no state cookie; the authorization code returns from the upstream directly to the client's own redirect URI and never transits the gateway, and the upstream AS enforces the redirect binding it recorded at registration. The token relay correspondingly posts the client's redirect_uri (required, matching the authorize leg) instead of the gateway callback. Because the gateway never has custody of the code in this arm, no gateway-side redirect trust decision exists to make, which is a strictly smaller attack surface than validating and forwarding codes to third-party redirect targets

In the short-circuit arm (an admin-configured or persisted OAuth client, so the upstream only knows the gateway's own callback), the existing /callback encrypted-state relay and validate_trusted_redirect_uri gate (same-origin, loopback, MCP_TRUSTED_REDIRECT_ORIGINS ops allowlist) apply unchanged; ops opt public clients in through the existing allowlist rather than any new mechanism

On both arms, dcr_bridge servers require PKCE with code_challenge_method=S256 at the gateway (RFC 7636 defaults a missing method to plain, so the method must be explicit); these servers serve unauthenticated public clients, so the downgrade paths are rejected here rather than trusting upstream enforcement. The dashboard's browser Authorize already sends S256 so it is unaffected, and servers without dcr_bridge keep the pre-bridge contract byte for byte (pinned by tests)

Tests cover the verbatim passthrough contract for both client-forwarded auth types, the four PKCE downgrade rejections, the short-circuit arm keeping callback plus redirect trust, the no-flag regression pins for authorize and token, and the token relay's redirect_uri requirement and upstream form fields


Note

High Risk
Changes OAuth authorize/token redirect handling on a security-sensitive surface (VERIA-57 code-theft); relay arm bypasses gateway redirect trust by design, so correctness of arm detection and PKCE enforcement matters.

Overview
For MCP servers with dcr_bridge, authorize and token handling now split into two paths instead of always forcing the gateway /callback relay.

Relay arm (upstream DCR via registration_url, no admin client_id): authorize_with_server redirects to the upstream IdP with the client’s client_id, redirect_uri, state, and PKCE unchanged—no OAuth state cookie and no gateway callback. exchange_token_with_server posts the client’s redirect_uri (required) to the upstream token endpoint instead of the proxy callback.

Short-circuit arm (admin or persisted OAuth client): existing validate_trusted_redirect_uri, encrypted state, and /callback behavior is unchanged after mandatory S256 PKCE.

All dcr_bridge servers must send code_challenge with code_challenge_method=S256 at the gateway; missing or non-S256 PKCE returns 400. Servers without dcr_bridge keep prior behavior (covered by regression tests).

Eleven new unit tests pin relay passthrough, PKCE rejections, short-circuit redirect trust, and token form fields.

Reviewed by Cursor Bugbot for commit 195f145. Bugbot is set up for automated code reviews on this repo. Configure here.

@tin-berri
tin-berri force-pushed the litellm_lit4337_dcr_bridge_authorize_relay branch from f937dd6 to 306a687 Compare July 10, 2026 07:29
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds the DCR-bridge authorize and token relay logic: for servers with dcr_bridge and a registration_url but no admin-configured client_id, the gateway passes the client's client_id, redirect_uri, state, and PKCE parameters verbatim to the upstream AS and sets no state cookie, so authorization codes flow directly to the client without transiting the gateway. For servers with an admin-configured client_id, the existing /callback relay and validate_trusted_redirect_uri gate are preserved unchanged. S256 PKCE is enforced at the gateway on both bridge arms.

  • _require_s256_pkce rejects missing challenges and non-S256 methods at the gateway; the _redirect_to_upstream_authorize helper merges client-supplied params into the upstream authorize URL without injecting a gateway callback.
  • exchange_token_with_server requires redirect_uri in the relay arm (must match the authorize leg) and sends it directly to the upstream token endpoint instead of substituting the gateway callback; the pre-bridge contract is preserved byte-for-byte for non-bridge servers.
  • 11 new tests pin the verbatim passthrough contract, the four PKCE downgrade rejections, the short-circuit arm's trust gate, and the token relay form fields; no existing tests are modified.

Confidence Score: 5/5

Safe to merge; the relay-arm and short-circuit-arm branching is correctly implemented and well-tested.

The relay arm correctly avoids gateway custody of authorization codes, S256 PKCE enforcement is well-tested across all four downgrade paths, and the short-circuit arm's existing trust gate is untouched. All 11 tests are new and additive with no weakening of prior coverage.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py Adds three helper functions and branches both authorize_with_server and exchange_token_with_server on the dcr_bridge relay arm vs short-circuit arm; the security properties are correctly implemented.
tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py Adds 11 new tests covering verbatim passthrough, PKCE downgrade rejections, short-circuit redirect trust, pre-bridge regression pin, and token relay fields; all new, no existing tests modified.

Reviews (4): Last reviewed commit: "docs(mcp): note S256 enforcement fires o..." | Re-trigger Greptile

Comment thread litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py Outdated
Comment thread litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py
Comment thread litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@tin-berri

Copy link
Copy Markdown
Contributor Author

On the third finding (bridge_challenge/bridge_method unused on the short-circuit arm): that is the deliberate shape of the narrowing helper. _require_s256_pkce both enforces and narrows; the relay arm consumes the narrowed pair, while the short-circuit arm calls it purely for enforcement and continues into the /callback flow with the original Optional values, which the downstream encode-state path accepts. Splitting enforcement from narrowing would mean either two helpers with one call site each or a cast, which the type-discipline gate rejects. The double _dcr_bridge_relays_client_registration call and the detail-shape inconsistency are fixed in the follow-up commit

Base automatically changed from litellm_lit4337_dcr_bridge_plumbing to litellm_internal_staging July 10, 2026 18:45
@tin-berri
tin-berri force-pushed the litellm_lit4337_dcr_bridge_authorize_relay branch from 733cf1e to c79dcef Compare July 10, 2026 18:46
@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai rereview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 c79dcef. Configure here.

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 195f145. Configure here.

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; thanks!

@tin-berri
tin-berri enabled auto-merge July 10, 2026 19:25
@codspeed-hq

codspeed-hq Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_lit4337_dcr_bridge_authorize_relay (195f145) with litellm_internal_staging (b9008cc)

Open in CodSpeed

@tin-berri
tin-berri merged commit 69f0a6d into litellm_internal_staging Jul 10, 2026
132 checks passed
@tin-berri
tin-berri deleted the litellm_lit4337_dcr_bridge_authorize_relay branch July 10, 2026 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants