feat(fetch): add opt-in host allowlisting (--allowed-hosts) - #4770
Open
g-yixuan wants to merge 1 commit into
Open
feat(fetch): add opt-in host allowlisting (--allowed-hosts)#4770g-yixuan wants to merge 1 commit into
g-yixuan wants to merge 1 commit into
Conversation
Add a host allowlist for the fetch server, addressing the SSRF surface tracked in modelcontextprotocol#2317 without changing default behavior: - New --allowed-hosts flag (exact hosts, case-insensitive; *.example.com wildcards covering the bare domain and any subdomain; IP literals). - Enforced on the initial request, the robots.txt pre-check, and every redirect hop: redirects are now followed manually (bounded at 20, same as httpx's default) and each hop is re-validated, so a redirect from an allowed host can no longer bounce a fetch to a disallowed host. - URL validation parses with httpx.URL — the same parser used to connect — so the validated host is always the host being connected. - Denials fail closed with an error that does not echo the allowlist. Default-IP-blocking is intentionally left to the separate default-deny proposal; with no flag set, behavior is identical to before.
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.
Description
Adds an opt-in host allowlist to the
fetchserver, addressing the SSRF surface tracked in #2317 without changing default behavior.Fixes #2317.
--allowed-hosts HOST [HOST ...]CLI flag. When unset, behavior is exactly as before (all hosts allowed), so deployments that intentionally fetch localhost/internal endpoints are unaffected (per the discussion on the issue).example.com); wildcard (*.example.com) matching the bare domain and any subdomain; literal IPs.robots.txtpre-check, and every redirect hop: redirects are now followed manually (bounded at 20, same as httpx's default) and each hop is re-validated before connecting, so a 302 from an allowed host can no longer bounce a fetch to a disallowed host.Publishing Your Server
N/A — change to an existing server.
Server Details
mcp-server-fetch)Motivation and Context
#2317 asks for host allowlisting. The issue discussion raised valid concerns about breaking users who intentionally fetch internal endpoints, so this PR deliberately does not change the default posture — the default-deny/IP-range part of the issue is separately covered by #4497, and this PR composes cleanly with it. A previous allowlisting attempt (#2568) went stale and was closed with an invitation to resubmit on a current base; this is that fresh take, scoped to avoid the default-behavior controversy.
On the TOCTOU/socket-level point in the issue: with a hostname allowlist, the validated hostname is exactly what gets connected (SNI/Host), so DNS check-time/connect-time skew does not weaken the control; the realistic bypass was redirect hops, which this closes.
How Has This Been Tested?
uv run pytest— 64 passed (existing suite untouched and green, plus new coverage: exact/wildcard/IP matching, case/trailing-dot normalization, userinfo bypass attempt, per-hop redirect enforcement parametrized over 301/302/303/307/308, redirect-loop limit, empty and malformedLocationheaders (matching httpx's prior behavior), relativeLocationresolution, robots.txt path enforcement, per-request timeout semantics, and unset-flag backwards compatibility).uv run pyright— 0 errors;uv run ruff check .— clean.--allowed-hosts 127.0.0.1, plus a real local HTTP server — allowlisted host fetched,http://169.254.169.254/latest/meta-datablocked, redirect to a non-allowlisted host blocked, malformedLocationhandled with a clean error.httpx.URL— the same parser that performs the connection — so the validated host is always the host that gets connected.Breaking Changes
None — default behavior is unchanged when
--allowed-hostsis not set.Types of changes
Checklist
Additional context
*.example.commatchesexample.comitself and any subdomain.