CVE-2026-53512: Better Auth OAuth Refresh-token Replay Vulnerability
CRITICALCVE-2026-53512 is a CRITICAL (CVSS 9.1) oauth refresh-token replay vulnerability (CWE-287: Improper Authentication) affecting Better Auth libraries in the npm ecosystem. The issue involves oauth refresh-token replay via missing client authentication on oidc-provider and mcp plugins. Remediation is available by upgrading affected packages to version 1.6.11.
CVE-2026-53512 at a Glance
Security Impact
CVSS: 9.1
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N).
How to Fix CVE-2026-53512
To remediate this vulnerability, upgrade all affected Better Auth dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install better-auth@1.6.11
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls better-auth
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-53512, inspect your project dependencies using npm:
npm ls better-auth
Analyze the terminal output to locate any version of Better Auth in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.6.11), your application is affected.
Note: The npm ls better-auth command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-287: Improper Authentication, where when an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct. In Better Auth, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-53512 Affected Better Auth Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
better-auth
|
npm |
< 1.6.11
|
1.6.11
|
References
Frequently Asked Questions
What is CVE-2026-53512?
Which Better Auth versions are affected?
What version fixes CVE-2026-53512?
Is better-auth affected?
What is the CVSS score of CVE-2026-53512?
What CWE is associated with CVE-2026-53512?
How can I check npm dependencies for CVE-2026-53512?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-pw9m-5jxm-xr6h",
"modified" : "2026-07-20T19:24:31Z",
"published" : "2026-07-07T20:11:50Z",
"aliases" : [ "CVE-2026-53512" ],
"summary" : "Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins",
"details" : "### Am I affected?\n\nUsers are affected if all of the following are true:\n\n- 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`).\n- 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.\n- Their application uses `better-auth` at a version below the patched release.\n\nIf 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.\n\nFix:\n\n1. Upgrade to `better-auth@1.6.11` or later.\n2. Migrate from the deprecated `oidcProvider()` to `@better-auth/oauth-provider` when feasible. The new package enforces client authentication on both grants by default.\n3. If developers cannot upgrade their applications, see workarounds below.\n\n### Summary\n\nThe 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.\n\n### Details\n\nRFC 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.\n\nToken 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.\n\nTwo 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.\n\nThe newer `@better-auth/oauth-provider` package routes both grants through `validateClientCredentials` and is not affected.\n\n### Patches\n\nFixed in `better-auth@1.6.11`. 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).\n\nThe `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.\n\nThe deprecated `oidc-provider` plugin remains deprecated. The recommended migration path is `@better-auth/oauth-provider`.\n\n### Workarounds\n\nNone of these close the bug fully without a code patch.\n\n- **Migrate to `@better-auth/oauth-provider`** if your deployment can adopt the new plugin. It enforces `client_secret` on both grants.\n- **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.\n- **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.\n- **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.\n- **For the mcp endpoint specifically**: drop the wildcard CORS at an upstream proxy and replace with a tight allowlist.\n\n### Impact\n\n- **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 refreshes the expiration window each call.\n- **Resource access at the user's authorized scope**: every minted access token carries the original user's authorization scope, so the attacker reads or writes whatever the resource server grants for that scope.\n\n### Credit\n\nReported by @subhanUmer.\n\n### Resources\n\n- [CWE-306: Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html)\n- [CWE-287: Improper Authentication](https://cwe.mitre.org/data/definitions/287.html)\n- [CWE-345: Insufficient Verification of Data Authenticity](https://cwe.mitre.org/data/definitions/345.html)\n- [CWE-863: Incorrect Authorization](https://cwe.mitre.org/data/definitions/863.html)\n- [RFC 6749 §6: Refreshing an Access Token](https://datatracker.ietf.org/doc/html/rfc6749#section-6)\n- [OAuth 2.1 §4.3: Refresh Token](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1#section-4.3)",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"
}, {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "better-auth"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "1.6.11"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/better-auth/better-auth/security/advisories/GHSA-pw9m-5jxm-xr6h"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-53512"
}, {
"type" : "WEB",
"url" : "https://github.com/better-auth/better-auth/pull/9576"
}, {
"type" : "WEB",
"url" : "https://github.com/better-auth/better-auth/commit/1f2ff4215c4affff0b140b0c0a712c0dde35659c"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/better-auth/better-auth"
}, {
"type" : "WEB",
"url" : "https://github.com/better-auth/better-auth/releases/tag/v1.6.11"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-287", "CWE-306", "CWE-345", "CWE-863" ],
"severity" : "CRITICAL",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-07T20:11:50Z",
"nvd_published_at" : "2026-07-15T18:16:47Z"
}
}