CL-6391: unify MCP OAuth state onto ConnectStateStore - #160
Merged
Merged
Conversation
Cover the sealed-state round trip carrying a caller-shaped payload, one-shot consumption (a replayed MCP callback is rejected before a second token exchange), TTL expiry, cross-provider AAD rejection, and payloads the caller's parser refuses.
The MCP-server connect flow carried its own sealed-state machinery (TTL, nonce, AAD, cipher calls) beside the store ./pkce.ts already provides. The store now seals a caller-shaped payload inside its hardened envelope (user binding, TTL, single-use replay guard, per-flow AAD), the fixed-registry OAuth routes seal their verifier through the same shape, and the MCP routes consume it one-shot -- a replayed callback now dies at the state check instead of reaching the token endpoint a second time.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The MCP-server OAuth connect flow (
packages/connections/src/mcp-oauth-routes.ts) carried its own sealed-state machinery — TTL, nonce minting, AAD, cipher calls — in parallel to the one-shotConnectStateStorethatpkce.tsalready provides for the fixed-registry OAuth connectors.createConnectStateStoreis now parameterized over a caller-shaped payload: the store owns the hardened envelope (user binding, TTL, per-flow AAD domain separation, single-use replay guard), while each connect flow declares and parses its own payload schema at the trust boundary.oauth-routes.tsseals its verifier payload through the same shape (the CL-6394 empty-verifier handling for non-PKCE flows is preserved).mcp-oauth-routes.tsdrops its duplicate machinery entirely and consumes the shared store one-shot; the PKCEcodeVerifiercarry from CL-6394: route OAuth connects through the tenant-scoped mount; one shared persist-and-seed sequence #149 is intact.Why
Hardening and dedup: two implementations of security-sensitive state handling meant two places to audit and keep aligned. The MCP flow now inherits the shared store's single-use consumption — a replayed callback is rejected at the state check instead of reaching the token endpoint a second time — plus the same replay-guard sweep and domain-separated AAD the other connect flows already had.
Tests
Store-level: payload round-trip, one-shot consumption (replay rejected), TTL expiry, cross-provider AAD rejection, parser-refused payloads, restart/key-rotation behavior. Route-level: a replayed MCP callback redirects with
state_expiredwithout a second token exchange; the full connect-flow suite (stub RFC 9728/8414/7591 authorization server) is unchanged and green.Refs CL-6391 (slate ticket — further items remain).