Conversation
jhrozek
left a comment
There was a problem hiding this comment.
Panel review — combined findings
Fixed point: origin/main (post-rebase)
Diff: 4 files, 205 insertions, 32 deletions, 2 commits
This combines an in-session four-axis panel review (Spec / Standards / Test adequacy / Domain) with an independently run second panel review (review-openai.md) that used a different spec source (issue #522) and an additional oauth-expert reviewer. Both reviews converged independently on the same root-cause defect.
Spec — does this implement what was asked?
Sources: PR description; Issue #522 (independently fetched by the other review)
- [blocker · Wrong, cross-confirmed by two independent spec reviews]
PinCallbackPathsilently overrides the registration-bound callback path that direct DCR relies on.mecated mcp logindoes support DCR (internal/cliconfig/mcpprofile.go'sloadOAuthClientpopulatesopts.Client.DCR) — the new comment inmcplogin.goclaiming otherwise is incorrect. DCR callsAuthorizeWithCallbackPathwith its registered path;resolveCallbackModepicksPinCallbackPath's/oauth/callbackinstead, violating the unguessable/registration-bound path requirement.
cmd/mecated/mcplogin.go:191,mcp/oauthlogin/runtime.go(AuthorizeWithCallbackPath,resolveCallbackMode),internal/app/mcplogin.go:99 - [important · Missing] No end-to-end test proves the real
mecated mcp loginflow succeeds through actual redirect-URI matching — command tests stubexecuteMCPLogin, runtime tests invoke the callback directly; neither would have caught this regression. - [advisory] The current case order makes the gap unreachable today only because the one
PinCallbackPathcaller never callsAuthorizeWithCallbackPath— nothing enforces that invariant going forward.
Standards — does this follow project conventions?
Sources read: AGENTS.md, ADR 0112, ADR 0325 (verified — Decision point 5: "Bind a stable random callback path to the registration and a fresh ephemeral IPv4 loopback port to each authorization")
- [blocker · Violation] The blanket
PinCallbackPath: truewiring inmcplogin.godirectly contradicts ADR 0325 §5's accepted decision for direct-DCR profiles — a documented, superseding-record-required violation, not just an implicit-convention gap.
docs/adr/0325-direct-mcp-dcr.md:44-56;cmd/mecated/mcplogin.go:191,mcp/oauthlogin/runtime.go:365 - [important · Violation] For preregistered/CIMD profiles, this further diverges from ADR 0112's stated scope (random path and port) without a superseding ADR or an updated living-design note.
- [important] Independent of the ADR question: the file's own established convention is fail-closed, explicit mutual exclusion (as done for
RedirectURL+PinCallbackPath) — leavingPinCallbackPath+callbackPathto silent case-order precedence breaks that internal consistency too.
Test adequacy — do the tests independently prove the contract?
- [blocker]
TestPinCallbackPathUsesFixedPathEphemeralPortassertsstrings.HasSuffix(redirect, fixedCallbackPath)— comparing against the same internal constant under test, not the external contract literal"/oauth/callback". A coordinated wrong-value change to the constant would still pass. (runtime_test.go:827) - [important · cross-confirmed] No test combines
PinCallbackPathwithAuthorizeWithCallbackPath— exactly the gap that let the ship-blocker regression through untested. Found independently by both reviews. - [important] The sequential-authorization test doesn't prove two logins can coexist concurrently, though it does already extract and diff two distinct ports across sequential calls.
- [advisory] No test guards that
fixedCallbackPath("/oauth/callback") andToolHiveCompatibleRedirectURL's path ("/callback") stay distinct.
Domain — what do the specialists say?
Panel (union of both runs): secure-code-reviewer, software-architect, library-reuse-reviewer, code-duplication-reviewer, oauth-expert
Ship-blockers (1)
- [blocker · High, cross-confirmed across two independent panel runs] Same DCR-override defect, from the security/architecture lens:
resolveCallbackMode's case order letsPinCallbackPathwin over an explicit, registration-boundcallbackPath, andAuthorizeWithCallbackPathonly guards againstRedirectURL, notPinCallbackPath. RFC 6749 §3.1.2.3 / RFC 7591 exact-match enforcement then rejects the login (fails closed), or on a lenient AS silently breaks the DCR contract.
Suggested fix: rejectPinCallbackPathinAuthorizeWithCallbackPath(mirroring its existingRedirectURLguard), and/or give an explicitcallbackPathpriority overPinCallbackPathin the switch; only pin the callback path at themecplogin.gocall site for non-DCR (server.OAuth.Client.DCR == nil) servers. Correct the inaccurate "never DCR" comment.
This exact ordering was introduced by the manual rebase-conflict resolution — it existed in neither original commit.
Judgement calls (1)
- [important · Medium] RFC 8252's variable-port loopback exception is a native-app pattern, not guaranteed for every MCP authorization server; CIMD metadata may in some cases require exact redirect-string matching. Consider constraining which profiles/servers
PinCallbackPathapplies to, backed by a real-authorization-server test. (oauth-expert)
Advisory / Polish (5)
- Stale doc comment:
resolveCallbackModeclaims its cases "mirrorOptions.RedirectURL/PinCallbackPath's doc comments exactly" — no longer true with the third, non-Options-driven case. - The
resolveCallbackModeextraction is justified only by a gocyclo comment — if that's the only reason, inlining may be more readable than a 6-return-value function. RedirectURLandPinCallbackPathcases both setattemptPolicy = attemptFixedRoute— minor shrink candidate, not worth blocking on.- The RFC 8252 rationale is repeated three times (struct doc, switch-case comment,
mcplogin.gocall-site comment); the 13-line call-site comment could shrink to 2–3 lines pointing at the type doc. TestPinCallbackPathUnauthenticatedFloodDoesNotSpendAttemptsis near-byte-identical to the existingTestExactRedirect...flood test — Rule of Three not yet met (2 sites); extract a shared helper only if a third variant appears.
Summary
- Spec axis: 3 findings (1 blocker, 1 important, 1 advisory)
- Standards axis: 3 findings (1 blocker, 2 important)
- Test adequacy axis: 4 findings (1 blocker, 2 important, 1 advisory)
- Domain axis: 1 blocker (cross-confirmed across two independent panel runs), 1 important (judgement call), 5 advisory
Most important single issue: the branch currently breaks direct MCP DCR login by having PinCallbackPath silently override its ADR-0325-mandated, registration-bound callback path — independently found by every axis in both review runs. This is a real correctness/security regression, introduced specifically by the manual git-rebase conflict resolution (not present in either original commit against the pre-rebase base), and should be fixed before merge.
PANEL: ship_blockers=4 important=6 advisory=7 reviewer_failures=0
🤖 Generated with Claude Code
dbe0435 to
f144ca0
Compare
|
Rebased onto current On the panel review's ship-blockerThe review's core finding — that Rather than rejecting This is exactly the review's second suggested fix ("give an explicit Other findings addressed
Still relevant?Yes. Confirmed on rebased |
Every server reachable through `mecated mcp login` already commits to a client identity that must be registered ahead of time: selectMCPLoginServer only returns servers whose OAuth was populated by loadOAuthClient, which accepts exactly a preregistered confidential client or a CIMD client, never DCR. Leaving oauthlogin.Options.RedirectURL empty put every such login on the runtime's random-path, ephemeral-port default (ADR 0112) instead, so the presented redirect_uri never matched what either client kind had registered -- the login could never actually succeed against a standards-compliant authorization server enforcing RFC 8252 loopback matching (which tolerates only a varying port, never a varying path). Set RedirectURL to the existing ExactRedirectURL constant instead, the same fixed callback cmd/mecatui/login.go's remote-login path already uses. Renamed and updated the one test that explicitly locked in the old random-path behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous commit reused ExactRedirectURL for mecated mcp login, which fixes BOTH the callback path and the port. That reintroduces exactly the local port-squatting exposure issue #522's original design required avoiding ("use an unguessable callback path"): a well-known port is predictable and can be pre-bound by another local process before the legitimate login starts, hijacking or denying the real callback. Only the PATH actually needs to be fixed for a preregistered/CIMD client's redirect_uri to be statically registerable. RFC 8252 SS7.3 loopback matching -- which every authorization server this login talks to already implements, since neither client kind is reachable through DCR -- ignores the port on both sides of the comparison. So the port can and should stay ephemeral, exactly as the random-path default already does; only the path needs to stop being randomly generated per invocation. Add Options.PinCallbackPath: fixes the callback to fixedCallbackPath while still binding "127.0.0.1:0" (OS-assigned port), extracted alongside ExactRedirectURL's fully-fixed mode into resolveCallbackMode to keep Authorize's branch count under the gocyclo limit. ExactRedirectURL itself is untouched and stays available for a target that genuinely needs an exact string match (e.g. cmd/mecatui/login.go's remote OIDC login, which cannot assume its target implements RFC 8252 dynamic-port matching). mecated mcp login now uses PinCallbackPath instead. This also removes the port-contention regression the previous commit introduced: concurrent `mecated mcp login` calls to different servers each get their own ephemeral port again, same as before either commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
f144ca0 to
da145b0
Compare
Summary
mecated mcp login <server>could never actually complete against a standards-compliant authorization server for its preregistered-confidential or CIMD profiles. Both of those client kinds commit to a redirect_uri that must be registered ahead of time. ButrunMCPLoginleft the login runtime on its random-callback-path, ephemeral-port default (ADR 0112) — a fresh path and port on every single invocation. RFC 8252 §7.3 loopback matching (which a standards-compliant AS implements) only tolerates a varying port; the path must match exactly. So the presented redirect_uri could never match anything registered in advance, for either of those client kinds.Update after rebase (2026-09-17): when this PR was opened,
loadOAuthClient(internal/cliconfig/mcpprofile.go) accepted only preregistered/CIMD clients, never DCR — the sentence above and the original in-code comment said so accurately at the time. Direct MCP OAuth DCR (ADR 0325,Client.DCR,oauthlogin.AuthorizeWithCallbackPath) landed onmainvia #1413 while this PR sat open, somecated mcp loginnow has a third client kind with its own, already-correct, registration-bound callback-path mechanism. The bug this PR fixes is unchanged and still live for the preregistered/CIMD case; see "Interaction with DCR" below for how the fix coexists with it.This surfaced while wiring Connector Gateway's embedded auth server for CIMD interop with mecatl (stacklok/stacklok-enterprise-platform#3843): the connector-gateway side is ready, but a real
mecated mcp loginagainst it would still fail on this.Fix (revised after review discussion)
An earlier version of this PR reused
oauthlogin.ExactRedirectURL(mecatui's remote-login constant), which fixes both the callback path and the port (18473). On reflection that reintroduces exactly what issue #522's original design required avoiding: "use an unguessable callback path". A well-known, fixed port is squattable — another local process can pre-bind it before the legitimate login starts, hijacking or denying the real callback.Only the path actually needs to be fixed for a preregistered/CIMD client's redirect_uri to be statically registerable. RFC 8252 §7.3 loopback matching — which every AS this command talks to already implements, since neither reachable client kind goes through DCR — ignores the port on both sides of the comparison. So this PR instead adds
oauthlogin.Options.PinCallbackPath: it fixes the callback to a well-known path but keeps binding an ephemeral port (127.0.0.1:0), exactly like the existing random-path default already does.mecated mcp loginnow uses this instead ofExactRedirectURL.Net effect:
mecated mcp logincalls to different servers each get their own ephemeral port, same as before this PR.oauthlogin.ExactRedirectURLitself is untouched and still available —cmd/mecatui/login.go's remote-login flow keeps using it, since a general-purpose OIDC target can't be assumed to implement RFC 8252 dynamic-port matching the way an MCP-shaped AS does.Interaction with DCR (added on rebase)
cmd/mecated/mcplogin.gobuilds one sharedoauthlogin.RuntimewithPinCallbackPath: truebefore it knows which client kindselectMCPLoginServerreturned, and a DCR login is routed through the same runtime'sAuthorizeWithCallbackPath(ctx, issuer, callbackPath, ...)with its own registration-boundcallbackPath.resolveCallbackMode's case order gives that explicit per-callcallbackPathpriority overPinCallbackPath, so a DCR login always presents the redirect_uri it actually registered, never the well-known/oauth/callbackpath —PinCallbackPathonly ever takes effect for the preregistered/CIMD path (a plainAuthorizecall with no explicitcallbackPath). Pinned byTestPinCallbackPathDoesNotOverrideRegistrationBoundPath.Development stage
Optionsfield + a smallAuthorizerefactor + a new test matrix, no new subsystem).Contract linkage
oauthlogin.Options/ExactRedirectURL/the fixed-vs-random callback split already exist; this adds a third, narrower mode alongside them (PinCallbackPath) and one call site's choice of which mode to use. No new abstractions beyond that.ExactRedirectURL's fully-fixed mode (introduced later, for mecatui) is also untouched.Issue relationship
Relates to stacklok/stacklok-enterprise-platform#3843 (Connector Gateway ↔ mecatl CIMD interop). No mecatl-side issue was filed for this specific gap before this PR.
Type of change
Test plan
main(post-feat(mcp): add direct MCP OAuth dynamic client registration #1413 DCR landing);git diffstill scoped to the same 4 filestask lint/golangci-lint run ./mcp/oauthlogin/... ./cmd/mecated/...) — 0 issuesgo test ./cmd/mecated/... ./mcp/oauthlogin/... ./internal/app/... ./internal/cliconfig/...all passgo run ./cmd/mecademo) — not applicablemecated mcp login's docs mention the callback shapecmd/andmcp/oauthloginonlyChanges
mcp/oauthlogin/runtime.gofixedCallbackPath; addOptions.PinCallbackPath(fixed path, ephemeral port); extractresolveCallbackModeto keepauthorize's branch count under the gocyclo limit, with explicit per-callcallbackPath(DCR) prioritized overPinCallbackPath; validateRedirectURL/PinCallbackPathare mutually exclusivemcp/oauthlogin/runtime_test.goTestPinCallbackPathUsesFixedPathEphemeralPort(proves path fixed, port varies across two runs; asserts against the literal path, not just the constant),TestPinCallbackPathUnauthenticatedFloodDoesNotSpendAttempts(mirrors the equivalentExactRedirect*test),TestPinCallbackPathAndRedirectURLAreMutuallyExclusive,TestPinCallbackPathDoesNotOverrideRegistrationBoundPath(proves DCR's explicit callback path wins overPinCallbackPath)cmd/mecated/mcplogin.goPinCallbackPath: trueinstead ofRedirectURL: ExactRedirectURL; updated the call-site comment for the now-three-way (preregistered/CIMD/DCR) client splitcmd/mecated/mcplogin_test.goPinCallbackPathis forwarded insteadUser-facing change
mecated mcp login <server>now binds its OAuth callback listener on a fixed, well-known path (/oauth/callback) instead of a random one, but the port is still chosen by the OS at bind time, same as before this change — no port pinning, no port-contention risk. Operators registering a preregistered client or publishing a CIMD document for a mecatl-facing server should usehttp://127.0.0.1/oauth/callback(no port, or any port — matching ignores it) as the redirect_uri.Special notes for reviewers
ExactRedirectURL(fixed path + fixed port), which a security-minded read flagged as reintroducing the port-squatting exposure issue Runtime-owned browser and loopback login for MCP OAuth #522 deliberately avoided. This version fixes only the path, keeping the port ephemeral, which should fully reconcile both concerns — please double check that reasoning holds.🤖 Generated with Claude Code