Skip to content

fix(oauth): keep a pending login alive across scans and the quarantine exemption - #1739

Merged
github-actions[bot] merged 1 commit into
mainfrom
claude/elastic-bell-f543b5
Oct 22, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
claude/elastic-bell-f543b5

Conversation

@Dumbris

@Dumbris Dumbris commented Oct 22, 2026

Copy link
Copy Markdown
Member

Closes #1722

Adding a remote OAuth upstream and approving it in the browser failed with instance retired (removed, replaced or quarantined) at the code exchange, although the callback had reached mcpproxy. Two background paths tore down the in-flight login; this PR fixes both and makes the failure visible when it still happens.

Cause 2: a scan restarted the pending-auth server

Every scan (admission, the Spec 118 informational rescan, a manual scan) that found the server disconnected called EnsureConnected, which restarts the server. A server waiting on a person's login can never connect, so the scan waited 30 s, then replaced the instance and retired the flow that owned the PKCE verifier and the callback waiter.

  • internal/security/scanner/service.go: optional oauthLoginAwareProvider capability (AwaitingOAuthLogin). Both EnsureConnected sites stand down for such a server and log why; the scan proceeds with what it can export without a restart (the ingested contracts) or fails closed as before.
  • internal/server/server.go: configServerInfoProvider.AwaitingOAuthLogin → Runtime.ServerAwaitsOAuthLogin: a manual login flow in flight (oauth.IsManualFlowActive) or a live instance parked in Pending Auth.

Cause 1: the inspection exemption expired under the login

A new server's add grants an hour-long review connection, but the admission scan's EnsureConnected immediately overwrote it with 15 minutes, and the expiry-driven disconnect ran while the operator was still signing in.

  • internal/runtime/supervisor/supervisor.go: RequestInspectionExemption never shortens an exemption in force; SetInspectionExemptionHold installs a predicate (the runtime passes oauth.IsManualFlowActive) and both expiry paths (IsInspectionExempted, the cleanup loop via collectExpiredExemptions) hold an expired exemption while a login is in flight, extending it in 5-minute steps, so it outlives the login by at most that grace.

Visibility

  • internal/upstream/core/connection_oauth.go: the background callback waiter also selects on its gate's context. When the instance is retired mid-login it stops waiting and invalidates its state.
  • internal/oauth/config.go: CallbackServer.InvalidateState(state, reason); the callback that lands afterwards renders Authorization Failed with "This sign-in was invalidated before it could finish: the server's connection was retired (restarted, replaced, removed or quarantined) while the sign-in was in progress" instead of a success page followed by a silent exchange error.

RT-02 untouched: nothing here approves, unquarantines or blocks anything. No new dependencies.

Tests (written first, watched fail)

  • scanner: TestStartScan_DoesNotReconnectServerAwaitingOAuthLogin (failed on main after the 30 s wait with EnsureConnected called 1 time(s)), plus the control that a disconnected server with no login is still reconnected.
  • supervisor: held while a login is in flight (IsInspectionExempted and the cleanup-loop path), no hold without a predicate, never-shortens.
  • oauth: invalidated state renders the failure page with the reason; unknown state keeps the generic message.
  • core: a retired gate ends the waiter, drops the waiter, and the real loopback callback then returns 400 with the reason.
  • server: the provider predicate follows the manual-flow claim.

Docs: docs/features/oauth-authentication.md (troubleshooting entry), docs/features/security-quarantine.md (informational scan section).

…e exemption (#1722)

A remote OAuth upstream's browser approval failed at the code exchange
with "instance retired" because two background paths tore down the
in-flight login:

- Every scan (admission, informational rescan, manual) that found the
  server disconnected called EnsureConnected, which restarts it. A
  server waiting on a person's login can never connect, so the scan
  waited 30 s, replaced the instance and retired the flow. The scanner
  now asks the optional oauthLoginAwareProvider capability
  (Runtime.ServerAwaitsOAuthLogin: manual flow in flight, or parked in
  Pending Auth) and scans without reconnecting such a server.

- A new server's one-hour inspection exemption was overwritten by the
  admission scan's 15-minute grant, and the expiry-driven disconnect
  landed while the operator was still signing in. A grant never
  shortens an exemption in force, and both expiry paths hold an expired
  exemption (extending it in 5-minute steps) while a login for the
  server is in flight.

When an instance is still retired mid-login (operator removal, restart
or quarantine), the callback waiter now notices through its gate's
context, invalidates its state, and the callback page says the sign-in
was invalidated instead of showing a success page before a silent
exchange failure.

RT-02 untouched: nothing here approves, unquarantines or blocks anything.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: d5590a4
Status: ✅  Deploy successful!
Preview URL: https://f12b2c58.mcpproxy-docs.pages.dev
Branch Preview URL: https://claude-elastic-bell-f543b5.mcpproxy-docs.pages.dev

View logs

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved (Model B): Paperclip review verdicts = ACCEPT and qa-gate green at this head SHA. Arming auto-merge; GitHub merges when all required checks pass.

@github-actions
github-actions Bot enabled auto-merge (squash) October 22, 2026 15:20
@codecov-commenter

codecov-commenter commented Oct 22, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 75.82418% with 22 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/runtime/runtime.go 10.00% 9 Missing ⚠️
internal/runtime/supervisor/supervisor.go 85.29% 3 Missing and 2 partials ⚠️
internal/oauth/config.go 88.88% 2 Missing and 1 partial ⚠️
internal/security/scanner/service.go 70.00% 2 Missing and 1 partial ⚠️
internal/server/server.go 50.00% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown
Contributor

📦 Build Artifacts

Workflow Run: View Run
Branch: claude/elastic-bell-f543b5

Available Artifacts

  • archive-darwin-amd64 (32 MB)
  • archive-darwin-arm64 (29 MB)
  • archive-linux-amd64 (19 MB)
  • archive-linux-arm64 (17 MB)
  • archive-windows-amd64 (32 MB)
  • archive-windows-arm64 (28 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (28 MB)
  • installer-dmg-darwin-arm64 (25 MB)
  • smart-mcp-proxymcpproxy-goL1SSR3.dockerbuild (0 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 39796957969 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@github-actions
github-actions Bot merged commit a983081 into main Oct 22, 2026
53 checks passed
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.

OAuth login fails after a successful browser approval: scan restart or quarantine expiry retires the pending flow

2 participants