Repository navigation
fix(mcp): reject origins outside the configured allowlist - #42649
Conversation
|
@greptileai Please review 7f2dcea for Origin rejection, compatibility, and denied upstream execution across both MCP transports. bugbot run |
|
|
bugbot run Please review commit 7f2dcea for Origin validation, preserved client compatibility, and blocked upstream execution. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
7f2dcea to
1321eda
Compare
|
@greptileai please review 1321eda after the rebase onto main for Origin rejection across both MCP transports |
|
bugbot run |
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 1321eda. Configure here.
Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
TLDR
Problem this solves:
How it solves it:
User Flow
Before: configured Origin restrictions only control browser response visibility
LITELLM_CORS_ORIGINS=http://localhost:4135and starts the gatewayhttp://localhost:4135/mcp/conformance_refwithOrigin: https://untrusted.exampleAfter: configured Origin restrictions also reject MCP requests
LITELLM_CORS_ORIGINS=http://localhost:4135and starts the gatewayhttp://localhost:4135/mcp/conformance_refwithOrigin: https://untrusted.exampleRelevant issues
The MCP transport specification requires HTTP 403 for a disallowed Origin
Affected release
Reproduced on v1.102.0 and main; introduction version has not been established
Linear ticket
Prerequisite for LIT-7744. The CI-only conformance gate will ship separately
Pre-Submission checklist
Screenshots / Proof of Fix
Source-built verification after the rebase onto
main, using a DB-free gateway and the official MCP reference server (@modelcontextprotocol/server-everything2026.8.31, streamable HTTP on port 3301). Baseline listened on 4137, tip on 4135; each process hadLITELLM_CORS_ORIGINS=http://localhost:<its port>. Both imported their own worktree source with the same locked environmentShared setup: configure an HTTP MCP server named
conformance_refpointing tohttp://localhost:3301/mcpand setKEYto the gateway master key. Request bodies:Before (093ceb5)
Streamable HTTP initialize
Send an authenticated initialize with a disallowed Origin:
Observed
HTTP/1.1 200 OKand an initialized MCP sessionRepeat with
Origin: http://localhost:4137, with no Origin, with duplicateOriginheaders (allowed plus evil in both orders), withOrigin: null, and against the rootPOST /mcp: all return 200Repeat the evil-Origin initialize without the
Authorizationheader:401 {"error":{"message":"Authentication required"...}}Streamable HTTP tool execution
$CALLwith the allowed Origin and with no Origin: both return 200 withEcho: MARKER_UPSTREAM_CALL$CALLwithOrigin: http://evil.example.com, then again withmcp-session-id: bogus: both return 200 and the echo textMARKER_UPSTREAM_CALLin the gateway debug log: it grows from 8 to 16 across the two denied-Origin calls, so both executed upstreamGET /mcp/conformance_refandDELETE /mcp/conformance_refwith the evil Origin and a bogus session return 200;DELETEwith the allowed Origin and no session returns 405Legacy SSE
GET http://localhost:4137/mcp/ssewithOrigin: http://evil.example.com:HTTP/1.1 200 OKevent stream. Same with duplicate allowed plus evil headers, the allowed Origin, and no OriginAuthorization:401 UnauthorizedPOST http://localhost:4137/mcp/sse/messageswith$INITand the evil Origin:400 session_id is required, the same protocol response as the allowed and no-Origin casesAfter (1321eda)
Streamable HTTP initialize
Send the same initialize against the tip:
Observed
HTTP/1.1 403 Forbiddenand{"detail":"Invalid Origin header"}Origin: http://localhost:4135and no Origin still return 200 with the initialized session. Duplicate headers in either order,Origin: null, and the rootPOST /mcpwith the evil Origin return 403The evil-Origin initialize without
Authorizationreturns 403Invalid Origin header, so the Origin check runs before authenticationStreamable HTTP tool execution
$CALLwith the allowed Origin and with no Origin: 200 withEcho: MARKER_UPSTREAM_CALL, unchanged$CALLwith the evil Origin, with and withoutmcp-session-id: bogus:403 {"detail":"Invalid Origin header"}MARKER_UPSTREAM_CALLstays at 8 in the gateway debug log across the two denied calls: zero denied upstream executionsGETandDELETE /mcp/conformance_refwith the evil Origin return 403;DELETEwith the allowed Origin and no session still returns 405Legacy SSE
GET http://localhost:4135/mcp/ssewith the evil Origin or duplicate allowed plus evil headers:HTTP/1.1 403 Forbidden. The allowed Origin and no Origin still return the 200 event streamAuthorization:403 ForbiddenPOST http://localhost:4135/mcp/sse/messageswith the evil Origin:403 {"detail":"Invalid Origin header"}; allowed and no-Origin requests still return400 session_id is requiredA third run of the tip on port 4136 with
LITELLM_CORS_ORIGINSunset (default wildcard policy) reproduced every Before result exactly, including 16 upstream markers, so default and wildcard deployments are unchangedLocal gates at the tip:
make lintpassed every gate (Ruff lint and format, strict-rule, type-discipline, test-quality, basedpyright, circular imports). The mapped test file passed 389 tests with--cov-branch: all 6 changed executable lines and both outcomes of the new guard condition are covered.test_wrapper_async_does_not_fire_failure_hook_for_post_success_error, which failed inmisc / Run testsat the previous merge base, was changed onmainin #42646 and passes at this tipType
Bug Fix
Caveats
Severe
Low
Final Attestation
Link to Devin session: https://app.devin.ai/sessions/f91a8ec1cc4b40d09f76e408c127c23b
Open in Devin Desktop: https://app.devin.ai/desktop/session/f91a8ec1cc4b40d09f76e408c127c23b?variant=devin
Note
Medium Risk
Tightens MCP admission when explicit CORS origins are set; wildcard/default behavior is unchanged, but operators with a fixed allowlist may see previously accepted cross-origin MCP clients start failing with 403.
Overview
MCP Streamable HTTP and legacy SSE entry points now enforce the same Origin policy as the gateway CORS allowlist (
LITELLM_CORS_ORIGINS/origins), instead of only affecting browser-visible responses.A new
reject_disallowed_mcp_originruns at the start of both handlers. When the allowlist is not wildcard (*), anyOriginvalue not in the list yields HTTP 403 withInvalid Origin header, including duplicate headers,null, empty, and subdomain lookalikes. Requests with noOriginstill proceed to normal auth. Wildcard CORS config is unchanged.The check runs before MCP authentication and upstream work, so disallowed origins no longer initialize sessions or execute tools. Tests cover multiple MCP routes/methods and assert auth is skipped on 403.
Reviewed by Cursor Bugbot for commit 1321eda. Bugbot is set up for automated code reviews on this repo. Configure here.