Skip to content

fix: add SSRF redirect protection to httpx client factory#2180

Open
dgenio wants to merge 5 commits intomodelcontextprotocol:mainfrom
dgenio:feat/ssrf-redirect-protection
Open

fix: add SSRF redirect protection to httpx client factory#2180
dgenio wants to merge 5 commits intomodelcontextprotocol:mainfrom
dgenio:feat/ssrf-redirect-protection

Conversation

@dgenio
Copy link

@dgenio dgenio commented Feb 28, 2026

What changed

Adds a RedirectPolicy enum and redirect validation event hook to create_mcp_http_client() in src/mcp/shared/_httpx_utils.py to protect against SSRF attacks via HTTP redirects.

Changes:

  • src/mcp/shared/_httpx_utils.py:

    • Added RedirectPolicy enum with three presets:
      • BLOCK_SCHEME_DOWNGRADE (default) — blocks HTTPS→HTTP redirect downgrades
      • ENFORCE_HTTPS — only allows HTTPS redirect destinations
      • ALLOW_ALL — no restrictions (legacy behavior)
    • Added _check_redirect() async response event hook that validates redirect targets against the configured policy using response.has_redirect_location and the Location header
    • Added redirect_policy parameter to create_mcp_http_client() (default: BLOCK_SCHEME_DOWNGRADE)
    • McpHttpClientFactory Protocol left unchanged — redirect_policy is an internal concern of create_mcp_http_client, not part of the caller-side contract
  • src/mcp/client/streamable_http.py:

    • Added docstring note on http_client parameter clarifying that user-provided clients do not receive SSRF redirect protection
    • Added logger.debug when a user-provided client is used
  • tests/shared/test_httpx_utils.py:

    • 14 unit tests covering all three policies, edge cases (non-redirect responses, missing Location header), and all scheme combinations
    • 2 integration tests using httpx.MockTransport that exercise the event hook wiring end-to-end through the real httpx client

Why

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 set follow_redirects=True with zero redirect validation. The default BLOCK_SCHEME_DOWNGRADE policy 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_request to inspect redirect targets, but httpx never populates next_request when follow_redirects=True — causing the protection to silently bypass all checks. Fixed to use response.has_redirect_location + parse the Location header directly, matching httpx's own _build_redirect_request flow.

How verified

  • ruff check . — all checks passed
  • ruff format --check . — no formatting issues
  • pyright — 0 errors, 0 warnings
  • pytest tests/shared/test_httpx_utils.py -v — 16 passed
  • pytest tests/client/test_client.py -v — 19 passed (no regressions)

Tradeoffs / risks

  • The new redirect_policy parameter on create_mcp_http_client has a default value, so all existing callers are unaffected — no breaking change
  • McpHttpClientFactory Protocol is not modified, preserving compatibility for downstream code implementing custom factories
  • Users who need the legacy behavior can pass redirect_policy=RedirectPolicy.ALLOW_ALL
  • Private/link-local IP range blocking is not included in this PR — suggested as a follow-up

Scope notes

  • RedirectPolicy is not re-exported from mcp.__init__create_mcp_http_client itself is also not publicly exported, so both share the same visibility via mcp.shared._httpx_utils. Public API surface expansion is suggested as a follow-up
  • HttpResource in resources/types.py uses a bare httpx.AsyncClient() (separate concern, not addressed here)

Closes #2106

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
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.

Add SSRF protection for HTTP client redirects

1 participant