feat(mcp): rotate MCP OAuth refresh tokens with replay detection - #8273
feat(mcp): rotate MCP OAuth refresh tokens with replay detection#8273andypalmi wants to merge 7 commits into
Conversation
Rotate the refresh token on every MCP refresh instead of keeping it stable: each refresh issues a new refresh token and records the previous one. A rotated-out token presented within a short grace window returns the current tokens, so a concurrent or retried refresh still succeeds; presented after the window it is treated as a replay and the grant is revoked (RFC 9700 section 4.14.2). Concurrent refreshes are serialised with a compare-and-swap on the current refresh token: the update only matches while the token is still current, so exactly one refresh rotates and the loser returns the winner's tokens from the shared cache rather than a token that never reached the row.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## feat/8271-mcp-audit-attribution #8273 +/- ##
==================================================================
Coverage ? 76.25%
==================================================================
Files ? 451
Lines ? 24175
Branches ? 6454
==================================================================
Hits ? 18435
Misses ? 5740
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The refresh route rejected any refresh token byRefreshToken could not find. For a rotated MCP token that is the normal case: its hash has moved to previousRefreshToken, so the guard short-circuited the grace window and replay detection in refreshToken() before either could run. Defer resolution to refreshToken() for MCP clients, which handles the current-or-previous token, the grace window, and replay revocation. Add a route-level test covering rotation, grace retry, and replay through /account/token.
…resh-token-rotation
…resh-token-rotation
…resh-token-rotation
…resh-token-rotation
|
it's unclear to me what we're fixing here |
|
It looks to me that we're treating a symptom before it presented it's effects; could this be postponed to 3.1? |
…resh-token-rotation # Conflicts: # forge/db/models/AccessToken.js
|
Yes, let's postpone this. Agreed it is security hardening rather than something the OAuth flow needs to function, so I'll pull it out of the stack into its own PR and target it separately (3.1 works for me). For context on what it does: the base refresh flow (exchanging a valid refresh token for a new access token) already lands in #8264 and is complete on its own. This PR adds hardening on top: rotating the refresh token on each use, a compare-and-swap so two concurrent refreshes converge on one result, a short grace window for retries, and replay detection that revokes the grant if an already-rotated refresh token is presented again (RFC 9700 section 4.14.2). None of that is required for refresh to work, which is why it can land later without blocking the rest of the stack. |
Summary
Rotates the MCP OAuth refresh token on every refresh, with a grace window and replay detection, replacing the stable (non-rotating) refresh token.
/account/token. A route-level test now drives rotation, grace retry, and replay through the endpoint.Part of #7423. Closes #8270.