Skip to main content

better-auth EUVDEUVD-2026-44735

| CVE-2026-53512 CRITICAL
Improper Authentication (CWE-287)
2026-07-07 https://github.com/better-auth/better-auth GHSA-pw9m-5jxm-xr6h
9.1
CVSS 4.0 · Vendor: https://github.com/better-auth/better-auth
Share

Severity by source

Vendor (https://github.com/better-auth/better-auth) PRIMARY
9.1 CRITICAL
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
vuln.today AI
7.4 HIGH

AV:N and PR:N because the token endpoint needs no client credentials, but AC:H because exploitation depends on separately obtaining a leaked refresh_token, a precondition outside attacker control; C/I high, A:N.

3.1 AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
4.0 AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/better-auth/better-auth).

CVSS VectorVendor: https://github.com/better-auth/better-auth

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
X

Lifecycle Timeline

1
Analysis Generated
Jul 07, 2026 - 20:51 vuln.today

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 26 npm packages depend on better-auth (19 direct, 7 indirect)

Ecosystem-wide dependent count for version 1.6.11.

DescriptionCVE.org

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 better-auth@1.6.11 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 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).

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 refreshes the expiration window each call.
  • 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.

Credit

Reported by @subhanUmer.

Resources

AnalysisAI

Confidential-client impersonation in better-auth below 1.6.11 lets an attacker who holds any leaked refresh_token and the public client_id mint fresh access tokens and rotated refresh tokens indefinitely. The deprecated oidcProvider() and mcp() plugins expose an OAuth 2.0 token endpoint whose refresh_token grant authenticates solely on the bound refresh-token row plus a matching client_id, never verifying the registered client_secret - a regression, since the same plugins' authorization_code grant does enforce the secret. No public exploit identified at time of analysis; the flaw was reported by @subhanUmer and fixed by the maintainers, with no CISA KEV listing or EPSS score supplied in the input.

Technical ContextAI

better-auth is a TypeScript/JavaScript authentication framework distributed on npm (pkg:npm/better-auth). The affected surface is its OAuth 2.0 / OIDC authorization-server functionality provided by two legacy plugins: oidcProvider() (better-auth/plugins/oidc-provider) and mcp() (better-auth/plugins/mcp), each of which stands up a token endpoint (/api/auth/oauth2/token and /api/auth/mcp/token respectively). The root cause maps to CWE-287 (Improper Authentication), and more precisely CWE-306 (Missing Authentication for Critical Function): RFC 6749 §6 and OAuth 2.1 §4.3 require confidential clients to authenticate to the token endpoint on every grant including refresh, but these plugins omit the client_secret check on the refresh path while enforcing it (via verifyStoredClientSecret in oidc-provider and raw equality in mcp) on the authorization_code path. Token rotation issues a new refresh_token per call and resets refreshTokenExpiresAt (default 7 days), so a single leak yields an indefinitely renewable session. Two adjacent mcp defects ship in the same patch: the mcp authorization_code grant used raw === comparison and ignored storeClientSecret ("encrypted"|"hashed"), and the /mcp/token endpoint returned Access-Control-Allow-Origin: * unconditionally, which lets browser-side script (XSS/CORS) reach the endpoint and amplify the bypass. The newer @better-auth/oauth-provider package routes both grants through validateClientCredentials and is not affected.

RemediationAI

Vendor-released patch: better-auth@1.6.11 - upgrade to 1.6.11 or later, which requires client_secret on the refresh_token grant for confidential clients using the same constant-time comparison the authorization_code grant already used, routes the mcp authorization_code grant through verifyStoredClientSecret (honoring storeClientSecret), fixes the RFC 6749 §2.3.1 Authorization: Basic parser, and removes the wildcard CORS header from /mcp/token (per https://github.com/better-auth/better-auth/releases/tag/v1.6.11 and advisory GHSA-pw9m-5jxm-xr6h). Where feasible, migrate from the deprecated oidcProvider() to @better-auth/oauth-provider, which enforces client authentication on both grants by default. If you cannot upgrade, effective workarounds each carry trade-offs: force every client to type "public" plus mandatory PKCE (removes the client_secret the bug fails to verify, but drops confidential-client semantics); restrict /api/auth/oauth2/token and /api/auth/mcp/token at the load balancer to known client IPs (works for server-to-server flows, not end-user-device clients); for mcp specifically, strip the wildcard Access-Control-Allow-Origin at an upstream proxy and replace it with a tight allowlist; and on any suspicion of leak, invalidate all refresh tokens for the affected client via db.deleteMany({ model: "oauthAccessToken", where: [{ field: "clientId", value: <id> }] }). None of the workarounds fully close the flaw without the code patch.

CVE-2011-3544 CRITICAL POC
9.8 Oct 19

Oracle Java SE JDK/JRE 7 and 6 Update 27 and earlier allows remote code execution with complete system compromise throug

CVE-2019-11043 CRITICAL POC
9.8 Oct 28

In PHP versions 7.1.x below 7.1.33, 7.2.x below 7.2.24 and 7.3.x below 7.3.11 in certain configurations of FPM setup it

CVE-2014-6271 CRITICAL POC
9.8 Sep 24

GNU Bash through 4.3 processes trailing strings after function definitions in the values of environment variables, which

CVE-2013-0422 CRITICAL POC
9.8 Jan 10

Multiple vulnerabilities in Oracle Java 7 before Update 11 allow remote attackers to execute arbitrary code by (1) using

CVE-2016-3714 HIGH POC
8.4 May 05

The (1) EPHEMERAL, (2) HTTPS, (3) MVG, (4) MSL, (5) TEXT, (6) SHOW, (7) WIN, and (8) PLT coders in ImageMagick before 6.

CVE-2017-12617 HIGH POC
8.1 Oct 04

When running Apache Tomcat versions 9.0.0.M1 to 9.0.0, 8.5.0 to 8.5.22, 8.0.0.RC1 to 8.0.46 and 7.0.0 to 7.0.81 with HTT

CVE-2016-8735 CRITICAL POC
9.8 Apr 06

Remote code execution is possible with Apache Tomcat before 6.0.48, 7.x before 7.0.73, 8.x before 8.0.39, 8.5.x before 8

CVE-2014-7169 CRITICAL POC
9.8 Sep 25

GNU Bash through 4.3 bash43-025 processes trailing strings after certain malformed function definitions in the values of

CVE-2014-0160 HIGH POC
7.5 Apr 07

The (1) TLS and (2) DTLS implementations in OpenSSL 1.0.1 before 1.0.1g do not properly handle Heartbeat Extension packe

CVE-2016-5195 HIGH POC
7.0 Nov 10

Race condition in mm/gup.c in the Linux kernel 2.x through 4.x before 4.8.3 allows local users to gain privileges by lev

CVE-2013-2423 LOW POC
3.7 Apr 17

Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 Update 17 and earlier, and

CVE-2023-4911 HIGH POC
7.8 Oct 03

Local privilege escalation in the GNU C Library (glibc) dynamic loader ld.so allows unprivileged local users on affected

Share

EUVD-2026-44735 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy