Skip to content

Security Proof Client Credential Isolation

Jm Rohmer edited this page Jul 18, 2026 · 3 revisions

Security Proof: Client Credential Isolation

Category: Security
Verified: 2026-07-05, live test on mcp.arleo.eu

Terminology note (#450, later): this proof uses the pre-#450 scope names (content.write, site.admin, content.read) that were canonical at verification time. The security property demonstrated — redirect URI isolation blocks cross-client credential reuse regardless of scope — is unaffected by the later collapse to the 2-scope read/write model; only the scope names below are historical. See OAuth Clients and Scopes for the current model.


What was tested

A user attempted to authenticate Claude.ai against the MCP server using the credentials of the chatgpt-write pre-registered client — a valid client ID and secret that exist in the server's client registry.

Result: The server refused the authorization with a redirect to an error page. No token was issued.


Why it was refused

The OAuth authorization endpoint validates the redirect_uri parameter against the URIs registered for the client being used.

When Claude.ai initiates the flow, it sends its own callback URI:

redirect_uri = https://claude.ai/api/mcp/auth_callback

The chatgpt-write client is registered with ChatGPT's callback URIs only:

# /etc/mcp-hugo-server-go/oauth-clients.yaml
- client_id: chatgpt-write
  redirect_uris:
    - https://chatgpt.com/aip/oauth/callback
    - https://chatgpt.com/connector/oauth/*
  scope: content.write

https://claude.ai/api/mcp/auth_callback does not match either of those patterns.
→ The server returns invalid_redirect_uri and no authorization code is issued.


What this proves

1. Credentials alone are not enough

Knowing a client's client_id and client_secret is insufficient to obtain a token. The authorization server also checks where the token will be delivered (the redirect URI). A stolen credential cannot be used from an attacker's domain.

2. Cross-client token theft is blocked

Attacker knows chatgpt-write secret
                ↓
Initiates flow from attacker.example.com
                ↓
Server: redirect_uri not in chatgpt-write allowed list
                ↓
Authorization refused — no code, no token

Even if an attacker has the client secret, they cannot redirect the authorization code to a domain they control unless that domain is explicitly registered.

3. Scope escalation via wrong client is blocked

chatgpt-write has content.write scope.
claude-admin has site.admin scope.

A client cannot claim another client's scope by using the other client's credentials — the redirect URI mismatch stops the flow before any scope is granted.


Client isolation matrix

Client Allowed redirect domains Scope
claude-admin claude.ai site.admin
chatgpt-write chatgpt.com content.write
Dynamic (DCR) Any (public, self-registered) always read — never inherited (#497, #505)

Correction (2026-07-18, #497/#505). The "inherits from matching pre-registered client" row above described the actual behavior at verification time, but that behavior was a P0 privilege-escalation vulnerability, not a safe design: a public DCR client (no secret, no proof of redirect-URI ownership) could obtain a real write token merely by registering with a redirect URI that textually matched a privileged pre-registered client's callback (e.g. claude-admin's or chatgpt-write's) — because /authorize is a plain HTTP endpoint and a caller invoking it directly reads the redirect Location header itself, regardless of whether it actually controls that domain. Fixed in #505: every DCR-created client now always gets read, regardless of redirect URI overlap with a privileged client. write is only obtainable by authenticating directly with a pre-registered client's own secret, which the redirect-URI-isolation property this page demonstrates still protects correctly. Verified live: registering with Claude.ai's exact redirect URI now returns scope: "read", not write.

A Claude.ai session can only ever receive tokens via claude.ai callbacks.
A ChatGPT session can only ever receive tokens via chatgpt.com callbacks.
Neither can impersonate the other, even with knowledge of the other's credentials.


Relevant RFC

RFC 6749 §4.1.2.1 — If the redirect URI does not match the registered value, the authorization server must not redirect and must inform the resource owner of the error.

If the request fails due to a missing, invalid, or mismatching redirection URI … the authorization server SHOULD inform the resource owner of the error and MUST NOT automatically redirect the user-agent to the invalid redirection URI.

This server implements that requirement: the user sees an error page, no redirect occurs, no authorization code is issued.


See also

Clone this wiki locally