Skip to content

maintenance: validate MCP OAuth exchanges - #4279

Open
zqr10159 wants to merge 4 commits into
apache:masterfrom
zqr10159:maintenance/oauth-flow-validation
Open

maintenance: validate MCP OAuth exchanges#4279
zqr10159 wants to merge 4 commits into
apache:masterfrom
zqr10159:maintenance/oauth-flow-validation

Conversation

@zqr10159

@zqr10159 zqr10159 commented Jul 30, 2026

Copy link
Copy Markdown
Member

What changed

  • implement authorization-code PKCE S256 end to end and consume failed-verifier codes
  • distinguish public (none) and confidential (client_secret_post) dynamic clients
  • remove the repository-known default confidential credential
  • bind approval to a one-time authorization transaction and consent nonce
  • enforce registered redirect URIs, response type, scopes, request-size limits, and URI encoding
  • expire and bound transactions, codes, access tokens, refresh tokens, registered clients, and rate-limit sources
  • limit open registration to 16 successful requests per minute for each resolved source instead of one global window
  • derive the source from the TCP peer by default; optionally honor a bounded forwarding chain only from explicitly trusted proxy CIDRs
  • retain a registered client for the complete lifetime of every issued refresh credential
  • reclaim the oldest never-activated registration at client capacity without evicting clients that hold live refresh credentials
  • rotate refresh tokens and make the advertised expires_in match server-side validation
  • require an explicit HTTPS public base URL in production instead of trusting Host

Registration lifetime and capacity

An unused registration expires after one hour. After a successful authorization-code or refresh-token exchange, the registered client remains valid through the complete 24-hour lifetime of the issued refresh token; rotation extends both together.

Registration admission is scoped by resolved source, so one caller cannot occupy every caller's window. The source table is capped at 4,096 entries. Direct deployments use the accepted TCP peer. Reverse-proxy deployments may set MCP_OAUTH_TRUSTED_PROXY_CIDRS to exact proxy networks; only those peers may supply X-Forwarded-For, the rightmost untrusted hop is selected, malformed or overlong chains fall back to the socket peer, and /0 trust is rejected.

The client store remains capped at 1,024 entries. Expired entries are pruned first. If anonymous clients fill the remaining capacity, the oldest client that has never received a refresh token is reclaimed. A client with a live refresh credential is not evicted. This keeps open MCP registration interoperable without allowing unapproved registrations to permanently reserve the store.

Operator and compatibility impact

Production requires:

MCP_OAUTH_APPROVAL_SECRET=<at least 32 characters>
MCP_OAUTH_PUBLIC_BASE_URL=https://mcp.example.com
MCP_OAUTH_TRUSTED_PROXY_CIDRS=<exact proxy CIDRs, only when a reverse proxy is used>

There is no built-in production client credential. OAuth-capable clients dynamically register and use PKCE. Existing tokens from the previous in-memory implementation do not survive a process restart and are intentionally invalid after this protocol correction.

Regression proof

The reviewed head failed three new outcome contracts:

  • after one peer filled the 16-request window, a different peer received HTTP 429 instead of HTTP 201;
  • a newly issued refresh token expired after its registered client;
  • 1,024 live but never-authorized registrations caused a replacement registration to receive HTTP 503.

All three now pass. Additional contracts prove the rate-limit source table remains bounded, trusted proxy chains resolve the rightmost untrusted address, untrusted peers cannot spoof forwarding headers, and invalid or address-family-wide proxy CIDRs are rejected.

Validation

  • cargo +1.88.0 test --manifest-path mcp-servers/mcp-bash-server/Cargo.toml — 67 passed
  • cargo +1.88.0 clippy --all-targets --all-features --manifest-path mcp-servers/mcp-bash-server/Cargo.toml -- -D warnings
  • cargo +1.88.0 fmt --manifest-path mcp-servers/mcp-bash-server/Cargo.toml -- --check
  • git diff --check

AI assistance: used for draft implementation and test iteration.
Human validation: reproduced the global-window, client/refresh lifetime, and unactivated-capacity failures; then ran the complete Rust suite, Clippy, rustfmt, peer/proxy admission, protected eviction, PKCE, expiry, refresh rotation, replay, redirect encoding, oversized-body, and public-base-URL contracts.
Risk notes: OAuth state remains process-local; a multi-instance deployment must provide sticky routing or replace the store with shared bounded state. A reverse proxy must be listed narrowly and must overwrite or safely append X-Forwarded-For.

@github-actions github-actions Bot added the doc Improvements or additions to documentation label Jul 30, 2026
@github-actions github-actions Bot added the infra label Jul 30, 2026
@zqr10159

Copy link
Copy Markdown
Member Author

Author remediation update:

The reviewed protocol gaps are now covered: PKCE S256 is transaction-bound and verified; public and confidential clients use distinct authentication rules; the repository-known credential is restricted to explicit development mode; authorization artifacts and access tokens have enforced expiry and bounded cleanup; unsupported refresh tokens are no longer issued; request bodies are bounded; redirect, scope, and response type checks are enforced; consent uses a one-time session-bound nonce; and public endpoints use a configured HTTPS origin with URI encoding.

Focused Rust validation passed (59 tests, fmt, and clippy). The current GitHub head also has successful MCP dev/release, backend, E2E, license, and label checks. The author-side blocker is resolved; maintainer review is still required.

@zqr10159
zqr10159 force-pushed the maintenance/oauth-flow-validation branch from 6be2c68 to ac02a5d Compare July 30, 2026 23:36
@zqr10159

Copy link
Copy Markdown
Member Author

Follow-up remediation: dynamic client registration is now rate-limited to 16 successful registrations per minute, idle clients expire after one hour, expired entries are pruned before the 1,024-client capacity check, and only a successful token exchange renews a client. The old head accepted the 61st anonymous registration; the new contract returns HTTP 429 with Retry-After and proves expired-capacity recovery. Full Rust validation passed: 61 tests, rustfmt, and Clippy with warnings denied.

@zqr10159
zqr10159 force-pushed the maintenance/oauth-flow-validation branch from 39eb042 to a6429a2 Compare July 31, 2026 00:53
@zqr10159

Copy link
Copy Markdown
Member Author

Follow-up for the reopened registration review is available on head a6429a297b.

The reviewed head failed three new outcome contracts:

  • a second TCP peer received HTTP 429 after the first peer filled the global 16-request window;
  • a newly issued 24-hour refresh token outlived its registered client;
  • 1,024 live but never-authorized registrations caused a legitimate replacement to receive HTTP 503.

The registration limiter is now per resolved source with a bounded 4,096-source table. It uses the socket peer by default. Deployments behind a proxy can explicitly configure exact trusted CIDRs; only those peers may provide X-Forwarded-For, the rightmost untrusted hop is used, malformed or overlong chains fall back to the socket peer, and /0 trust is rejected.

Successful token issue and rotation now keep the registered client valid through the complete refresh-token lifetime. At client capacity, only the oldest never-activated registration is reclaimed; a client with a live refresh credential is protected.

All 67 Rust tests, rustfmt, Clippy for all targets/features with warnings denied, and Git whitespace checks pass locally. The three old-head failures and trusted-proxy spoofing/bounded-state cases are included in that result. GitHub CI is running on the new head.

@zqr10159

Copy link
Copy Markdown
Member Author

Final current-head CI update for a6429a297b: all checks passed.

  • MCP Bash Server CI (dev): passed
  • MCP Bash Server CI (release): passed
  • Backend Build: passed
  • Backend Maven E2E: passed
  • Backend Image E2E: passed
  • License Checker: passed
  • labeler: passed

The PR is based on master and remains mergeable. No CI rerun or follow-up
code change was needed.

@zqr10159
zqr10159 marked this pull request as ready for review July 31, 2026 02:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc Improvements or additions to documentation infra

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant