Skip to content

Harden MCP portal authentication changes - #199

Open
dcartertwo wants to merge 1 commit into
feature/mcp-portal-large-catalogsfrom
feature/mcp-portal-auth-hardening
Open

Harden MCP portal authentication changes#199
dcartertwo wants to merge 1 commit into
feature/mcp-portal-large-catalogsfrom
feature/mcp-portal-auth-hardening

Conversation

@dcartertwo

Copy link
Copy Markdown
Collaborator

Portal authentication mode and deployment-owned tokens can change while an account or facet remains live. This makes auth-mode transitions clear stale OAuth state, refuses fallback/callback paths no longer allowed by current configuration, and invalidates captured connection generations when token authority changes.

OAuth redirects record their configuration revision before handing off to the callback, preventing an existing facet from invalidating the attempt while the user is authorizing.

Stacked on #170. Verified with the MCP and portal suites plus full pnpm lint.

@github-actions github-actions Bot added mcp/shared Changes to shared MCP integration code gatekeeper Changes to a gatekeeper integration labels Aug 13, 2026
connected: ServerAuthKind, configured: ServerAuthKind,
): boolean {
return (connected === "token") !== (configured === "token");
return configured === "oauth" ? connected === "token" : connected !== configured;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The doc-comment above (lines 165-168) is now stale relative to this new logic. It says probing may legitimately move between none and OAuth (bidirectionally), but configured === "oauth" ? connected === "token" : connected !== configured only tolerates that drift when the portal is configured as oauth. When configured as none, a connected oauth state now requires a reconnect (connected !== configured) — the intended new hardening, but it contradicts the comment. Please update the doc to describe the asymmetric rule (an oauth-configured portal may prove public during probing, while an explicitly none-configured portal stays strict).

@ask-bonk

ask-bonk Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review submitted with one inline finding.

I reviewed the PR (hardening MCP portal authentication). The logic is sound: the auth-mode reconnect rule, the OAuth fallback/callback gates, the connection-generation invalidation, and the per-revision in-flight counter all correctly implement "clear stale OAuth state / refuse paths no longer allowed / invalidate on token-authority change." The truth-table change to portalAuthRequiresReconnect flips exactly one case (connected=oauth, configured=none → now requires reconnect), which matches the stated intent, and the concurrency guard around #connectingRevisions and the baseline-revision logic in getConnection avoid clobbering a newer attempt. The new test validly exercises the revision-recording path.

I submitted 1 inline finding: the doc-comment on portalAuthRequiresReconnect in config.ts is now stale — it claims bidirectional none↔OAuth drift is fine, but the new implementation only tolerates that when the portal is configured oauth, and keeps none-configured strict.

github run

@dcartertwo
dcartertwo force-pushed the feature/mcp-portal-large-catalogs branch from 972434f to b4a5027 Compare August 14, 2026 00:35
@dcartertwo
dcartertwo force-pushed the feature/mcp-portal-auth-hardening branch from 2846cdc to f5c6473 Compare August 14, 2026 00:35
@ask-bonk

ask-bonk Bot commented Aug 14, 2026

Copy link
Copy Markdown

I've completed my review. My analysis confirms the logic is sound across all six files:

  • account.ts: The allowsOAuthFallback/allowsOAuthCallback hooks default to true (no regression for the generic connector), and authChanged only triggers when a caller passes a target with differing auth — which for user-supplied reconnects it never does (resolveConnectTarget passes existing). The generation/callback invalidation is guarded correctly.
  • portal.ts: The per-revision in-flight counter (#connectingRevisions) correctly prevents a losing/older concurrent attempt from clobbering a newer one's baseline. The getConnection migration branch invalidates a pre-revision token account exactly once and only invalidates oauth accounts' state when the revision genuinely changes.
  • config.ts: The portalAuthRequiresReconnect truth-table change (connected=oauth, configured=none now requires reconnect) matches the stated hardening.
  • Tests and the new vitest.config.ts alias (needed because portal.ts imports cloudflare:workers) are valid.

The only actionable issue is the stale doc-comment on portalAuthRequiresReconnect (config.ts:165-168), which claims bidirectional none↔OAuth drift is tolerated while the new asymmetric rule only tolerates it for oauth-configured portals. However, this exact finding was already submitted inline by the prior ask-bonk review on this same PR. Per review guidelines, I will not repeat a published finding, and I found no additional actionable problems.

No new actionable findings. (1 pre-existing finding — the stale portalAuthRequiresReconnect doc-comment — was already posted inline by an earlier review and is not repeated.)

github run

@ask-bonk

ask-bonk Bot commented Aug 14, 2026

Copy link
Copy Markdown

Now let me examine the authChanged interaction with the portal's allowsOAuthFallback more carefully for a subtle bug.

Consider a portal reconfigured from oauth to none:

  • On reconnect, target.auth = "none", existing.auth = "oauth".
  • authChanged = true → clears tokens, stores server with auth: "none".
  • allowsOAuthFallback(server) for portal returns server.auth !== "none"false.
  • Probe runs. If the server (still requiring auth) 401s, since allowsOAuthFallback is false, it throws "requires authorization but configured for unauthenticated access." Good.
  • If probe succeeds (public), records auth: "none". Good.

Now consider noneoauth:

  • target.auth = "oauth", existing.auth = "none". authChanged = true, clears state, stores server auth=oauth.
  • Probe unauthenticated. If 401, allowsOAuthFallback = "oauth" !== "none" = true → OAuth flows. Good.
  • If probe succeeds (public), records auth: "none" (line 378 sets {...server, auth:"none"} since not token). Hmm — so a portal configured oauth but actually public records auth: "none". Then portalAuthRequiresReconnect("none", "oauth") = false. Good, consistent.

The logic is coherent. Now let me double check one concern in getConnection: the #connectingRevisions Map is per-DO-instance in memory. If the DO evicts/restarts between a beginConnect redirect and the eventual getConnection, the map is empty. But that's fine because portalConfigRevision is persisted in storage during beginConnect, so previous === revision and no invalidation. The in-memory map is only an optimization for the concurrent-attempt window, which by nature is within one activation. Correct.

I've completed a thorough review. The implementation is sound. The only actionable finding is the stale doc-comment on portalAuthRequiresReconnect in config.ts (lines 165-168), which was already posted inline by a prior review on this PR. Per the review guidelines, I will not repeat a published finding, and I found no additional actionable issues.

No new actionable findings. (1 pre-existing finding — the stale portalAuthRequiresReconnect doc-comment at config.ts:165-168, which still claims bidirectional none↔OAuth drift is tolerated while the new asymmetric rule only tolerates it for oauth-configured portals — was already posted inline by an earlier review and is not repeated.)

github run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gatekeeper Changes to a gatekeeper integration mcp/shared Changes to shared MCP integration code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant