PrefectHQ/fastmcp · error · SSRFFetchError

HTTP {response.status_code} fetching {url}

Error message

HTTP {response.status_code} fetching {url}

What it means

ssrf_safe_fetch_response raises SSRFFetchError when the HTTP response status code is not in the allowed set (default {200}; callers may widen via allowed_status_codes). Because redirects are not followed (follow_redirects=False), 3xx responses also trigger this error unless explicitly allowed.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/ssrf.py:495

                    # normal default (True). Proxy-trust mode sets an explicit
                    # proxy_url and turns trust_env off, so httpx2 has no environment
                    # -based routing decision left to make — see validate_url() above
                    # for why that matters.
                    proxy=target.proxy_url,
                    trust_env=target.proxy_url is None,
                ) as client,
                client.stream(
                    "GET",
                    target.url,
                    headers=headers,
                    extensions=extensions,
                ) as response,
            ):
                if time.monotonic() - start_time > overall_timeout:
                    raise SSRFFetchError(f"Overall timeout exceeded: {url}")

                if response.status_code not in expected_statuses:
                    raise SSRFFetchError(f"HTTP {response.status_code} fetching {url}")

                # Check Content-Length header first if available
                content_length = response.headers.get("content-length")
                if content_length:
                    try:
                        size = int(content_length)
                        if size > max_size:
                            raise SSRFFetchError(
                                f"Response too large: {size} bytes (max {max_size})"
                            )
                    except ValueError:
                        pass

                # Stream the response and enforce size limit during download
                chunks = []
                total = 0
                async for chunk in response.aiter_bytes():
                    if time.monotonic() - start_time > overall_timeout:

View on GitHub (pinned to 1f02114297)

Solutions

  1. Verify the URL is the final, canonical resource location (no redirect chain).
  2. Widen acceptance with allowed_status_codes={200, 301, 302} only if you also handle the Location manually — redirect following is intentionally off for SSRF safety.
  3. Pass required credentials via request_headers (note: Host header is stripped).
  4. If fetching a CIMD client_id, confirm the metadata document exists at the issuer URL exactly.
  5. Catch SSRFFetchError, inspect the str() for the status code, and surface a clear message to the user.

Example fix

// before
resp = await ssrf_safe_fetch_response(url)  # 404 -> SSRFFetchError
// after
resp = await ssrf_safe_fetch_response(url, allowed_status_codes={200, 404})
if resp.status_code == 404:
    return None  # metadata not published
Defensive patterns

Strategy: try-catch

Validate before calling

# pre-check the endpoint out-of-band
code = (await client.head(url, follow_redirects=False)).status_code
if code not in (200,):
    raise RuntimeError(f"endpoint returned {code}, not 200")

Try / catch

try:
    resp = await ssrf_safe_fetch_response(url)
except SSRFFetchError as e:
    m = re.match(r"HTTP (\d+)", str(e))
    if m:
        status = int(m.group(1))
        # handle 404 / redirect specifically
    else:
        raise

Prevention

When it happens

Trigger: Target returns 301/302/307 redirect (redirects are disabled by design); returns 404/410 for a missing CIMD metadata document; returns 403/401 for auth-protected resources; or any non-200 when the caller did not pass allowed_status_codes.

Common situations: Fetching OAuth client metadata at a URL that redirects to a canonical domain; document not published at the exact issuer URL; server requiring an Authorization header the caller omitted from request_headers.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/44edae49e9fa762f. Report an issue: GitHub.