-
Notifications
You must be signed in to change notification settings - Fork 0
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-scoperead/writemodel; only the scope names below are historical. See OAuth Clients and Scopes for the current model.
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.
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.writehttps://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.
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.
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.
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 | 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
writetoken merely by registering with a redirect URI that textually matched a privileged pre-registered client's callback (e.g.claude-admin's orchatgpt-write's) — because/authorizeis a plain HTTP endpoint and a caller invoking it directly reads the redirectLocationheader itself, regardless of whether it actually controls that domain. Fixed in #505: every DCR-created client now always getsread, regardless of redirect URI overlap with a privileged client.writeis 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 returnsscope: "read", notwrite.
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.
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.
- Pitfall-Redirect-URI-Exact-Matching — URI format rules and wildcard support
- OAuth-Clients-and-Scopes — full client registry and scope hierarchy
- Pitfall-Claude-auth_callback-vs-oauth_callback — Claude.ai-specific redirect URI variants