Skip to content

CIMD client metadata fetch fails (403) for client_id hosts behind SNI-routing CDNs (e.g. Cloudflare) #837

Description

@jenwitteng

Summary

Authorizing with a CIMD client_id whose host is fronted by a CDN that routes by TLS SNI (e.g. Cloudflare) fails with:

{"error":"invalid_request","error_description":"Unable to fetch client metadata"}

This reproduces for client_id=https://claude.ai/oauth/claude-code-client-metadata, used by Claude Code / Claude Desktop, when authorizing against a self-hosted tableau-mcp server.

Root cause

In getClientFromMetadataDoc() (src/server/oauth/authorize.ts):

  1. The hostname of the client metadata URL is DNS-resolved to a raw IP address (DNS-rebinding SSRF mitigation).
  2. The URL's hostname is rewritten to that IP.
  3. isSSRFSafeURL validates the IP-based URL.
  4. axios.get() requests the IP-based URL, overriding only the Host header to the original hostname.

Step 4 connects to the raw IP over HTTPS. Node/axios derive the TLS SNI from the request URL's host — which is now the IP, not the original hostname. CDNs like Cloudflare route and select which certificate to present based on SNI, not the Host header. Since the SNI is an unrecognized IP, Cloudflare's edge rejects the request (403), which the server logs as e.g.:

Failed to fetch client metadata from https://claude.ai/oauth/claude-code-client-metadata
AxiosError: Request failed with status code 403
url: 'https://<resolved-ip>/oauth/claude-code-client-metadata'

...and surfaces to the OAuth caller as the generic Unable to fetch client metadata, indistinguishable from a real network/DNS failure.

This affects any CIMD client whose client_id domain sits behind an SNI-routing CDN — not specific to claude.ai — so it's likely to recur for other MCP clients adopting CIMD (a newer part of the MCP authorization spec).

Related: #193 (proposed disabling CIMD entirely as a workaround; closed without a fix for CIMD-enabled deployments).

Fix

Opened as # — pins the outbound TLS SNI (via https.Agent({ servername })) to the original hostname while still connecting to the pre-resolved IP, so the existing SSRF protection is unaffected but CDN-fronted client_id hosts resolve correctly.

Environment

  • tableau-mcp: self-hosted, OAuth/CIMD enabled
  • Reported via an enterprise deployment behind a corporate proxy; confirmed via manual axios.get() against the raw resolved IP that the 403 is independent of proxy config — it reproduces identically from outside that network.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions