Skip to content

PR-1: OAuth/session security hardening (P0)#230

Merged
PaulJPhilp merged 1 commit into
mainfrom
codex/pr-1-oauth-session-hardening
Feb 11, 2026
Merged

PR-1: OAuth/session security hardening (P0)#230
PaulJPhilp merged 1 commit into
mainfrom
codex/pr-1-oauth-session-hardening

Conversation

@PaulJPhilp
Copy link
Copy Markdown
Owner

@PaulJPhilp PaulJPhilp commented Feb 11, 2026

Summary

  • enforce refresh-token expiry during refresh grant and revoke expired refresh sessions
  • invalidate old access tokens on refresh-token rotation
  • add bounded cleanup for OAuth sessions + authorization codes (size caps + periodic expiry sweeps)
  • add bounded TTL cleanup for stream event store in streamable HTTP transport
  • add auth tests for token rotation invalidation and refresh-token expiry rejection

Files

  • packages/mcp-server/src/auth/oauth-config.ts
  • packages/mcp-server/src/auth/oauth-server.ts
  • packages/mcp-server/src/mcp-streamable-http.ts
  • packages/mcp-server/src/auth/tests/oauth-server.test.ts

Gate

  • bun run test:auth (packages/mcp-server): pass (47/47)
  • bun run typecheck (packages/mcp-server): pass

Note

Medium Risk
Touches authentication/token issuance and validation logic, so mistakes could cause unintended logouts or refresh failures. Changes are localized and covered by new unit tests plus configurable limits to reduce operational risk.

Overview
OAuth token/session hardening: OAuth2Server now tracks issuedAt and optional refreshTokenExpiresAt, enforces refresh-token expiry, and invalidates old access tokens on refresh by revoking all sessions tied to the refresh token and deleting stale aliases during validation/cleanup.

Memory/state bounding: Adds periodic expiry sweeps plus size caps for in-memory OAuth sessions and authorization codes (configurable via new env vars in mcp-streamable-http.ts), and makes the streamable HTTP in-memory event store bounded by TTL and max events. Adds tests covering refresh rotation invalidation and refresh-token expiry rejection.

Written by Cursor Bugbot for commit 5ab4299. This will update automatically on new commits. Configure here.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
effect-patterns Error Error Feb 11, 2026 9:03pm
effect-patterns-mcp-server Error Error Feb 11, 2026 9:03pm
effect-patterns-mcp-tier-v2 Error Error Feb 11, 2026 9:03pm

Request Review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

if (expiredAccessToken || staleAlias || expiredRefreshToken) {
this.sessions.delete(token);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup deletes sessions with valid refresh tokens prematurely

High Severity

cleanupExpiredState deletes sessions when expiredAccessToken is true, even if the refresh token is still valid. Since handleRefreshTokenGrant calls cleanupExpiredState() as its first action, a client trying to refresh after access-token expiry will find its session already garbage-collected and receive invalid_grant. This completely breaks the standard OAuth refresh flow—refresh tokens become unusable the moment the access token expires, despite potentially having hours of validity remaining. The same deletion occurs in validateBearerToken when someone presents an expired access token.

Additional Locations (1)

Fix in Cursor Fix in Web

private async handleAuthorizationCodeGrant(
params: TokenRequest,
): Promise<TokenResponse> {
this.cleanupExpiredState();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant double cleanup on every token request

Low Severity

handleTokenRequest calls cleanupExpiredState(), then dispatches to handleAuthorizationCodeGrant, handleRefreshTokenGrant, or handleClientCredentialsGrant, each of which also calls cleanupExpiredState() as its first action. This results in every token request performing the full cleanup scan (iterating all sessions and authorization codes) twice with no state change in between, adding unnecessary overhead.

Additional Locations (2)

Fix in Cursor Fix in Web

@PaulJPhilp PaulJPhilp merged commit 11282bb into main Feb 11, 2026
16 of 25 checks passed
@PaulJPhilp PaulJPhilp deleted the codex/pr-1-oauth-session-hardening branch February 11, 2026 21:52
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