fix: add SSRF redirect protection to httpx client factory#2180
Open
dgenio wants to merge 5 commits intomodelcontextprotocol:mainfrom
Open
fix: add SSRF redirect protection to httpx client factory#2180dgenio wants to merge 5 commits intomodelcontextprotocol:mainfrom
dgenio wants to merge 5 commits intomodelcontextprotocol:mainfrom
Conversation
Add a RedirectPolicy enum to create_mcp_http_client() that validates redirect targets via httpx event hooks. The default policy (BLOCK_SCHEME_DOWNGRADE) blocks HTTPS-to-HTTP redirect downgrades. ENFORCE_HTTPS restricts all redirects to HTTPS-only destinations. ALLOW_ALL preserves the previous unrestricted behavior. Github-Issue: modelcontextprotocol#2106
response.next_request is not populated by httpx when follow_redirects=True, causing the redirect protection hook to silently bypass all checks. Parse the Location header directly (matching httpx's own _build_redirect_request flow) and add integration tests via MockTransport to exercise the event hook wiring end-to-end. Github-Issue: modelcontextprotocol#2106
The Protocol describes the caller-side contract. No caller passes redirect_policy to the factory (sse_client only passes headers, timeout, auth). Adding it would break downstream code implementing custom factories. Github-Issue: modelcontextprotocol#2106
Add docstring note on the http_client parameter of streamable_http_client() clarifying that user-provided clients do not receive SSRF redirect protection. Also emit a logger.debug when a user-provided client is used. Github-Issue: modelcontextprotocol#2106
…anch Add test for relative Location headers (exercises the is_relative_url branch). Mark unreachable 200-response fallback in blocking test with pragma: no cover. Github-Issue: modelcontextprotocol#2106
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Adds a
RedirectPolicyenum and redirect validation event hook tocreate_mcp_http_client()insrc/mcp/shared/_httpx_utils.pyto protect against SSRF attacks via HTTP redirects.Changes:
src/mcp/shared/_httpx_utils.py:RedirectPolicyenum with three presets:BLOCK_SCHEME_DOWNGRADE(default) — blocks HTTPS→HTTP redirect downgradesENFORCE_HTTPS— only allows HTTPS redirect destinationsALLOW_ALL— no restrictions (legacy behavior)_check_redirect()async response event hook that validates redirect targets against the configured policy usingresponse.has_redirect_locationand theLocationheaderredirect_policyparameter tocreate_mcp_http_client()(default:BLOCK_SCHEME_DOWNGRADE)McpHttpClientFactoryProtocol left unchanged —redirect_policyis an internal concern ofcreate_mcp_http_client, not part of the caller-side contractsrc/mcp/client/streamable_http.py:http_clientparameter clarifying that user-provided clients do not receive SSRF redirect protectionlogger.debugwhen a user-provided client is usedtests/shared/test_httpx_utils.py:Locationheader), and all scheme combinationshttpx.MockTransportthat exercise the event hook wiring end-to-end through the real httpx clientWhy
A malicious MCP server can respond with a 3xx redirect pointing to internal network addresses (
http://169.254.169.254,http://localhost, etc.), enabling SSRF attacks. The SDK previously setfollow_redirects=Truewith zero redirect validation. The defaultBLOCK_SCHEME_DOWNGRADEpolicy prevents the most common SSRF vector (HTTPS→HTTP downgrade to reach internal services) while remaining backward-compatible for legitimate use cases.Bug discovered during review
The initial implementation used
response.next_requestto inspect redirect targets, but httpx never populatesnext_requestwhenfollow_redirects=True— causing the protection to silently bypass all checks. Fixed to useresponse.has_redirect_location+ parse theLocationheader directly, matching httpx's own_build_redirect_requestflow.How verified
ruff check .— all checks passedruff format --check .— no formatting issuespyright— 0 errors, 0 warningspytest tests/shared/test_httpx_utils.py -v— 16 passedpytest tests/client/test_client.py -v— 19 passed (no regressions)Tradeoffs / risks
redirect_policyparameter oncreate_mcp_http_clienthas a default value, so all existing callers are unaffected — no breaking changeMcpHttpClientFactoryProtocol is not modified, preserving compatibility for downstream code implementing custom factoriesredirect_policy=RedirectPolicy.ALLOW_ALLScope notes
RedirectPolicyis not re-exported frommcp.__init__—create_mcp_http_clientitself is also not publicly exported, so both share the same visibility viamcp.shared._httpx_utils. Public API surface expansion is suggested as a follow-upHttpResourceinresources/types.pyuses a barehttpx.AsyncClient()(separate concern, not addressed here)Closes #2106