{"record":{"id":"61b5d3b722f2046b","repo":"PrefectHQ/fastmcp","slug":"str-e-from-ssrferror-ssrffetcherror","errorCode":null,"errorMessage":"{str(e) from SSRFError/SSRFFetchError}","messagePattern":"\\{str\\(e\\) from SSRFError/SSRFFetchError\\}","errorType":"exception","errorClass":"CIMDValidationError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/cimd.py","lineNumber":363,"sourceCode":"            if cached.etag:\n                request_headers[\"If-None-Match\"] = cached.etag\n            if cached.last_modified:\n                request_headers[\"If-Modified-Since\"] = cached.last_modified\n            if request_headers:\n                allowed_status_codes = {200, 304}\n\n        try:\n            response = await ssrf_safe_fetch_response(\n                client_id_url,\n                require_path=True,\n                max_size=self.MAX_RESPONSE_SIZE,\n                timeout=self.timeout,\n                overall_timeout=30.0,\n                request_headers=request_headers,\n                allowed_status_codes=allowed_status_codes,\n            )\n        except SSRFError as e:\n            raise CIMDValidationError(str(e)) from e\n        except SSRFFetchError as e:\n            raise CIMDFetchError(str(e)) from e\n\n        if response.status_code == 304:\n            if cached is None:\n                raise CIMDFetchError(\n                    \"CIMD server returned 304 Not Modified without cached document\"\n                )\n\n            now = time.time()\n            if self._has_freshness_headers(response.headers):\n                policy = self._parse_cache_policy(response.headers, now)\n            else:\n                # RFC allows 304 to omit unchanged headers. Preserve existing\n                # cache policy rather than resetting to fallback defaults.\n                policy = _CIMDCachePolicy(\n                    etag=None,\n                    last_modified=None,","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/cimd.py#L345-L381","documentation":"When fetching a CIMD document, the SSRF-safe HTTP layer (ssrf_safe_fetch_response) can raise SSRFError for requests that violate SSRF protections (private/internal IPs, disallowed schemes, DNS pinning failures, oversized/timeout responses). CIMDFetcher.fetch() converts those into CIMDValidationError so callers get a single CIMD-specific error type.","triggerScenarios":"CIMDFetcher.fetch(client_id_url) is called (directly, via get_client, or by token flows using the client_id as a URL) and validate_url/ssrf_safe_fetch_response rejects the URL — e.g. client_id points at localhost, a private RFC1918 address, a non-HTTP scheme, or DNS resolves to a blocked IP.","commonSituations":"A client_id that is not a public HTTPS URL; testing against local CIMD servers; DNS rebinding/internal-network hostnames in multi-tenant deployments; misconfigured metadata pointing at internal services.","solutions":["Ensure the client_id is a public HTTPS URL reachable from the server without crossing SSRF protections","If testing locally, run behind an allowed host or adjust SSRF allowlist configuration in ssrf.py","Catch CIMDValidationError in your client-management flow and return a 400-style invalid_client response","Inspect the wrapped SSRFError message for the specific violated rule (blocked IP, scheme, size, timeout)"],"exampleFix":"try:\n    doc = await fetcher.get_client(client_id)\nexcept CIMDValidationError as e:\n    return JSONResponse(status_code=400, content={\"error\": str(e)})","handlingStrategy":"try-catch","validationCode":"from urllib.parse import urlparse\ndef is_public_https(url: str) -> bool:\n    p = urlparse(url)\n    return p.scheme in (\"http\", \"https\") and not (\n        p.hostname in (\"localhost\",) or p.hostname and (\n            p.hostname.startswith(\"10.\") or p.hostname.startswith(\"192.168.\")\n        )\n    )","typeGuard":"def is_http_url(u: object) -> bool:\n    if not isinstance(u, str):\n        return False\n    p = urlparse(u)\n    return p.scheme in (\"http\", \"https\") and bool(p.netloc)","tryCatchPattern":"try:\n    doc = await fetcher.get_client(client_id)\nexcept CIMDValidationError as e:\n    return JSONResponse(400, {\"error\": \"invalid_client\", \"detail\": str(e)})","preventionTips":["Use public HTTPS URLs for client_id metadata","Don't point metadata at localhost/private IPs in production","Log the wrapped SSRF reason to distinguish policy blocks from bugs"],"tags":["oauth","cimd","ssrf","network","security"],"backgroundTag":"ssrf-blocked-url","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}