GHSA-pw9m-5jxm-xr6h: Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins
### Am I affected? Users are affected if all of the following are true: - Their application uses `better-auth` and has enabled at least one of: `oidcProvider()` (imported from `better-auth/plugins/oidc-provider`), or `mcp()` (imported from `better-auth/plugins/mcp`). - Their application has at least one confidential OAuth client registered (any client with `type: "web" | "native" | "user-agent-based"` in the `oauthApplication` table, or any `trustedClients` entry without `type: "public"`). Public clients with PKCE are not affected. - Their application uses `better-auth` at a version below the patched release. If an application only uses `@better-auth/oauth-provider` (the canonical replacement for `oidc-provider`) and the `mcp` plugin is not enabled, it is not affected. Fix: 1. Upgrade to `[email protected]` or later. 2. Migrate from the deprecated `oidcProvider()` to `@better-auth/oauth-provider` when feasible. The new package enforces client authentication on both grants by default. 3. If developers cannot upgrade their applications, see workarounds below. ### Summary The legacy `oidcProvider` and `mcp` plugins each expose an OAuth 2.0 token endpoint whose `refresh_token` grant authenticates the request entirely on possession of the bound `refreshToken` row and a matching `client_id`. Neither plugin verifies the registered confidential client's `client_secret` on the refresh path. An attacker who obtains any valid `refresh_token` (via database read, log capture, browser-side XSS, or CORS-amplified script in the mcp case) and the public `client_id` can mint fresh access tokens and rotated refresh tokens until the chain is revoked. ### Details RFC 6749 §6 and OAuth 2.1 §4.3 require confidential clients to authenticate to the token endpoint on every grant, including refresh. The same plugins' `authorization_code` grant correctly enforces `client_secret` (the oidc-provider via `verifyStoredClientSecret`, the mcp plugin via raw equality), which proves the omission on the refresh path is a regression rather than a design choice. Token rotation issues a new `refresh_token` with each call, so a single leaked refresh-token grants indefinite access until the row is revoked or its `refreshTokenExpiresAt` (default 7 days) passes; rotation refreshes that window each call. Two adjacent issues on the mcp surface ship in the same patch. The mcp `authorization_code` grant uses raw `===` for client-secret comparison and ignores the `storeClientSecret: "encrypted" | "hashed"` configuration; the fix routes both grants through `verifyStoredClientSecret`. The mcp `/mcp/token` endpoint sets `Access-Control-Allow-Origin: *` unconditionally, which amplifies the refresh bypass in browser contexts; the fix narrows the CORS allowlist. The newer `@better-auth/oauth-provider` package routes both grants through `validateClientCredentials` and is not affected. ### Patches Fixed in `[email protected]`. The legacy `oidcProvider` and `mcp` token endpoints now require `client_secret` on the `refresh_token` grant for confidential clients, using the same constant-time comparison the `authorization_code` grant already used. Public clients are unaffected (they have no secret to enforce, and PKCE substitutes on the auth-code grant). The `Authorization: Basic` parser is fixed to follow RFC 6749 §2.3.1: the credential is split on the first colon and each half is percent-decoded. Client IDs and secrets that contain reserved characters now authenticate correctly. The `/mcp/token` endpoint's CORS configuration is narrowed in the same change (the wildcard `Access-Control-Allow-Origin: *` header is removed), matching the standalone `@better-auth/oauth-provider` package. The deprecated `oidc-provider` plugin remains deprecated. The recommended migration path is `@better-auth/oauth-provider`. ### Workarounds None of these close the bug fully without a code patch. - **Migrate to `@better-auth/oauth-provider`** if your deployment can adopt the new plugin. It enforces `client_secret` on both grants. - **Force all clients to public + PKCE**: set every client's `type: "public"` and require PKCE. The bug is unreachable when there is no `client_secret` to verify. - **Network-layer ingress restriction**: limit `/api/auth/oauth2/token` and `/api/auth/mcp/token` to known client IPs at the load balancer. Practical for server-to-server flows, not for end-user-device clients. - **Out-of-band refresh-token rotation**: on any suspicion of leak, run `db.deleteMany({ model: "oauthAccessToken", where: [{ field: "clientId", value: <id> }] })` to invalidate all refresh tokens for the affected client. - **For the mcp endpoint specifically**: drop the wildcard CORS at an upstream proxy and replace with a tight allowlist. ### Impact - **Indefinite confidential-client impersonation**: an attacker holding any valid `refresh_token` and the public `client_id` can mint access tokens and rotated refresh tokens indefinitely, until the row is revoked. Rotation re
GHSA-pw9m-5jxm-xr6h: Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins
Description
### Am I affected? Users are affected if all of the following are true: - Their application uses `better-auth` and has enabled at least one of: `oidcProvider()` (imported from `better-auth/plugins/oidc-provider`), or `mcp()` (imported from `better-auth/plugins/mcp`). - Their application has at least one confidential OAuth client registered (any client with `type: "web" | "native" | "user-agent-based"` in the `oauthApplication` table, or any `trustedClients` entry without `type: "public"`). Public clients with PKCE are not affected. - Their application uses `better-auth` at a version below the patched release. If an application only uses `@better-auth/oauth-provider` (the canonical replacement for `oidc-provider`) and the `mcp` plugin is not enabled, it is not affected. Fix: 1. Upgrade to `[email protected]` or later. 2. Migrate from the deprecated `oidcProvider()` to `@better-auth/oauth-provider` when feasible. The new package enforces client authentication on both grants by default. 3. If developers cannot upgrade their applications, see workarounds below. ### Summary The legacy `oidcProvider` and `mcp` plugins each expose an OAuth 2.0 token endpoint whose `refresh_token` grant authenticates the request entirely on possession of the bound `refreshToken` row and a matching `client_id`. Neither plugin verifies the registered confidential client's `client_secret` on the refresh path. An attacker who obtains any valid `refresh_token` (via database read, log capture, browser-side XSS, or CORS-amplified script in the mcp case) and the public `client_id` can mint fresh access tokens and rotated refresh tokens until the chain is revoked. ### Details RFC 6749 §6 and OAuth 2.1 §4.3 require confidential clients to authenticate to the token endpoint on every grant, including refresh. The same plugins' `authorization_code` grant correctly enforces `client_secret` (the oidc-provider via `verifyStoredClientSecret`, the mcp plugin via raw equality), which proves the omission on the refresh path is a regression rather than a design choice. Token rotation issues a new `refresh_token` with each call, so a single leaked refresh-token grants indefinite access until the row is revoked or its `refreshTokenExpiresAt` (default 7 days) passes; rotation refreshes that window each call. Two adjacent issues on the mcp surface ship in the same patch. The mcp `authorization_code` grant uses raw `===` for client-secret comparison and ignores the `storeClientSecret: "encrypted" | "hashed"` configuration; the fix routes both grants through `verifyStoredClientSecret`. The mcp `/mcp/token` endpoint sets `Access-Control-Allow-Origin: *` unconditionally, which amplifies the refresh bypass in browser contexts; the fix narrows the CORS allowlist. The newer `@better-auth/oauth-provider` package routes both grants through `validateClientCredentials` and is not affected. ### Patches Fixed in `[email protected]`. The legacy `oidcProvider` and `mcp` token endpoints now require `client_secret` on the `refresh_token` grant for confidential clients, using the same constant-time comparison the `authorization_code` grant already used. Public clients are unaffected (they have no secret to enforce, and PKCE substitutes on the auth-code grant). The `Authorization: Basic` parser is fixed to follow RFC 6749 §2.3.1: the credential is split on the first colon and each half is percent-decoded. Client IDs and secrets that contain reserved characters now authenticate correctly. The `/mcp/token` endpoint's CORS configuration is narrowed in the same change (the wildcard `Access-Control-Allow-Origin: *` header is removed), matching the standalone `@better-auth/oauth-provider` package. The deprecated `oidc-provider` plugin remains deprecated. The recommended migration path is `@better-auth/oauth-provider`. ### Workarounds None of these close the bug fully without a code patch. - **Migrate to `@better-auth/oauth-provider`** if your deployment can adopt the new plugin. It enforces `client_secret` on both grants. - **Force all clients to public + PKCE**: set every client's `type: "public"` and require PKCE. The bug is unreachable when there is no `client_secret` to verify. - **Network-layer ingress restriction**: limit `/api/auth/oauth2/token` and `/api/auth/mcp/token` to known client IPs at the load balancer. Practical for server-to-server flows, not for end-user-device clients. - **Out-of-band refresh-token rotation**: on any suspicion of leak, run `db.deleteMany({ model: "oauthAccessToken", where: [{ field: "clientId", value: <id> }] })` to invalidate all refresh tokens for the affected client. - **For the mcp endpoint specifically**: drop the wildcard CORS at an upstream proxy and replace with a tight allowlist. ### Impact - **Indefinite confidential-client impersonation**: an attacker holding any valid `refresh_token` and the public `client_id` can mint access tokens and rotated refresh tokens indefinitely, until the row is revoked. Rotation re
CVSS v3.1
Affected software
Run on your own infrastructure? Check whether these packages are installed with threat-finder — our free open-source scanner.
Technical Details
- Gcve Source
- db.gcve.eu
- Osv Id
- GHSA-pw9m-5jxm-xr6h
- Osv Schema Version
- 1.4.0
- Aliases
- ["CVE-2026-53512"]
- Ecosystems
- ["npm"]
- Database Specific Severity
- CRITICAL
- Cvss Version
- 3.1
Threat ID: 6a4e4ef9c9d9e3dbe328cf2e
Added to database: 07/08/2026, 13:22:01 UTC
Last updated: 07/08/2026, 13:22:01 UTC
Views: 1
Community Reviews
0 reviewsCrowdsource mitigation strategies, share intel context, and vote on the most helpful responses. Sign in to add your voice and help keep defenders ahead.
Want to contribute mitigation steps or threat intel context? Sign in or create an account to join the community discussion.
Actions
Need more coverage?
Upgrade to Pro Console for AI refresh and higher limits.
For incident response and remediation, OffSeq services can help resolve threats faster.
Latest Threats
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.