feat(oauth): dynamic OAuth client registration via Connection [AI-2883]#453
Draft
feat(oauth): dynamic OAuth client registration via Connection [AI-2883]#453
Conversation
Replace the hardcoded domain whitelist in SimpleOAuthProvider with proper client validation against Connection's /oauth/clients/validate endpoint. Flow A (pre-registered clients): validate redirect_uri against Connection's registered URI list instead of a static whitelist. Flow B (dynamic registration): unknown clients are flagged as 'pending'; their info is forwarded to Connection as URL params so Connection can show an approval screen during the OAuth login flow. Security changes: - Remove _ALLOWED_DOMAINS / _RE_LOCALHOST whitelist constants - validate_redirect_uri() now only rejects dangerous scripting schemes (javascript, data, vbscript); per-client domain validation is delegated to Connection - _validate_client() fails open on Connection errors during migration Refs: RISK-76 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines
+277
to
+284
| except Exception: | ||
| # Fail open during the migration period to avoid breaking existing clients when Connection is | ||
| # temporarily unreachable. Switch to fail-closed once all known clients are pre-registered. | ||
| LOG.warning( | ||
| '[_validate_client] Failed to reach Connection; treating client as approved (fail-open)', | ||
| exc_info=True, | ||
| ) | ||
| return ClientValidationResult(status='approved') |
Contributor
There was a problem hiding this comment.
This looks unsafe to me because it returns approved with no redirect_uris and then authorize() will accept any redirect uri. I think we should fail in that case or fallback to the previous whitelist during the migration time otherwise all clients and all redirects are trusted in failure.
Comment on lines
+197
to
+198
| # Flow A: for pre-registered clients, validate the redirect_uri against Connection's registered list. | ||
| if validation.redirect_uris and redirect_uri_str not in validation.redirect_uris: |
Contributor
There was a problem hiding this comment.
This validates the redirect uri only when redirect_uris are not empty. What if the Connection returns an empty list?
Comment on lines
+291
to
+296
| else: | ||
| LOG.warning( | ||
| f'[_validate_client] Unexpected response from Connection (fail-open): ' | ||
| f'client_id={client_id}, status={response.status_code}, text={response.text}' | ||
| ) | ||
| return ClientValidationResult(status='approved') |
Contributor
There was a problem hiding this comment.
This is also risky returning approved with emtpy list of redirect uris as it pass the authorize(). I think we should fail closed in this case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Linear: AI-2883 | Relates to: RISK-76
Change Type
Summary
Addresses the security issue RISK-76 where the MCP server bypassed Connection's OAuth client registry entirely — accepting any
client_idwithout validation and using a hardcoded domain whitelist for redirect URI validation.What changed:
_ALLOWED_DOMAINS/_RE_LOCALHOSTwhitelist constants_validate_client()method that calls Connection'sPOST /oauth/clients/validateendpoint before authorizing any clientauthorize()now enforces two flows:pendingstatus; theirclient_id,redirect_uri, andclient_nameare forwarded as URL params to Connection, which shows an approval screen during the OAuth login flowvalidate_redirect_uri()is now minimal — only rejects dangerous scripting schemes (javascript,data,vbscript); per-client domain validation is fully delegated to Connectionapproved) when Connection is unreachable, to avoid breaking existing clients during migration rolloutConnection side (prerequisite — must be deployed first):
POST /oauth/clients/validateendpointphp bin/console league:oauth2-server:create-clientTesting
Streamable-HTTPtransports)Optional testing
canary-orionMCP (SSEandStreamable-HTTP)canary-orioncanary-orionChecklist