Skip to content

feat(oauth): enable profile-bound remote authorization - #180

Merged
mohanagy merged 3 commits into
developmentfrom
feat/82-remote-oauth
Jul 22, 2026
Merged

feat(oauth): enable profile-bound remote authorization#180
mohanagy merged 3 commits into
developmentfrom
feat/82-remote-oauth

Conversation

@mohanagy

@mohanagy mohanagy commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Closes #82

Summary

Enable standards-compatible remote MCP OAuth for exact profile-bound Streamable HTTP upstreams. The implementation covers Protected Resource Metadata and authorization-server discovery, PKCE S256 browser authorization, RFC 8707 resource binding, RFC 9207 issuer-response validation, three explicit client registration modes, encrypted credential persistence, refresh, and authenticated reconnect.

The runtime and published schema now reject unsupported or ambiguous registration values, preserve the actual granted scope set, and keep every credential tied to its config, profile, upstream, resource, and issuer identity.

Security impact

  • OAuth tokens, refresh tokens, and dynamic client secrets are stored only in the existing OS-keyring-backed encrypted vault; there is no environment, plaintext-file, or plugin fallback.
  • The loopback callback binds literal 127.0.0.1 on a dynamic port and validates Host, path, state, issuer, and Origin with single-use, bounded, no-store behavior.
  • Discovery requires exact resource and issuer matches, authorization code support, PKCE S256, RFC 9207 support, safe HTTPS endpoints, and the configured client-registration capability.
  • Client-ID metadata validation is shared by config and runtime and rejects credentials, fragments, queries, root paths, and non-HTTPS URLs.
  • RFC 9207 capability evidence is bounded to 64 KiB and cached only for the exact issuer origin and path represented by the discovery request.
  • Cancellation remains distinct from reauthentication, and provider output, tokens, client secrets, and sensitive failures are never included in public diagnostics or audit events.
  • No dependency, subprocess-shell, policy, coverage-threshold, or package-version change is included.

Validation

  • A failing test was observed first for each behavior or configuration-contract change.
  • npm run lint
  • npm run typecheck
  • npm test — exact-head Linux quality passed.
  • npm run build
  • node dist/cli/main.js schema
  • npm run check:pack
  • Fixtures, logs, screenshots, and examples contain no credentials or private data.
  • User-facing documentation and CHANGELOG.md are updated when applicable.
  • Dependency and packaged-file changes are intentional and reviewed.
  • Undisclosed vulnerabilities are reported privately instead of in this pull request.

Additional evidence:

  • OAuth and CodeRabbit regressions: 55 passed.
  • Core suite: 402 passed, 23 platform-skipped.
  • Installed package contract: 18 passed.
  • Original implementation coverage gate: 95.46% statements, 91.55% branches, 98.35% functions, 95.46% lines.
  • The final loaded coverage rerun reproduced only test: diagnose multi-upstream availability cascade under coverage #122; no timeout, skip, worker setting, or coverage threshold was changed.
  • Exact-head CI is the merge gate.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds a standards-compatible remote OAuth runtime for exact v3 HTTPS Streamable HTTP connections, including strict configuration validation, browser PKCE authorization, profile-bound vault credentials, refresh/reconnect handling, transport integration, redacted errors, and comprehensive tests and documentation.

Changes

Remote OAuth runtime

Layer / File(s) Summary
OAuth contracts and validation
src/config/*, src/utils/errors.ts, src/cli/exit-codes.ts
Adds explicit registration-mode validation and OAuth-specific typed error and exit-code mappings.
Authorization and credential lifecycle
src/oauth/*
Adds discovery validation, loopback browser handoff, PKCE state handling, credential refresh, dynamic registration persistence, and vault-bound client credentials.
Runtime and transport wiring
src/runtime/*, src/upstream/*, src/mcp/server/*
Builds the OAuth runtime, attaches audit handling, injects profile-bound providers into Streamable HTTP, retries authorization after unauthorized responses, and closes providers during teardown.
Tests, fixtures, and documentation
tests/*, docs/*, README.md, CHANGELOG.md
Adds integration and security coverage for discovery, callback validation, refresh, restart, reconnect, redaction, configuration, and documented support boundaries.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Poem

A bunny browses, PKCE in flight,
Through loopback paths kept clean and tight.
Tokens hide in a vault below,
Refreshing where bound credentials flow.
Reconnects hop when sessions stall—
Safe OAuth carrots for all! 🐇


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Docstring Coverage ❌ Error Docstring coverage is 29.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation and tests align with #82: discovery, PKCE, callback checks, refresh, reconnect, typed errors, and isolation are covered.
Out of Scope Changes check ✅ Passed The code, docs, and tests are all directly tied to the OAuth runtime scope; no unrelated changes stand out.
Title check ✅ Passed The title clearly matches the main change: enabling profile-bound remote OAuth authorization.
Description check ✅ Passed The description includes the required Summary, Security impact, and Validation sections with concrete details.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/82-remote-oauth

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

coderabbitai[bot]
coderabbitai Bot previously requested changes Jul 22, 2026

@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: 4

🤖 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 `@src/config/schema.ts`:
- Around line 70-89: Unify client-id-metadata URL validation between
oauthClientRegistrationSchema and isSafeHttpsUrl by reusing a shared validator
or adding the missing query-string restriction to isSafeHttpsUrl. Ensure both
paths accept and reject identical URLs, including rejecting URLs with non-empty
search components while preserving the existing HTTPS and credential
restrictions.

In `@src/oauth/oauth-metadata-fetch-guard.ts`:
- Around line 19-50: The OAuthMetadataFetchGuard cache currently stores issuer
support from any authorization metadata response, allowing unrelated or poisoned
responses to affect later requests. In the constructor’s fetch wrapper, validate
that the metadata response issuer matches the requested URL origin/issuer before
adding it to issuerResponseSupport, or scope the cache per request; preserve
issuerResponseSupported’s lookup behavior for valid matching discovery
responses.

In `@src/oauth/remote-oauth-credential-refresher.ts`:
- Around line 26-28: Distinguish cancellation from genuine reauthentication in
refresh(): add or reuse a local cancellation throw path such as
refreshCancelledLocally() that produces the OAUTH_REFRESH_CANCELLED outcome, and
replace the signal.aborted branches in refresh() (including the checks around
lines 89–95 and 109–110) while keeping reauthenticationRequired() for missing or
malformed refresh-token cases.

In `@src/oauth/secure-credential-store.ts`:
- Around line 6-7: Update maximumSerializedCredentialBytes and the save flow
around validateCredential so envelopes containing all four bounded fields cannot
be written successfully and later rejected by parseEnvelope; either widen the
serialized limit to cover clientId and clientSecret or enforce the existing
limit on the serialized payload before keyring.setPassword, preserving
fail-closed behavior.
🪄 Autofix (Beta)

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: ASSERTIVE

Plan: Pro Plus

Run ID: 78c1b234-3b22-4382-ab81-ff34d1700df7

📥 Commits

Reviewing files that changed from the base of the PR and between 3a9ea68 and 96a5c92.

📒 Files selected for processing (37)
  • CHANGELOG.md
  • README.md
  • docs/architecture.md
  • docs/config.md
  • docs/library-api.md
  • docs/oauth-console-threat-model.md
  • docs/oauth-support.md
  • docs/security.md
  • src/cli/exit-codes.ts
  • src/config/generate-json-schema.ts
  • src/config/schema.ts
  • src/mcp/server/miftah-server.ts
  • src/oauth/connection-lifecycle.ts
  • src/oauth/loopback-authorization-handoff.ts
  • src/oauth/oauth-metadata-fetch-guard.ts
  • src/oauth/remote-oauth-client-provider.ts
  • src/oauth/remote-oauth-credential-refresher.ts
  • src/oauth/remote-oauth-runtime.ts
  • src/oauth/secure-credential-store.ts
  • src/runtime/create-miftah-runtime.ts
  • src/runtime/create-runtime.ts
  • src/runtime/resolve-runtime-config.ts
  • src/upstream/upstream-process-manager.ts
  • src/utils/errors.ts
  • tests/cli-exit-codes.test.ts
  • tests/config-schema-contract.test.ts
  • tests/helpers/fake-remote-upstream.ts
  • tests/oauth-config.test.ts
  • tests/oauth-console-threat-model-docs-contract.test.ts
  • tests/oauth-loopback-handoff.test.ts
  • tests/oauth-secure-credential-store.test.ts
  • tests/oauth-support-docs-contract.test.ts
  • tests/readme-product-contract.test.ts
  • tests/remote-oauth-client-provider.test.ts
  • tests/remote-oauth-credential-refresher.test.ts
  • tests/remote-oauth-runtime.test.ts
  • tests/remote-oauth-transport.test.ts
💤 Files with no reviewable changes (1)
  • src/runtime/resolve-runtime-config.ts

Comment thread src/config/schema.ts
Comment thread src/oauth/oauth-metadata-fetch-guard.ts
Comment thread src/oauth/remote-oauth-credential-refresher.ts
Comment thread src/oauth/secure-credential-store.ts
@mohanagy

Copy link
Copy Markdown
Owner Author

Independent local Claude Opus review completed on the original PR head.

No correctness or security blocker was found. The actionable low-severity findings were resolved on 910fde3:

  • persist and restore the actual granted scope set rather than reconstructing configured scopes
  • carry refresh, dynamic registration, and scope state forward when refresh responses omit replacements
  • bound authorization metadata parsing to 64 KiB without allowing an oversized cloned response to stall discovery
  • cover dynamic, pre-registered, and client-ID-metadata registration through the full transport flow
  • cover non-Streamable-HTTP rejection and authorization-handoff cleanup

One efficiency advisory was intentionally not changed: the MCP OAuth provider must expose an exact loopback redirect URI, including its dynamically allocated port, before the SDK builds the authorization request. Deferring listener allocation until after the challenge would make that redirect URI unavailable or introduce a port-rebinding race. The listener is single-use and is closed immediately after successful connection or on every failure/manager cleanup path.

Post-review validation:

  • full suite: 101 files passed, 1 platform-skipped; 1,195 tests passed, 23 platform-skipped
  • focused review regressions: 45 passed
  • core: 402 passed, 23 platform-skipped
  • lint, typecheck, build, CLI smoke, package contents, and installed package contract passed

@mohanagy
mohanagy dismissed coderabbitai[bot]’s stale review July 22, 2026 11:07

All four actionable findings were addressed or verified with regression evidence on 96dfd95, every review thread is resolved, and the incremental CodeRabbit review is explicitly rate-limited. An independent local Claude Opus review also found no blocker.

@mohanagy
mohanagy merged commit 9be6181 into development Jul 22, 2026
12 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.

[Protocol] Implement standards-based remote MCP OAuth authorization, refresh, and reconnect

1 participant