Skip to content

Fix Cursor OAuth callback handoff hanging on "waiting for callback" - #322

Open
PuneetPunamiya wants to merge 1 commit into
redhat-data-and-ai:mainfrom
PuneetPunamiya:fix-cursor-callback
Open

Fix Cursor OAuth callback handoff hanging on "waiting for callback"#322
PuneetPunamiya wants to merge 1 commit into
redhat-data-and-ai:mainfrom
PuneetPunamiya:fix-cursor-callback

Conversation

@PuneetPunamiya

@PuneetPunamiya PuneetPunamiya commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 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 server-side SSO login had already succeeded.
  • Replaced it with an html/template-rendered page that does a JS redirect plus a visible fallback link. The target URL is typed as template.URL — a plain string field gets silently replaced with html/template's inert #ZgotmplZ placeholder for the cursor:// scheme, since it isn't on the default safe-scheme allowlist — and is safely JSON-quoted for the inline <script> context.
  • Logged the client's redirect_uri when 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 issues
  • Manually verified end-to-end locally: ran the MCP server, authenticated via Cursor against a local server entry, confirmed Cursor no longer hangs on "waiting for callback" and connects successfully.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 20db58c0-df08-4be6-a7c0-d1ab1c67fd65

📥 Commits

Reviewing files that changed from the base of the PR and between 4034c8a and 6588688.

📒 Files selected for processing (1)
  • pkg/auth/pages.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/auth/pages.go

📝 Walkthrough

Summary by CodeRabbit

  • Enhancements
    • Improved authentication redirect and callback pages with clickable fallback links.
    • Added JavaScript-based navigation for smoother, more reliable redirects.
    • Improved support for custom redirect schemes.
    • Enhanced authentication success logging with redirect destination details.
  • Documentation
    • Updated authentication completion guidance to reflect the JavaScript-based redirect experience.

Walkthrough

Authentication redirect and callback pages now use html/template with typed redirect data, clickable fallback links, and JavaScript navigation. Template failures return HTTP 500 responses. Successful authentication logs include the client redirect URI.

Changes

Authentication redirect rendering

Layer / File(s) Summary
Redirect page templates and data
pkg/auth/pages.go
Redirect and callback pages use named HTML templates, typed redirect data, clickable fallback links, JavaScript navigation, and JSON-quoted URLs. Template rendering failures log the error and return HTTP 500 responses.
Authentication completion logging
pkg/auth/server.go
Successful authentication logs include client_redirect. HandleComplete documentation describes JavaScript-based redirection.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Cursor OAuth callback handoff issue fixed by the changes.
Description check ✅ Passed The description accurately explains the JavaScript redirect, fallback link, safe URL handling, logging, and verification steps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5a86b26 and 4034c8a.

📒 Files selected for processing (2)
  • pkg/auth/pages.go
  • pkg/auth/server.go

Comment thread pkg/auth/pages.go
Comment on lines +61 to +84
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),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 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/auth

Repository: 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)}")
PY

Repository: 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.

Comment thread pkg/auth/server.go
Comment on lines +232 to +235
s.logger.Info("authentication successful, redirecting to client",
"client_id", safePrefix(pending.ClientID),
"client_redirect", pending.RedirectURI,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 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>
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