feat: wildcard redirect-allowlist support; enable ChatGPT connector#46
Conversation
ChatGPT's connector callback is per-connector (https://chatgpt.com/connector/oauth/<id>), so an exact-match allowlist breaks whenever a connector is recreated. Support a trailing "*" in OAUTH_ALLOWED_REDIRECT_URIS entries as a scheme+host-locked prefix match, and add https://chatgpt.com/connector/oauth/* to the default allowlist so ChatGPT works out of the box. Exact matches are unchanged. Add tests covering wildcard acceptance and host-locking, and document ChatGPT setup + the wildcard syntax. https://claude.ai/code/session_01U3EtN3puoZRq2t7nedcnHY
There was a problem hiding this comment.
Pull request overview
This PR updates the OAuth Dynamic Client Registration (DCR) redirect-URI allowlist to support wildcard prefix entries (to handle ChatGPT’s per-connector callback URLs), adds ChatGPT’s callback prefix to the default allowlist, and documents the new configuration and setup.
Changes:
- Add wildcard
*suffix support forOAUTH_ALLOWED_REDIRECT_URISentries (prefix matching). - Extend the default redirect allowlist with
https://chatgpt.com/connector/oauth/*. - Add tests for wildcard acceptance + host-locking, and document ChatGPT connector setup and wildcard syntax.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
app/oauth.py |
Implements wildcard-aware redirect URI allowlist checks during DCR registration. |
app/config.py |
Adds ChatGPT wildcard callback prefix to the default redirect allowlist. |
tests/test_oauth.py |
Adds tests verifying wildcard prefix acceptance and rejecting lookalike hosts. |
docs/USER_GUIDE.md |
Documents ChatGPT OAuth connector setup and wildcard allowlist syntax. |
.env.example |
Updates example configuration to include wildcard syntax and ChatGPT default entry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _redirect_uri_allowed(uri: str, allowed: list[str]) -> bool: | ||
| # Exact match, or prefix match for an allowlist entry ending in "*". The | ||
| # wildcard only extends the path under a fixed scheme+host the operator | ||
| # configured (e.g. ChatGPT's per-connector https://chatgpt.com/connector/oauth/*), | ||
| # so it can't redirect codes to a different host. | ||
| for entry in allowed: | ||
| if entry.endswith("*"): | ||
| if uri.startswith(entry[:-1]): | ||
| return True | ||
| elif uri == entry: | ||
| return True | ||
| return False |
There was a problem hiding this comment.
Good catch — fixed in a733085. _redirect_uri_allowed now parses both the pattern and candidate with urllib.parse.urlsplit, requires an exact scheme + netloc match, and prefix-matches only on path. Over-broad patterns lacking a concrete host or path (*, https://*, https://chatgpt.com*) are ignored rather than honored, so a misconfigured entry can no longer match chatgpt.com.evil.com. Added a unit test covering these cases.
Generated by Claude Code
| | `PUBLIC_BASE_URL` | yes | — | Public URL, e.g. `https://mem0.your-domain.com`. Used in OAuth metadata. | | ||
| | `OAUTH_SIGNING_KEY` | no | empty | PEM RSA private key. **Setting this enables Phase 2 OAuth.** Leave blank for Phase 1. | | ||
| | `OAUTH_ALLOWED_REDIRECT_URIS` | no | claude.ai + cowork callbacks | Comma-separated allowlist for OAuth redirect URIs. | | ||
| | `OAUTH_ALLOWED_REDIRECT_URIS` | no | claude.ai + cowork + chatgpt callbacks | Comma-separated allowlist for OAuth redirect URIs. An entry ending in `*` is a prefix match (e.g. `https://chatgpt.com/connector/oauth/*`, since ChatGPT uses a per-connector callback path). | |
| connector you create. The default allowlist already covers these via the prefix entry | ||
| `https://chatgpt.com/connector/oauth/*`, so you don't need to add the exact URL. If you've | ||
| customized `OAUTH_ALLOWED_REDIRECT_URIS`, include that wildcard entry. |
Address review: the previous wildcard used a raw startswith, so the "host-locked" claim wasn't actually enforced -- a misconfigured entry like https://chatgpt.com* would match lookalike hosts (chatgpt.com.evil.com). Parse both the pattern and candidate with urlsplit, require an exact scheme+netloc match, and prefix-match only on path. Over-broad patterns lacking a concrete host or path (*, https://*, https://chatgpt.com*) are ignored rather than honored. Add a unit test for these cases and clarify the docs on the required scheme://host/path/ wildcard format. https://claude.ai/code/session_01U3EtN3puoZRq2t7nedcnHY
Summary
Enables ChatGPT (Developer Mode custom connectors) to connect via OAuth, and makes the redirect allowlist robust to per-connector callbacks.
Background
ChatGPT's OAuth callback isn't a fixed URL — the diagnostic logging from #43 revealed it sends a per-connector redirect URI:
The
<id>segment is unique to each connector, so an exact-match allowlist breaks whenever a connector is deleted/recreated.Change
OAUTH_ALLOWED_REDIRECT_URIS: an entry ending in*matches any redirect URI with that prefix. The wildcard only extends the path under a fixed scheme+host the operator configured, so it can't redirect authorization codes to a different host (verified by a host-locking test).https://chatgpt.com/connector/oauth/*, so ChatGPT works out of the box alongside the existing claude.ai/Cowork entries.Tests
test_dcr_allows_wildcard_prefix— a ChatGPT per-connector callback registers (201).test_dcr_wildcard_is_host_locked—evil.com/...andchatgpt.com.evil.com/...are still rejected (400).ruffclean.Docs
.env.exampledocument the trailing-*prefix syntax and the ChatGPT default.https://claude.ai/code/session_01U3EtN3puoZRq2t7nedcnHY
Generated by Claude Code