Skip to content

fix(transports,framework): tighten mcp oauth2 server validation (#7851) - #7875

Merged
akshaydeo merged 1 commit into
mainfrom
backport/mcp-oauth2-server
Oct 3, 2026
Merged

akshaydeo merged 1 commit into
mainfrom
backport/mcp-oauth2-server

Conversation

@akshaydeo

Copy link
Copy Markdown
Contributor

Summary

Briefly explain the purpose of this PR and the problem it solves.

Changes

  • What was changed and why
  • Any notable design decisions or trade-offs

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

Describe the steps to validate this change. Include commands and expected outcomes.

# Core/Transports
go version
go test ./...

# UI
cd ui
pnpm i || npm i
pnpm test || npm test
pnpm build || npm run build

If adding new configs or environment variables, document them here.

Screenshots/Recordings

If UI changes, add before/after screenshots or short clips.

Breaking changes

  • Yes
  • No

If yes, describe impact and migration instructions.

Related issues

Link related issues and discussions. Example: Closes #123

Security considerations

Note any security implications (auth, secrets, PII, sandboxing, etc.).

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: maximhq/bifrost/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 4a0ff549-a70b-4884-8895-85fffc024d96
📥 Commits

Reviewing files that changed from the base of the PR and between 46be3ee and 8211deb.

📒 Files selected for processing (15)
  • docs/mcp/gateway-auth.mdx
  • docs/openapi/openapi.json
  • docs/openapi/schemas/management/config.yaml
  • tests/e2e/api/collections/bifrost-v1-mcp-auth.postman_collection.json
  • tests/e2e/api/collections/provider-harness.json
  • tests/integrations/python/config.json
  • transports/bifrost-http/handlers/config_test.go
  • transports/bifrost-http/handlers/mcpoauth2consent.go
  • transports/bifrost-http/handlers/mcpoauth2consent_test.go
  • transports/bifrost-http/handlers/mcpoauth2discovery_test.go
  • transports/bifrost-http/handlers/mcpoauth2issuance.go
  • transports/bifrost-http/handlers/mcpoauth2issuance_test.go
  • transports/bifrost-http/handlers/mcpoauth2utils.go
  • transports/bifrost-http/handlers/mcpoauth2utils_test.go
  • transports/config.schema.json

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.


📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • OAuth discovery and issuance endpoints now return 404 when authentication is configured for headers-only mode.
    • Session consent now requires a signed-in identity; anonymous and authentication-bypassed requests receive 401.
    • OAuth configuration now requires an issuer URL when OAuth is enabled. Issuers are no longer inferred from the request host.
    • Oversized registration and authorization inputs are rejected with HTTP 400 before being stored or processed.
    • OAuth discovery metadata and JWKS responses now include Cache-Control: no-store.
  • Documentation
    • Updated OAuth configuration, consent, input limits, and troubleshooting guidance.

Walkthrough

MCP OAuth configuration now requires an issuer in OAuth-enabled modes. Issuance endpoints are gated by auth mode and validate input sizes. Session consent now requires a signed-in identity under the documented conditions. Tests and documentation cover these rules.

Changes

MCP OAuth behavior

Layer / File(s) Summary
Issuer configuration requirements
transports/config.schema.json, transports/bifrost-http/handlers/mcpoauth2utils.go, transports/bifrost-http/handlers/config_test.go, docs/openapi/*, tests/integrations/python/config.json, tests/e2e/api/collections/*
OAuth-enabled modes require issuer_url. Configuration validation rejects its omission, and issuer resolution does not derive it from the request Host when OAuth is enabled.
Issuance availability and input validation
transports/bifrost-http/handlers/mcpoauth2issuance.go, transports/bifrost-http/handlers/mcpoauth2issuance_test.go, transports/bifrost-http/handlers/mcpoauth2discovery_test.go, tests/e2e/api/collections/*, docs/mcp/gateway-auth.mdx
Issuance endpoints return 404 when disabled. Registration and authorization reject oversized inputs. Documentation and tests cover endpoint availability and Cache-Control: no-store on discovery responses.
Session-consent identity checks
transports/bifrost-http/handlers/mcpoauth2consent.go, transports/bifrost-http/handlers/mcpoauth2consent_test.go, tests/e2e/api/collections/*, docs/mcp/gateway-auth.mdx
Session mode is offered only to signed-in consenting identities when inference auth enforcement is disabled. Anonymous and auth-bypassed submissions return 401. Tests and documentation cover the behavior.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ConsentFlow
  participant IdentityCheck
  Browser->>ConsentFlow: Request available consent modes
  ConsentFlow->>IdentityCheck: Check request identity
  IdentityCheck-->>ConsentFlow: Return identity status
  ConsentFlow-->>Browser: Return available modes
  Browser->>ConsentFlow: Submit session consent
  ConsentFlow->>IdentityCheck: Verify consenting identity
  IdentityCheck-->>ConsentFlow: Return identity status
  ConsentFlow-->>Browser: Return 401 if identity is absent
Loading

Suggested reviewers: pratham-mishra04

Merge Risk: ⚪ Minimal · up to 8211d

No actionable issue was established in the reviewed changes; the PR is ready for normal checks before merging through the stack.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description contains only the repository template and placeholder text. It does not explain the changes, identify affected areas, provide test results, or address the breaking change and security … Replace the placeholder text with a completed description. Summarize the OAuth2 validation and authorization changes, select the applicable change types and affected areas, provide actual test steps and results, describe the issuer_url migr…
Docstring Coverage ⚠️ Warning Docstring coverage is 65.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 8 files. (6 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the change as tighter MCP OAuth2 server validation. It is concise and matches the main changes.
Linked Issues check ✅ Passed Issue #123 is closed and completed, so it supplies historical context only. No active directly linked issue adds coding requirements.
Out of Scope Changes check ✅ Passed The reviewed changes concern MCP OAuth configuration, discovery, issuance, consent, and related tests and documentation. These changes match the current PR intent. The summary shows no unrelated Files…
Full details: Description check

Explanation

The description contains only the repository template and placeholder text. It does not explain the changes, identify affected areas, provide test results, or address the breaking change and security considerations.

Resolution

Replace the placeholder text with a completed description. Summarize the OAuth2 validation and authorization changes, select the applicable change types and affected areas, provide actual test steps and results, describe the issuer_url migration and breaking change, address security considerations, and complete the checklist. Mark non-applicable sections as not applicable.

Full details: Docstring Coverage

Explanation

Docstring coverage is 65.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 8 files. (6 skipped: 6 unsupported.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

coderabbitai[bot]
coderabbitai Bot previously approved these changes Oct 3, 2026
@akshaydeo
akshaydeo force-pushed the backport/mcp-oauth2-server branch from 8211deb to bf39499 Compare October 3, 2026 06:29
@akshaydeo
akshaydeo force-pushed the backport/provider-endpoint-updates branch from 46be3ee to 1b176c5 Compare October 3, 2026 06:29

akshaydeo commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Oct 3, 8:43 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Oct 3, 8:55 AM UTC: Graphite rebased this pull request as part of a merge.
  • Oct 3, 8:57 AM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo
akshaydeo changed the base branch from backport/provider-endpoint-updates to graphite-base/7875 October 3, 2026 08:52
@akshaydeo
akshaydeo changed the base branch from graphite-base/7875 to main October 3, 2026 08:54
@akshaydeo
akshaydeo dismissed coderabbitai[bot]’s stale review October 3, 2026 08:54

The base branch was changed.

@mintlify

mintlify Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
bifrost 🟢 Ready View Preview Oct 3, 2026, 8:56 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

Hardens the Bifrost MCP OAuth2 authorization server against a Host-header poisoning attack: when discovery is enabled, every issuer reference (discovery documents, authorize redirect, JWT `iss`/`aud`) was previously derived from the unauthenticated, per-request `Host` header when `issuer_url` was not explicitly set. An attacker who could reach the always-public `/.well-known/` endpoints could spoof the `issuer`, `token_endpoint`, and `jwks_uri` that MCP clients trust. `issuer_url` is now required whenever `mcp_server_auth_mode` is `oauth` or `both`, enforced at config load time, at `PUT /api/config`, and in the JSON schema.

Additionally, the anonymous "session" consent identity is now gated on the consenting party being authenticated (verified dashboard session or SSO/IdP user), and the issuance endpoints (`/oauth2/register`, `/oauth2/authorize`, `/oauth2/token`) now return `404` in `headers` mode, matching the discovery endpoints. Input bounds are enforced on free-text DCR and authorize fields before any state is written, and all discovery responses carry `Cache-Control: no-store`.

- **`issuer_url` is now required** when `mcp_server_auth_mode` is `oauth` or `both`. `validateClientConfig` rejects configs missing it at load time; `updateConfig` rejects the write at the API layer. The JSON schema enforces this with an `allOf`/`if`/`then` constraint. The fallback to `BuildBaseURL(request)` is retained only in `headers` mode, where no OAuth issuer identity is served.
- **`oauth2IssuerURL` never derives the issuer from the request `Host` header while discovery is enabled.** If the invariant is bypassed, it returns `""` (yielding relative URLs) rather than an attacker-chosen absolute one.
- **Discovery responses are sent with `Cache-Control: no-store`** so a CDN or reverse proxy in front of Bifrost never serves a cached copy.
- **Issuance endpoints 404 in `headers` mode.** `/oauth2/register`, `/oauth2/authorize`, and `/oauth2/token` now check `issuanceEnabled()` (same predicate as the discovery handler) and return `404` when MCP OAuth is off, so disabling discovery disables the whole authorization server.
- **Session consent requires an authenticated consenting user.** `flowSubmit` returns `401` if `mode=session` is submitted without a verified dashboard session or IdP identity. `flowDetail` omits `session` from `available_modes` for unauthenticated callers. The auth-disabled bypass (`BifrostContextKeyAuthBypassed`) explicitly does not count as being signed in.
- **Input bounds on DCR and authorize parameters** are enforced before any row is written: `client_name` ≤ 2048 bytes, `scope` ≤ 4096 bytes, at most 32 `redirect_uris` of ≤ 2048 bytes each, `state` ≤ 8192 bytes, `code_challenge` ≤ 128 bytes. Oversize requests are refused with `400` before touching the store.
- **Documentation updated** to reflect that `issuer_url` is required (not optional), that `headers` mode 404s the issuance endpoints, that session mode requires a signed-in consenter, and to add troubleshooting entries for the new `401` and the `issuer_url must be set` rejection.
- **E2E and unit tests added** covering: oversize `client_name` refusal, anonymous session-mode `401`, auth-bypass not counting as identity, VK consent completing normally, discovery document pinning the configured issuer regardless of `Host`, `Cache-Control: no-store` on all three discovery documents, issuance endpoints 404ing in `headers` mode, all DCR/authorize oversize-field cases, and `issuer_url`-required validation at both load time and the config API.

- [x] Bug fix
- [x] Feature
- [ ] Refactor
- [x] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [x] Docs

```sh
go test ./transports/bifrost-http/handlers/... ./transports/bifrost-http/lib/...
```

Key scenarios to validate manually:

1. Start Bifrost with `mcp_server_auth_mode: oauth` and no `issuer_url` — boot must fail with `issuer_url must be set`.
2. `PUT /api/config` switching to `oauth` or `both` without `issuer_url` must return `400`.
3. In `headers` mode, `GET /.well-known/oauth-authorization-server`, `POST /oauth2/register`, `GET /oauth2/authorize`, and `POST /oauth2/token` must all return `404`.
4. In `both`/`oauth` mode with a spoofed `Host: evil.example` header, the discovery document's `issuer`, `token_endpoint`, and `jwks_uri` must reflect the configured `issuer_url`, not `evil.example`. The response must include `Cache-Control: no-store`.
5. Open a consent flow without a dashboard session — `available_modes` must not include `session`. Submitting `mode=session` must return `401` with `authenticated consenting user` in the body.
6. Open a consent flow with a valid dashboard session — `session` appears in `available_modes` and submitting it succeeds.
7. `POST /oauth2/register` with a 2049-char `client_name` must return `400 invalid_client_metadata`.

- [x] Yes
- [ ] No

`issuer_url` is now required whenever `mcp_server_auth_mode` is `oauth` or `both`. Existing deployments using either mode without a pinned `issuer_url` will fail to start after this change. **Migration:** add `oauth2_server_config.issuer_url` set to Bifrost's stable public URL to `config.json` (or via `PUT /api/config`) before upgrading.

This PR directly addresses a Host-header injection vulnerability in the OAuth2 discovery surface. The `/.well-known/` endpoints are always public (pre-auth), so an attacker reachable to those endpoints could previously poison the `issuer`, `token_endpoint`, and `jwks_uri` values that MCP clients cache and trust, enabling token substitution or redirect attacks. The fix makes `issuer_url` a hard requirement and removes the per-request fallback entirely while OAuth is enabled. The `Cache-Control: no-store` addition prevents a CDN from amplifying a poisoned response. The session-mode identity gate prevents an unauthenticated party who receives a consent link from minting themselves a valid `/mcp` token with no identity attached.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
@akshaydeo
akshaydeo force-pushed the backport/mcp-oauth2-server branch from bf39499 to 2a7aea3 Compare October 3, 2026 08:55
@akshaydeo
akshaydeo merged commit 02cad04 into main Oct 3, 2026
14 checks passed
@akshaydeo
akshaydeo deleted the backport/mcp-oauth2-server branch October 3, 2026 08:57

This branch was successfully deployed

1 active deployment
staging - docs — 2a7aea35 Deployed Oct 3, 2026 by mintlify[bot]
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.

1 participant