fix(auth): loopback redirect URIs must match on any port (RFC 8252 §7.3) #8
Labels
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jlxq0/jmap-mcp#8
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
Claude Code CLI cannot complete Dynamic Client Registration:
Observed against
caldav-mcpand diagnosed there;src/oauth_redirect.rsis copy-pasted across all five Rust MCP servers, so this repo has the same defect.Cause
is_allowed_redirect_uri(src/oauth_redirect.rs:27) requires exact string equality against the allowlist. The allowlist carrieshttp://localhost:8787/callback. Claude Code CLI does not use 8787: it binds a random free port per session (the observed attempt usedhttp://localhost:3118/callback) and only consults a fixed port if every random draw fails.MCP_OAUTH_CALLBACK_PORToverrides it, but nothing sets that by default. No static entry can match, so native loopback clients are permanently locked out.RFC 8252 §7.3:
validate_redirect_urialready implements the loopback carve-out for the scheme check — cleartexthttpis permitted on loopback hosts only. The port half of the same rule was never written.Fix
When an allowlist entry is a loopback
httpURI, compare scheme + host + path and ignore the port. Non-loopback entries keep exact matching: the port is a meaningful part of anhttpsor private-scheme callback and relaxing it there would be a real hole.Tests:
http://localhost:8787/callbackallowlisted acceptshttp://localhost:3118/callbackhttp://localhost:3118/other— path still mattershttp://127.0.0.1:3118/callbackunless127.0.0.1is listed separately — RFC 8252 relaxes the port, not the hosthttps://claude.ai/api/mcp/auth_callbackstill rejectshttps://claude.ai:8443/api/mcp/auth_callbackBefore trusting the new tests, break the matcher and watch them go red.
Scope
Sibling issue with the full diagnosis: jlxq0/caldav-mcp#2
Also affected: carddav-mcp, m365-mcp, typst-mcp, caldav-mcp.
No deployment manifest change is required — the existing allowlists already carry a loopback entry, which starts matching once the rule is right.
Correction: the diagnosis in this issue was wrong when it was filed
jmap-mcpdid not have this defect.c9f5ae1 fix(oauth): accept ephemeral loopback portslanded on 2026-08-19, six days before this issue, and is contained in tags v0.2.11 through v0.2.14. The live deployment on Fondue was already running v0.2.14.src/oauth_redirect.rs:27has delegated toloopback_redirect_matchessince then; it has not demanded exact string equality.Verified against the running service, using this issue's own failing case:
POST /registerwithhttp://localhost:3118/callbackhttp://evil.example/callbackhttps://claude.ai:8443/api/mcp/auth_callbackHow the error was made, since that is the part worth keeping: I grepped
fn is_allowed_redirect_uriacross all five Rust MCP repos, got a line number back from each, and wrote five issues as though identical filenames implied identical function bodies. I never read this repo's. Four of the five were right by luck of the copies not having diverged in that direction; this one was not.The check that would have caught it costs one command —
git log -- src/oauth_redirect.rs— and the worker assigned here ran it before writing any code, which is why nothing wrong was committed.What the PR that closed this actually fixed
A real gap, in the tests rather than the code. Mutating
loopback_redirect_matchesto compare scheme + host + path only — relaxing the port on every scheme, includinghttps://claude.ai/…— leftallowlist_matches_exact_redirect_uri_onlygreen. Nothing asserted the fourth case from this issue's body, so the suite could not distinguish the correct fix from the dangerous one.Three mutations, each watched red before being trusted:
loopback_allowlist_varies_only_the_portallowlist_accepts_private_use_schemes,loopback_allowlist_varies_only_the_portallowlist_matches_exact_redirect_uri_onlyNo release tag, deliberately
The change is test-only and the behaviour it covers already runs in production as v0.2.14. A v0.2.15 would build a functionally identical image, open a Renovate PR against
oddie-apps/platformand trigger an ArgoCD sync for no behaviour change. Left at v0.2.14.Sibling status:
caldav-mcpv0.1.2 andm365-mcpv0.1.7 shipped real fixes;carddav-mcpandtypst-mcpgenuinely still had exact-string matching onmainand are in flight. This repo was the only false positive.