PrefectHQ/fastmcp · error · CIMDValidationError

CIMD jwks_uri failed SSRF validation: {e}

Error message

CIMD jwks_uri failed SSRF validation: {e}

What it means

When the CIMD document declares a jwks_uri, fetch() applies the same SSRF validation to that JWKS URL as to the client_id URL. If validate_url raises SSRFError (private IP, blocked scheme, DNS pinning failure), the error is wrapped in CIMDValidationError so a hostile document cannot make the server fetch internal endpoints.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/cimd.py:429

        try:
            doc = CIMDDocument.model_validate(data)
        except Exception as e:
            raise CIMDValidationError(f"Invalid CIMD document: {e}") from e

        if str(doc.client_id).rstrip("/") != client_id_url.rstrip("/"):
            raise CIMDValidationError(
                f"CIMD client_id mismatch: document says '{doc.client_id}' "
                f"but was fetched from '{client_id_url}'"
            )

        # Validate jwks_uri if present (SSRF check for JWKS endpoint)
        if doc.jwks_uri:
            jwks_uri_str = str(doc.jwks_uri)
            try:
                await validate_url(jwks_uri_str)
            except SSRFError as e:
                raise CIMDValidationError(
                    f"CIMD jwks_uri failed SSRF validation: {e}"
                ) from e

        logger.info(
            "CIMD document fetched and validated: %s (client_name=%s)",
            client_id_url,
            doc.client_name,
        )

        if not policy.no_store:
            self._store_cache_entry(
                client_id_url,
                _CIMDCacheEntry(
                    doc=doc,
                    etag=policy.etag,
                    last_modified=policy.last_modified,
                    expires_at=policy.expires_at,
                    freshness_lifetime=policy.freshness_lifetime,

View on GitHub (pinned to 1f02114297)

Solutions

  1. Host the JWKS at a public HTTPS URL and update jwks_uri in the CIMD document
  2. Use inline 'jwks' (a JWK Set object) in the document instead of jwks_uri if the keys are small
  3. If legitimately internal, configure the SSRF allowlist in fastmcp.server.auth.ssrf to permit that endpoint
  4. Treat repeated occurrences as an attack signal; the validation exists to block SSRF via client-supplied URLs

Example fix

// before
"jwks_uri": "http://localhost:8080/jwks.json"
// after
"jwks_uri": "https://keys.app.example.com/jwks.json"
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse
def jwks_uri_safe(u: str) -> bool:
    p = urlparse(u)
    host = p.hostname or ""
    return p.scheme == "https" and not (
        host in ("localhost", "127.0.0.1", "169.254.169.254")
        or host.startswith("10.") or host.startswith("192.168.")
        or host.startswith("172.16.")
    )

Try / catch

try:
    doc = await fetcher.get_client(client_id)
except CIMDValidationError as e:
    if "jwks_uri failed SSRF" in str(e):
        raise InvalidClientError("jwks_uri must be a public HTTPS URL") from e
    raise

Prevention

When it happens

Trigger: A fetched CIMD document contains jwks_uri pointing at localhost, 127.0.0.1, RFC1918 addresses, metadata services (169.254.169.254), or a non-HTTP scheme; detected during fetch/get_client.

Common situations: Malicious or misconfigured client documents attempting SSRF via jwks_uri; internally-hosted JWKS endpoints that worked in dev but are blocked in production; documents authored before SSRF protections were added.

Related errors


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