Skip to content

feat(oauth): dynamic OAuth client registration via Connection [AI-2883]#453

Draft
Matovidlo wants to merge 1 commit intomainfrom
AI-2883-dynamic-oauth-client-registration
Draft

feat(oauth): dynamic OAuth client registration via Connection [AI-2883]#453
Matovidlo wants to merge 1 commit intomainfrom
AI-2883-dynamic-oauth-client-registration

Conversation

@Matovidlo
Copy link
Copy Markdown
Contributor

Description

Linear: AI-2883 | Relates to: RISK-76

Change Type

  • Major (breaking changes, significant new features)
  • Minor (new features, enhancements, backward compatible)
  • Patch (bug fixes, small improvements, no new features)

Summary

Addresses the security issue RISK-76 where the MCP server bypassed Connection's OAuth client registry entirely — accepting any client_id without validation and using a hardcoded domain whitelist for redirect URI validation.

What changed:

  • Removed the hardcoded _ALLOWED_DOMAINS / _RE_LOCALHOST whitelist constants
  • Added _validate_client() method that calls Connection's POST /oauth/clients/validate endpoint before authorizing any client
  • authorize() now enforces two flows:
    • Flow A — Pre-registered clients (Claude.ai, ChatGPT, Make.com, Cursor, n8n, etc.): redirect URI is validated against Connection's registered URI list for that specific client
    • Flow B — Dynamic registration: unknown clients receive pending status; their client_id, redirect_uri, and client_name are forwarded as URL params to Connection, which shows an approval screen during the OAuth login flow
  • validate_redirect_uri() is now minimal — only rejects dangerous scripting schemes (javascript, data, vbscript); per-client domain validation is fully delegated to Connection
  • Fails open (treats as approved) when Connection is unreachable, to avoid breaking existing clients during migration rollout

Connection side (prerequisite — must be deployed first):

  • POST /oauth/clients/validate endpoint
  • Client approval screen in OAuth login flow
  • Pre-registered known clients via php bin/console league:oauth2-server:create-client

Testing

  • Tested with Cursor AI desktop (Streamable-HTTP transports)

Optional testing

  • Tested with Cursor AI desktop (all transports)
  • Tested with claude.ai web and canary-orion MCP (SSE and Streamable-HTTP)
  • Tested with In Platform Agent on canary-orion
  • Tested with RO chat on canary-orion

Checklist

  • Self-review completed
  • Unit tests added/updated (if applicable)
  • Integration tests added/updated (if applicable)
  • Project version bumped according to the change type (if applicable)
  • Documentation updated (if applicable)

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')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants