{"record":{"id":"44edae49e9fa762f","repo":"PrefectHQ/fastmcp","slug":"http-response-status-code-fetching-url","errorCode":null,"errorMessage":"HTTP {response.status_code} fetching {url}","messagePattern":"HTTP (.+?) fetching (.+?)","errorType":"http","errorClass":"SSRFFetchError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/ssrf.py","lineNumber":495,"sourceCode":"                    # normal default (True). Proxy-trust mode sets an explicit\n                    # proxy_url and turns trust_env off, so httpx2 has no environment\n                    # -based routing decision left to make — see validate_url() above\n                    # for why that matters.\n                    proxy=target.proxy_url,\n                    trust_env=target.proxy_url is None,\n                ) as client,\n                client.stream(\n                    \"GET\",\n                    target.url,\n                    headers=headers,\n                    extensions=extensions,\n                ) as response,\n            ):\n                if time.monotonic() - start_time > overall_timeout:\n                    raise SSRFFetchError(f\"Overall timeout exceeded: {url}\")\n\n                if response.status_code not in expected_statuses:\n                    raise SSRFFetchError(f\"HTTP {response.status_code} fetching {url}\")\n\n                # Check Content-Length header first if available\n                content_length = response.headers.get(\"content-length\")\n                if content_length:\n                    try:\n                        size = int(content_length)\n                        if size > max_size:\n                            raise SSRFFetchError(\n                                f\"Response too large: {size} bytes (max {max_size})\"\n                            )\n                    except ValueError:\n                        pass\n\n                # Stream the response and enforce size limit during download\n                chunks = []\n                total = 0\n                async for chunk in response.aiter_bytes():\n                    if time.monotonic() - start_time > overall_timeout:","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/ssrf.py#L477-L513","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the URL is the final, canonical resource location (no redirect chain).","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.","Pass required credentials via request_headers (note: Host header is stripped).","If fetching a CIMD client_id, confirm the metadata document exists at the issuer URL exactly.","Catch SSRFFetchError, inspect the str() for the status code, and surface a clear message to the user."],"exampleFix":"// before\nresp = await ssrf_safe_fetch_response(url)  # 404 -> SSRFFetchError\n// after\nresp = await ssrf_safe_fetch_response(url, allowed_status_codes={200, 404})\nif resp.status_code == 404:\n    return None  # metadata not published","handlingStrategy":"try-catch","validationCode":"# pre-check the endpoint out-of-band\ncode = (await client.head(url, follow_redirects=False)).status_code\nif code not in (200,):\n    raise RuntimeError(f\"endpoint returned {code}, not 200\")","typeGuard":null,"tryCatchPattern":"try:\n    resp = await ssrf_safe_fetch_response(url)\nexcept SSRFFetchError as e:\n    m = re.match(r\"HTTP (\\d+)\", str(e))\n    if m:\n        status = int(m.group(1))\n        # handle 404 / redirect specifically\n    else:\n        raise","preventionTips":["Use canonical, non-redirecting URLs for metadata endpoints","Pass allowed_status_codes only when you explicitly handle alternates","Send required auth via request_headers (Host is stripped)","Test the exact URL with curl -I before wiring it in"],"tags":["network","http","ssrf","status-code"],"backgroundTag":"http-error-status","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}