Skip to content

fix(oauth): align MCP scope boundary - #222

Merged
vishr merged 4 commits into
mainfrom
fix/mcp-oauth-scope-boundary
Sep 5, 2026
Merged

fix(oauth): align MCP scope boundary#222
vishr merged 4 commits into
mainfrom
fix/mcp-oauth-scope-boundary

Conversation

@vishr

@vishr vishr commented Sep 5, 2026

Copy link
Copy Markdown
Member

What changed

Make OAuth scopes the MCP delegation boundary with canonical
telemetry:read and dashboard:manage names. Existing fanout:read and
fanout:dashboard grants remain accepted and are canonicalized in memory or
on token rotation, so registrations and active credentials are not
invalidated.

Delegated tokens no longer expose the account role, and authorization checks
map scopes to the user's live capabilities. Dashboard tool calls require
dashboard:manage and return an insufficient_scope challenge when a
read-only token attempts them.

OAuth discovery now advertises the canonical scopes, protected-resource name,
and RFC 9207 issuer support; authorization redirects include iss. The token
endpoint rejects scope on authorization-code exchange and permits refresh
tokens to retain or narrow—but never expand—the original grant without
consuming a token on invalid_scope. Public MCP documentation and regression
coverage are updated with the new contract.

Closes #221

Contract notes:

  • OAuth discovery and newly issued token responses use telemetry:read and
    dashboard:manage.
  • Legacy scope names remain compatibility aliases; there is no migration,
    client deletion, forced re-registration, or forced re-consent.
  • Dashboard MCP tool calls now produce HTTP 403 with a scoped bearer challenge
    when the token lacks dashboard:manage.
  • Authorization callbacks add the RFC 9207 iss query parameter.

Verification

  • just check
  • just test-race when auth, API, ingest, query, MCP, or agent paths changed
  • User-facing behavior and configuration docs are current
  • No credentials, private telemetry, host details, or enterprise-only source are included
  • API, migration, ingest, MCP/AG-UI, or release-contract changes are called out

Additional checks performed during implementation included focused OAuth,
auth, MCP, and dashboard tests plus go vet ./....

Copilot AI 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.

🟡 Changes recommended

The new dashboard step-up enforcement relies on client-supplied MCP headers for authorization decisions, which can allow requests to bypass the intended HTTP-layer insufficient-scope challenge behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates Fanout’s MCP OAuth contract so OAuth scopes (not account roles) define the delegated authorization boundary, introducing canonical scopes (telemetry:read, dashboard:manage) while keeping legacy fanout:* scopes working via canonicalization.

Changes:

  • Canonicalize/validate MCP OAuth scopes (including legacy aliases) and enforce refresh-token narrowing (no scope expansion).
  • Remove delegated account role exposure from MCP TokenInfo.Extra, and re-check live user capabilities when verifying delegated tokens.
  • Advertise updated OAuth discovery metadata (canonical scopes, protected-resource name, RFC 9207 iss), and update docs/tests accordingly.
File summaries
File Description
site/src/content/docs/guides/connect-over-mcp.mdx Updates public MCP docs to reflect canonical scopes and scope-based delegation boundary.
internal/dashboard/identity.go Switches dashboard OAuth scope constant to canonical dashboard:manage.
internal/auth/oauth_store.go Canonicalizes scopes at issuance/verification and enforces refresh scope narrowing.
internal/auth/oauth_store_test.go Adds regression coverage for legacy-scope canonicalization and updates refresh rotation call sites.
internal/auth/oauth_scope.go Introduces canonical scope parsing + legacy alias handling and refresh narrowing policy.
internal/auth/oauth_scope_test.go Adds unit tests for canonicalization and refresh narrowing/expansion rejection.
internal/api/oauth.go Updates MCP OAuth endpoints/metadata, removes delegated role exposure, adds iss, and adds HTTP-layer scope challenges.
internal/api/oauth_test.go Expands OAuth/MCP regression tests for discovery metadata, iss, role non-exposure, and scope enforcement.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/api/oauth.go Outdated
Comment thread internal/api/oauth.go

Copilot AI 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.

🟡 Changes recommended

The new HTTP-layer per-tool scope gate can fail open for oversized/unparseable MCP request bodies, which can break the promised 403 + insufficient_scope challenge behavior for dashboard tool calls in edge cases.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread internal/api/oauth.go Outdated
Comment thread internal/api/oauth.go

Copilot AI 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.

🟡 Changes recommended

The new MCP authorization-body parser currently treats JSON null / empty JSON-RPC batches as valid, which can bypass the intended “invalid MCP request body” 400 response from the authorization gate.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread internal/api/oauth.go Outdated

Copilot AI 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.

🔵 Needs a closer look

It changes core OAuth/MCP authorization and request-gating logic in security-sensitive paths that warrant final human review.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@vishr
vishr merged commit bc8429c into main Sep 5, 2026
9 checks passed
@vishr
vishr deleted the fix/mcp-oauth-scope-boundary branch September 5, 2026 02:38
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.

Align MCP OAuth scopes and delegated authorization boundary

2 participants