CVE-2026-53517
JavaScript vulnerability analysis and mitigation

Overview

CVE-2026-53517 is a race condition vulnerability in the Better Auth OAuth provider library for TypeScript, allowing authenticated users to bypass refresh token revocation by exploiting concurrent token redemption. The flaw affects @better-auth/oauth-provider versions >= 1.6.0 and < 1.6.11, and the embedded plugin in better-auth versions >= 1.4.8-beta.7 and < 1.6.0. It was first published on May 31, 2026, added to the GitHub Advisory Database on July 7, 2026, and published by NVD on July 15, 2026. The vulnerability carries a CVSS v3.1 base score of 8.1 (High) and a CVSS v4.0 base score of 7.6 (High) (GitHub Advisory, Github Advisory).

Technical details

The root cause is a non-atomic read/validate/revoke/mint sequence on the oauthRefreshToken database row in the POST /oauth2/token endpoint's refresh_token grant handler (CWE-362, CWE-367). The adapter.update predicate is keyed only on id, not on revoked IS NULL, so two concurrent requests presenting the same parent refresh token both pass the revocation check before either write completes — a classic TOCTOU race condition (CWE-367). Additionally, the schema lacked a UNIQUE constraint on oauthRefreshToken.token, so concurrent inserts did not collide on a database-level violation. The result is a forked refresh-token family from a single parent token, violating RFC 9700 §4.14's requirement for atomic family invalidation on detected reuse (CWE-294, CWE-613) (GitHub Advisory, Fix Commit).

Impact

Successful exploitation allows an attacker holding a single stolen refresh token to maintain indefinite unauthorized access by creating forked refresh-token families that survive any single revocation attempt — as long as the attacker holds at least one branch of the forked family. Legitimate users whose tokens have been forked do not trigger family invalidation when they refresh, because the attacker's branch has already swapped the parent row, effectively bypassing detection. The impact is high on both confidentiality and integrity (access to protected resources at the victim's authorization scope), with no availability impact (GitHub Advisory).

Exploitability

No public proof-of-concept exploit code has been reported, and there is no evidence of in-the-wild exploitation as of the time of disclosure (Github Advisory). The EPSS score is approximately 0.41% (Feedly data) to 0.24% (GitHub Advisory Database), placing it in a low exploitation probability tier. The vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. Exploitation requires the attacker to already possess a valid refresh token (low privileges required) and the ability to send concurrent HTTP requests, which is achievable via scripting or timing attacks against SPAs sharing tokens across tabs or mobile clients retrying after failures (GitHub Advisory).

Exploitation steps

  1. Obtain a valid refresh token: Acquire a refresh token for a target user — either through theft (e.g., XSS, token leakage), a compromised session, or by controlling a legitimate account with offline_access scope.
  2. Identify the vulnerable endpoint: Confirm the target application uses @better-auth/oauth-provider >= 1.6.0 and < 1.6.11 (or better-auth >= 1.4.8-beta.7 and < 1.6.0) and that the offline_access scope is in use, meaning refresh tokens are minted.
  3. Send concurrent token refresh requests: Using a script or tool (e.g., curl with &, Python asyncio, or a browser with multiple tabs), simultaneously submit two or more POST /oauth2/token requests with grant_type=refresh_token and the same stolen refresh token value before either request's revocation write completes.
  4. Collect forked refresh tokens: Both concurrent requests pass the revoked check and each receives a new, distinct refresh token — creating a forked family. The attacker retains one branch while the legitimate user may receive the other.
  5. Maintain persistent access: Use the attacker-held branch to continue refreshing access tokens indefinitely. Even if the legitimate user's branch is revoked, the attacker's branch remains valid until its refreshTokenExpiresAt (default 7 days), which resets with each successful rotation (GitHub Advisory, Fix Commit).

Indicators of compromise

  • Network: Multiple near-simultaneous POST /oauth2/token requests with grant_type=refresh_token and the same refresh_token value arriving within milliseconds of each other from the same or different IP addresses.
  • Logs: OAuth token endpoint logs showing two or more successful token issuances (HTTP 200 with refresh_token in response) for the same parent refresh token value within a very short time window; subsequent access token usage from unexpected IP addresses or user agents.
  • Database: Multiple oauthRefreshToken rows sharing the same parentId or originating from the same parent token, with different token values and close createdAt timestamps — indicating a forked family.
  • Application Behavior: A user's session being invalidated or their refresh token returning invalid_grant unexpectedly, while access continues from another source — suggesting the legitimate user lost the race and the attacker holds the winning branch (GitHub Advisory).

Mitigation and workarounds

The primary fix is to upgrade to @better-auth/oauth-provider@1.6.11 or better-auth@1.6.0 (for embedded plugin users), which introduces an atomic compare-and-swap (UPDATE ... WHERE id = ? AND revoked IS NULL) on the parent refresh token row, ensuring only one concurrent rotation wins and the loser receives invalid_grant (v1.6.11 Release). Existing database installations should manually add the unique constraint: CREATE UNIQUE INDEX oauth_refresh_token_token_uniq ON "oauthRefreshToken" (token);. If immediate upgrade is not possible, partial mitigations include: configuring the database adapter for serializable isolation or row-level locking on oauthRefreshToken; setting a very short refreshTokenExpiresIn (e.g., 60 seconds) to limit attacker persistence; implementing client-side mutex for refresh token usage; or disabling offline_access scope entirely to prevent refresh token issuance (GitHub Advisory).

Community reactions

The vulnerability was reported by community researcher @chdanielmueller and fixed by the Better Auth maintainers in a coordinated disclosure (GitHub Advisory). The fix was included in the v1.6.11 release alongside several other security hardening changes, including fixes for magic-link race conditions, device authorization binding, and SSRF vulnerabilities in the SSO plugin (v1.6.11 Release). The vulnerability was noted in CISA's weekly vulnerability bulletin (SB26-201) and received coverage from automated vulnerability tracking services shortly after NVD publication.

Additional resources


SourceThis report was generated using AI

Related JavaScript vulnerabilities:

CVE ID

Severity

Score

Technologies

Component name

CISA KEV exploit

Has fix

Published date

CVE-2026-77415CRITICAL9.3
  • JavaScript logoJavaScript
  • jsonata
NoYesAug 21, 2026
CVE-2026-77414CRITICAL9.3
  • JavaScript logoJavaScript
  • jsonata
NoYesAug 21, 2026
CVE-2026-77413CRITICAL9.3
  • JavaScript logoJavaScript
  • jsonata
NoYesAug 21, 2026
CVE-2026-63421HIGH7.5
  • JavaScript logoJavaScript
  • @keystone-6/core
NoYesAug 21, 2026
CVE-2026-53509MEDIUM5.7
  • JavaScript logoJavaScript
  • @aborruso/ckan-mcp-server
NoYesAug 21, 2026

Free Vulnerability Assessment

Benchmark your Cloud Security Posture

Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.

Request assessment

Get a personalized demo

Ready to see Wiz in action?

"Best User Experience I have ever seen, provides full visibility to cloud workloads."
David EstlickCISO
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
Adam FletcherChief Security Officer
"We know that if Wiz identifies something as critical, it actually is."
Greg PoniatowskiHead of Threat and Vulnerability Management