fix(mcp-oauth): allowlist Cursor's OAuth callbacks so its dynamic registration succeeds (#2225)
* fix(mcp-oauth): allowlist Cursor's OAuth callbacks so its dynamic registration succeeds Cursor (IDE, CLI, and Grok Bot on top of it) registers three redirect URIs in one /register request: cursor://anysphere.cursor-mcp/oauth/callback, https://www.cursor.com/agents/mcp/oauth/callback and http://localhost:8787/callback. Only the loopback matched a built-in pattern and /register fails the whole set on any unknown URI, so every Cursor connection to the URL we hand out in Settings died with "Redirect URI not allowed". Users cannot self-register the cursor:// form either (the settings panel requires https). Add a built-in `cursor` provider with the two non-loopback callbacks as exact matches (no cursor.com prefix), name it "Cursor (Anysphere)" on the consent page, list the pre-approved clients in the OAuth clients settings text (sv + en) and the mcp-server rules, and cover the register, allowlist and consent paths with tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBFeTvQXgMCNXR6fG9drff * fix(mcp-oauth): show the cursor:// deeplink unverified and let CSP pass its post-consent redirect Review findings on #2225, one pass: - Skeptic (correctness), REFUTED: new URL('cursor://...').origin is the string "null", so the consent page emitted form-action 'self' null and Chromium would block the 303 to the deeplink after Allow. The header now uses the scheme-source (cursor:) when the origin is opaque; a test pins the header on the cursor:// URI. - Skeptic (security), CodeRabbit (Major) and Superagent (P2): a custom scheme can be claimed by any local app (RFC 8252 section 8.4), so it must not be presented as a vendor-verified callback. The deeplink is its own provider, cursor_deeplink, rendered "Cursor (Anysphere)" with the localhost tag "Din egen dator" and verified: false. The https cursor.com callback keeps the verified label. A test pins that a code minted without a code_challenge can never be exchanged, which is what keeps a scheme hijack from turning into a token. - CodeRabbit (Minor): the rules doc now says the Grok callback matches with or without the trailing slash. - Regression skeptic: docs/WHITELABEL.md listed only Claude and localhost and pointed at the wrong file; now lists the built-ins and points at lib/auth/oauth-allowlist.ts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DBFeTvQXgMCNXR6fG9drff --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
d900fea1a8
commit
bc5da12372
+2
-2
@@ -2695,7 +2695,7 @@
|
||||
},
|
||||
"settings_oauth_clients": {
|
||||
"title": "OAuth clients",
|
||||
"description": "Register redirect URIs for self-built MCP clients. Claude.ai and localhost are already allowed by default: only register here if you're building your own app.",
|
||||
"description": "Register redirect URIs for self-built MCP clients. Claude, ChatGPT, Grok, Cursor and localhost are already allowed by default: only register here if you're building your own app.",
|
||||
"register_uri": "Register URI",
|
||||
"default_client_name": "OAuth client",
|
||||
"toast_fetch_failed": "Could not load OAuth clients",
|
||||
@@ -2710,7 +2710,7 @@
|
||||
"registered_on": "Registered",
|
||||
"revoke_aria": "Revoke {name}",
|
||||
"register_dialog_title": "Register redirect URI",
|
||||
"register_dialog_description": "Only for self-built MCP clients with a public HTTPS callback. Do <bold>not</bold> add <code>localhost</code> or <code>claude.ai</code>: they already work without registration. The URI is compared verbatim against the redirect_uri parameter in the OAuth flow.",
|
||||
"register_dialog_description": "Only for self-built MCP clients with a public HTTPS callback. Do <bold>not</bold> add <code>localhost</code> or the Claude, ChatGPT, Grok or Cursor callbacks: they already work without registration. The URI is compared verbatim against the redirect_uri parameter in the OAuth flow.",
|
||||
"client_name_label": "Client name",
|
||||
"client_name_placeholder": "e.g. My bookkeeping agent",
|
||||
"redirect_uri_label": "Redirect URI",
|
||||
|
||||
+2
-2
@@ -2695,7 +2695,7 @@
|
||||
},
|
||||
"settings_oauth_clients": {
|
||||
"title": "OAuth-klienter",
|
||||
"description": "Registrera redirect-URI:er för egenutvecklade MCP-klienter. Claude.ai och localhost är redan godkända som standard: registrera bara här om du bygger en egen app.",
|
||||
"description": "Registrera redirect-URI:er för egenutvecklade MCP-klienter. Claude, ChatGPT, Grok, Cursor och localhost är redan godkända som standard: registrera bara här om du bygger en egen app.",
|
||||
"register_uri": "Registrera URI",
|
||||
"default_client_name": "OAuth-klient",
|
||||
"toast_fetch_failed": "Kunde inte hämta OAuth-klienter",
|
||||
@@ -2710,7 +2710,7 @@
|
||||
"registered_on": "Registrerad",
|
||||
"revoke_aria": "Återkalla {name}",
|
||||
"register_dialog_title": "Registrera redirect URI",
|
||||
"register_dialog_description": "Bara för egenbyggda MCP-klienter med en publik HTTPS-callback. Lägg <bold>inte</bold> till <code>localhost</code> eller <code>claude.ai</code>: de fungerar redan utan registrering. URI:n jämförs ord-för-ord mot redirect_uri-parametern i OAuth-flödet.",
|
||||
"register_dialog_description": "Bara för egenbyggda MCP-klienter med en publik HTTPS-callback. Lägg <bold>inte</bold> till <code>localhost</code> eller callbacks för Claude, ChatGPT, Grok eller Cursor: de fungerar redan utan registrering. URI:n jämförs ord-för-ord mot redirect_uri-parametern i OAuth-flödet.",
|
||||
"client_name_label": "Klientnamn",
|
||||
"client_name_placeholder": "t.ex. Min bokföringsagent",
|
||||
"redirect_uri_label": "Redirect URI",
|
||||
|
||||
Reference in New Issue
Block a user