Fix Cursor OAuth callback handoff hanging on "waiting for callback" - #322
Fix Cursor OAuth callback handoff hanging on "waiting for callback"#322PuneetPunamiya wants to merge 1 commit into
Conversation
3205d07 to
4034c8a
Compare
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAuthentication redirect and callback pages now use ChangesAuthentication redirect rendering
Estimated code review effort: 3 (Moderate) | ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/auth/pages.go`:
- Around line 61-84: Restrict redirect URI validation in the registration flow
before clients are stored, using the existing validation symbols such as
isValidRedirectURI and HandleRegister. Accept only cursor:// URIs or approved
loopback callback URIs, and reject ftp://, custom://, and all other schemes so
values later passed through writeRedirectPage and writeCallbackPage cannot reach
template.URL or window.location.replace.
In `@pkg/auth/server.go`:
- Around line 232-235: Update the authentication success log in the server
handler to redact pending.RedirectURI before passing it as client_redirect.
Parse the URI and log only its scheme and host, or sanitize it by removing user
info, query, and fragment, while preserving the existing authentication flow and
safePrefix handling for client ID.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Enterprise
Run ID: 367535dc-ea8c-4696-a71f-705eae6411d1
📒 Files selected for processing (2)
pkg/auth/pages.gopkg/auth/server.go
| type redirectPageData struct { | ||
| Message string | ||
| // URL is typed template.URL (rather than string) because html/template | ||
| // otherwise sanitizes href values against a scheme allowlist that does | ||
| // NOT include custom schemes like cursor:// — an untyped string would | ||
| // silently render as the inert placeholder "#ZgotmplZ" instead of the | ||
| // real link. Callers must only pass already-validated redirect URIs | ||
| // (see isValidRedirectURI / HandleRegister's scheme checks). | ||
| URL template.URL | ||
| URLJS template.JS | ||
| } | ||
|
|
||
| func writeRedirectPage(w http.ResponseWriter, targetURL, message string) { | ||
| writeHTMLTemplate(w, redirectPageTmpl, redirectPageData{ | ||
| Message: message, | ||
| URL: template.URL(targetURL), //nolint:gosec // targetURL is a validated redirect_uri, not user-controlled HTML. | ||
| URLJS: jsString(targetURL), | ||
| }) | ||
| } | ||
|
|
||
| func writeCallbackPage(w http.ResponseWriter, redirectURL string) { | ||
| writeHTMLTemplate(w, callbackPageTmpl, redirectPageData{ | ||
| URL: template.URL(redirectURL), //nolint:gosec // redirectURL is a validated redirect_uri, not user-controlled HTML. | ||
| URLJS: jsString(redirectURL), |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map authentication validation and handler symbols before inspecting them.
ast-grep outline pkg/auth --items all --type function
# Trace redirect URI validation, registration, storage, and tests.
rg -n -C 10 'isValidRedirectURI|HandleRegister|RedirectURI|redirect_uri|javascript:|cursor://|localhost:8787' pkg/authRepository: redhat-data-and-ai/unstructured-data-controller
Length of output: 22908
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- pkg/auth/server.go ---'
cat -n pkg/auth/server.go | sed -n '1,245p'
printf '%s\n' '--- pkg/auth/pages.go ---'
cat -n pkg/auth/pages.go | sed -n '1,125p'
printf '%s\n' '--- redirect-page call sites ---'
rg -n -C 8 'writeRedirectPage|writeCallbackPage|StoreCompletion|ConsumeCompletion|StorePending' pkg/auth
printf '%s\n' '--- scheme definitions and related tests ---'
rg -n -C 8 'dangerousSchemes|redirect.*URI|RedirectURIs|HandleRegister|HandleComplete' --glob '*.go' .Repository: redhat-data-and-ai/unstructured-data-controller
Length of output: 50403
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from urllib.parse import urlsplit
dangerous = {"javascript", "data", "vbscript", "file"}
cases = [
"cursor://callback",
"http://localhost:8787/callback",
"https://example.com/callback",
"javascript:alert(1)",
"data:text/html,<script>alert(1)</script>",
"vbscript:msgbox(1)",
"file:///tmp/callback",
"ftp://example.com/callback",
"custom://callback",
]
def accepted(uri):
try:
u = urlsplit(uri)
except ValueError:
return False
if not u.scheme or not u.netloc or u.scheme in dangerous:
return False
hostname = u.hostname
is_localhost = hostname in {"localhost", "127.0.0.1", "[::1]"}
if u.scheme == "http" and not is_localhost:
return False
return True
for uri in cases:
print(f"{uri!r}: {accepted(uri)}")
PYRepository: redhat-data-and-ai/unstructured-data-controller
Length of output: 485
Restrict redirect URI schemes before storing clients.
HandleAuthorize stores only registered URIs, and registration rejects javascript, data, blob, and vbscript. However, registration still accepts arbitrary schemes such as ftp:// and custom://. template.URL then bypasses scheme filtering, and HandleComplete sends these URIs to both href and window.location.replace. Allow only cursor:// and loopback callback URIs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pkg/auth/pages.go` around lines 61 - 84, Restrict redirect URI validation in
the registration flow before clients are stored, using the existing validation
symbols such as isValidRedirectURI and HandleRegister. Accept only cursor://
URIs or approved loopback callback URIs, and reject ftp://, custom://, and all
other schemes so values later passed through writeRedirectPage and
writeCallbackPage cannot reach template.URL or window.location.replace.
| s.logger.Info("authentication successful, redirecting to client", | ||
| "client_id", safePrefix(pending.ClientID), | ||
| "client_redirect", pending.RedirectURI, | ||
| ) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Redact the redirect URI in the Info log.
pending.RedirectURI can contain static query parameters, user identifiers, or tokens. Line 234 stores all of them in routine logs. Log the parsed scheme and host, or a URI with user info, query, and fragment removed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pkg/auth/server.go` around lines 232 - 235, Update the authentication success
log in the server handler to redact pending.RedirectURI before passing it as
client_redirect. Parse the URI and log only its scheme and host, or sanitize it
by removing user info, query, and fragment, while preserving the existing
authentication flow and safePrefix handling for client ID.
The post-login redirect page used a meta-refresh to hand the browser off to the MCP client's redirect_uri (Cursor's cursor:// deep link or its localhost:8787 loopback). Meta-refresh is unreliable for custom URL schemes, so the handoff would silently fail and Cursor would sit on "waiting for callback" even though the server-side SSO login had already succeeded. Replace it with an html/template-rendered page that does a JS redirect plus a visible fallback link, with the target URL typed as template.URL (a plain string field would get replaced with html/template's inert "#ZgotmplZ" placeholder for the cursor:// scheme, since it isn't on the default safe-scheme allowlist) and safely JSON-quoted for the inline <script> context. Also log the client's redirect_uri when authentication completes, so a future stuck handoff can be diagnosed from server logs alone instead of guessing which redirect mechanism the client requested. Co-authored-by: Cursor <cursoragent@cursor.com>
4034c8a to
6588688
Compare
Summary
redirect_uri(Cursor'scursor://deep link or itslocalhost:8787loopback). Meta-refresh is unreliable for custom URL schemes, so the handoff would silently fail and Cursor would sit on "waiting for callback" even though server-side SSO login had already succeeded.html/template-rendered page that does a JS redirect plus a visible fallback link. The target URL is typed astemplate.URL— a plain string field gets silently replaced with html/template's inert#ZgotmplZplaceholder for thecursor://scheme, since it isn't on the default safe-scheme allowlist — and is safely JSON-quoted for the inline<script>context.redirect_uriwhen authentication completes, so a future stuck handoff can be diagnosed from server logs alone.Test plan
go build ./...go test ./pkg/auth/...golangci-lint run— 0 issueslocalserver entry, confirmed Cursor no longer hangs on "waiting for callback" and connects successfully.