Skip to content

GHSA-7w99-5wm4-3g79 CVE-2026-53518

@better-auth/oauth-provider's OAuth authorization-code grant allows concurrent redemption when two token requests race the find-then-delete primitive

High npm
Severity
0.0 / 10
High · CVSS 4.0
Exploit chance (EPSS)
0.41%
Chance of exploitation in the next 30 days. Higher than 35.18% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-294 Authentication Bypass by Capture-replay
CWE-362 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
CWE-367 Time-of-check Time-of-use (TOCTOU) Race Condition
Fix
Available
2 packages have a fixed version

Description

Am I affected?

Users are affected if all of the following are true:

  • Their project depends on @better-auth/oauth-provider at a version >= 1.6.0, < 1.6.11, or uses the embedded plugin in better-auth >= 1.4.8-beta.7, < 1.6.0, or enables the legacy oidc-provider or mcp plugins from better-auth/plugins.
  • Their application exposes /api/auth/oauth2/token (or the legacy plugins' /oauth2/token and /mcp/token) as a token endpoint to OAuth/OIDC clients, including internal MCP clients (Claude Desktop, custom MCP tool callers, AI agents).
  • Their application has not implemented an external mitigation: a load-balancer-level idempotency cache keyed by code, a database trigger that rejects duplicate token issuance for the same authorization code, or a custom adapter override that performs an atomic compare-and-delete.

Fix:

  1. Upgrade to @better-auth/oauth-provider@1.6.11 or later. If developers use the legacy plugin paths from better-auth/plugins, upgrade better-auth to 1.6.11 or later.
  2. If developers cannot upgrade, see workarounds below.

Summary

The OAuth provider's POST /oauth2/token endpoint, on the authorization_code grant, redeems a single-use authorization code through a non-atomic find-then-delete sequence. Two concurrent requests with the same code value both pass the read step before either delete completes, then both proceed to PKCE verification and createUserTokens. Each surviving request mints a fresh access token, refresh token, and id token. RFC 6749 §4.1.2 requires authorization codes to be single-use; this primitive does not enforce that under concurrency.

Details

The same architectural primitive (find a single-use verification row, then delete it, then trust the row to authorize) is used in 20 other call sites across the codebase. The deletion primitive returns Promise<void>, discarding the row count surfaced by adapter.deleteMany, so no call site can detect "another caller already claimed this row". The fix lands at the primitive layer rather than at any individual call site.

The fix introduces a claimVerificationByIdentifier primitive at the internal-adapter layer that performs an atomic claim-and-return, replaces the find-then-delete pair at this call site, and migrates the highest-impact variant sites in the same release.

Patches

Fixed in @better-auth/oauth-provider@1.6.11 and better-auth@1.6.11 for the legacy oidc-provider and mcp plugin paths. All three token-exchange call sites now consume the verification row through internalAdapter.consumeVerificationValue, an atomic claim primitive that deletes the row and returns its prior value in one operation. The first request to arrive takes the row and mints tokens; concurrent racers observe an empty result and return invalid_grant.

Error-code consistency is also tightened on the @better-auth/oauth-provider token endpoint: the malformed-verification-value branches previously returned a project-specific invalid_verification code, which is not part of RFC 6749 §5.2's response error set. Both branches now return invalid_grant so spec-compliant clients can branch on the standard code without a special case.

Workarounds

None of these close the bug fully without a code patch. Upgrading is the only good path.

  • Network-layer: deploy an authorization-server-aware reverse proxy (Envoy, NGINX with Lua, custom Cloudflare Worker) that holds an in-flight registry keyed by the code parameter and serializes concurrent requests for the same code. Fragile under multi-instance deployments unless the registry is shared (Redis-backed).
  • Database-layer: add a SQL or Mongo uniqueness constraint that prevents two oauthAccessToken rows from being created with the same upstream code reference. Adapter-specific and not always feasible since the schema does not currently store the source code.
  • Application-layer: wrap deleteVerificationByIdentifier with a custom hook that uses adapter.deleteMany and surfaces the count, then injects an invalid_grant rejection when the count is zero. Requires forking the internal adapter.

Impact

  • Multiple independent token sets from a single authorization: forked access tokens, refresh tokens, and id tokens issued from the same code, all valid for the original user's authorization scope.
  • Detection bypass: standard OAuth single-use enforcement does not fire for the second redemption when both requests interleave through the read step.
  • Legacy-plugin reach: oidc-provider and mcp plugins share the primitive on the same surface, so deployments using them inherit the same impact.

Credit

Reported by @chdanielmueller.

Resources

Affected versions

npm

PackageVulnerableFixed in
@better-auth/oauth-provider
>= 1.6.0, < 1.6.11
1.6.11
better-auth
< 1.6.11
1.6.11

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

@better-auth/oauth-provider npm → 1.6.11

npm
npm install @better-auth/oauth-provider@1.6.11
yarn
yarn add @better-auth/oauth-provider@1.6.11
pnpm
pnpm add @better-auth/oauth-provider@1.6.11

better-auth npm → 1.6.11

npm
npm install better-auth@1.6.11
yarn
yarn add better-auth@1.6.11
pnpm
pnpm add better-auth@1.6.11

Severity breakdown

CVSS 4.0 0.0

CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N

Attack vector
Network
Attack complexity
Low
Attack requirements
Present
Privileges required
None
User interaction
Passive
Confidentiality (vulnerable system)
High
Integrity (vulnerable system)
High
Availability (vulnerable system)
None
Confidentiality (subsequent systems)
None
Integrity (subsequent systems)
None
Availability (subsequent systems)
None

CVSS 3.1 8.1

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
None

Weakness

CWE-294: Authentication Bypass by Capture-replay

A capture-replay flaw exists when the design of the product makes it possible for a malicious user to sniff network traffic and bypass authentication by replaying it to the server in question to the same effect as the original message (or with minor changes).

How to prevent it
  • Architecture and Design: Utilize some sequence or time stamping functionality along with a checksum which takes this into account in order to ensure that messages can be parsed only once.
  • Architecture and Design: Since any attacker who can listen to traffic can see sequence numbers, it is necessary to sign messages with some kind of cryptography to ensure that sequence numbers are not simply doctored along with content.

CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')

The product contains a concurrent code sequence that requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence operating concurrently.

How to prevent it
  • Architecture and Design: In languages that support it, use synchronization primitives. Only wrap these around critical code to minimize the impact on performance.
  • Architecture and Design: Use thread-safe capabilities such as the data access abstraction in Spring.
  • Architecture and Design: Minimize the usage of shared resources in order to remove as much complexity as possible from the control flow and to reduce the likelihood of unexpected conditions occurring. Additionally, this will minimize the amount of synchronization necessary and may even help to reduce the likelihood of a denial of service where an attacker may be able to repeatedly trigger a critical section (CWE-400).
  • Implementation: When using multithreading and operating on shared variables, only use thread-safe functions.
  • Implementation: Use atomic operations on shared variables. Be wary of innocent-looking constructs such as "x++". This may appear atomic at the code layer, but it is actually non-atomic at the instruction layer, since it involves a read, followed by a computation, followed by a write.
  • Implementation: Use a mutex if available, but be sure to avoid related weaknesses such as CWE-412.
  • Implementation: Avoid double-checked locking (CWE-609) and other implementation errors that arise when trying to avoid the overhead of synchronization.
  • Implementation: Disable interrupts or signals over critical parts of the code, but also make sure that the code does not go into a large or infinite loop.
  • Implementation: Use the volatile type modifier for critical variables to avoid unexpected compiler optimization or reordering. This does not necessarily solve the synchronization problem, but it can help.
  • Architecture and Design, Operation: Run your code using the lowest privileges that are required to accomplish the necessary tasks [REF-76]. If possible, create isolated accounts with limited privileges that are only used for a single task. That way, a successful attack will not immediately give the attacker access to the rest of the software or its environment. For example, database applications rarely need to run as the database administrator, especially in day-to-day operations.

CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition

The product checks the state of a resource before using that resource, but the resource's state can change between the check and the use in a way that invalidates the results of the check.

How to prevent it
  • Implementation: The most basic advice for TOCTOU vulnerabilities is to not perform a check before the use. This does not resolve the underlying issue of the execution of a function on a resource whose state and identity cannot be assured, but it does help to limit the false sense of security given by the check.
  • Implementation: When the file being altered is owned by the current user and group, set the effective gid and uid to that of the current user and group when executing this statement.
  • Architecture and Design: Limit the interleaving of operations on files from multiple processes.
  • Implementation, Architecture and Design: If you cannot perform operations atomically and you must share access to the resource between multiple processes or threads, then try to limit the amount of time (CPU cycles) between the check and use of the resource. This will not fix the problem, but it could make it more difficult for an attack to succeed.
  • Implementation: Recheck the resource after the use call to verify that the action was taken appropriately.
  • Architecture and Design: Ensure that some environmental locking mechanism can be used to protect resources effectively.
  • Implementation: Ensure that locking occurs before the check, as opposed to afterwards, such that the resource, as checked, is the same as it is when in use.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-r4xp-prcw-77qf
better-auth: OAuth Proxy lets an attacker sign in as another user
Critical Also affects better-auth
GHSA-965c-763c-88jm
better-auth: OAuth state can be used as a Magic Link to sign in as another user
Critical Also affects better-auth
GHSA-44jh-23m7-hpcf
@better-auth/drizzle-adapter: Concurrent requests can exceed rate limits on PostgreSQL
Medium Also affects better-auth
GHSA-q84f-53jg-9ppm
better-auth: Device login approval does not show the requesting client or permissions
High Also affects better-auth
GHSA-qq9h-g4jm-xgf3
Better Auth: Account takeover via pre-account hijacking on magic-link and email-OTP sign-in
High Also affects better-auth
GHSA-2vg6-77g8-24mp
Better Auth: Stale sessions persist after user deletion across admin, anonymous, and SCIM flows
Medium Also affects better-auth
GHSA-392p-2q2v-4372
Better Auth: OAuth refresh-token rotation forks the token family on concurrent redemption
High Also affects better-auth
GHSA-9h47-pqcx-hjr4
Better Auth has insecure cryptographic defaults in oidcProvider: alg=none advertised and plain PKCE accepted by default
Critical Also affects better-auth
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor