ANT-2026-CGR7Y3J8 · supabase/auth

other low

Severity Claude low · Security research firm - · Maintainer -

Discovered by Claude Mythos Preview

REPORT

Anthropic's analysis of this finding, sealed at approval.

ANT-2026-CGR7Y3J8: OAuth authorization code single-use not enforced atomically

In the authorization_code grant handler of the OAuth token endpoint, FindOAuthServerAuthorizationByCode is called outside the transaction with no row lock, and the subsequent tx.Destroy(authorization) at line 416 issues a DELETE that reports success even when zero rows are affected. Two concurrent POST /oauth/token requests carrying the same code can therefore both read the approved row, both pass client_id/PKCE/expiry validation, and both receive tokens. An attacker who already holds a valid code, the PKCE verifier, and client credentials can race the endpoint to mint multiple independent refresh-token sessions from a single code. This violates RFC 6749 §4.1.2 single-use semantics and defeats code-replay detection and any logic assuming one code yields one session.

Target

Project: supabase/auth
Location: internal/api/oauthserver/handlers.go:416
Discovery: static analysis — not yet dynamically reproduced

Technical Details

The authorization-code lookup runs outside the transaction with no FOR UPDATE lock, and tx.Destroy uses an unconditional DELETE ... WHERE id=? whose zero-rows-affected case is not checked. Because consumption is not atomic, both racers observe the row as valid and both proceed to token issuance before either delete takes effect. The consent path, by contrast, correctly uses FOR UPDATE SKIP LOCKED.

Reproduction

  1. Obtain a valid authorization code plus its PKCE verifier and client credentials.
  2. Send two (or more) POST /oauth/token requests with grant_type=authorization_code and the same code concurrently.
  3. Both requests read the approved authorization row before either deletes it.
  4. Both requests pass validation and each receives an access/refresh token pair.

[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]

Suggested Fix

Make authorization-code consumption atomic: either SELECT ... FOR UPDATE the authorization row before validating, or perform a conditional DELETE/UPDATE and verify the affected-row count is 1 before issuing tokens.

Acknowledgement

This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-CGR7Y3J8.


Reference: ANT-2026-CGR7Y3J8
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure

TIMELINE

Dates from discovery through public reveal.

  1. 2026-05-14 Reported to tracker
  2. 2026-05-14 Maintainer acknowledged
  3. 2026-05-15 Sent to maintainer
  4. 2026-07-13 Patch released
  5. 2026-08-18 Publicly revealed
PROVENANCE

SHA-3-512 hash:

df05c60b9a6d1c310232d67398246397993f18f427124c42e5df05c3f6c58d759aa78303fefeda597149fdba22e57fa9993c5d003e91978ad965b4dc992c72e4

Committed 2026-05-17 18:00 PT

Revealed 2026-08-18 07:12 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-CGR7Y3J8",
  "bug_class": "other",
  "claude_severity": "low",
  "commit_sha": null,
  "created_at": "2026-05-14T22:01:29+00:00",
  "description": "In the authorization_code grant handler of the OAuth token endpoint, FindOAuthServerAuthorizationByCode is called outside the transaction with no row lock, and the subsequent tx.Destroy(authorization) at line 416 issues a DELETE that reports success even when zero rows are affected. Two concurrent POST /oauth/token requests carrying the same code can therefore both read the approved row, both pass client_id/PKCE/expiry validation, and both receive tokens. An attacker who already holds a valid code, the PKCE verifier, and client credentials can race the endpoint to mint multiple independent refresh-token sessions from a single code. This violates RFC 6749 §4.1.2 single-use semantics and defeats code-replay detection and any logic assuming one code yields one session.",
  "discovered_at": "2026-05-10T00:00:00+00:00",
  "location": "internal/api/oauthserver/handlers.go:416",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "supabase/auth",
  "reproduction": [
    "1. Obtain a valid authorization code plus its PKCE verifier and client credentials.",
    "2. Send two (or more) POST /oauth/token requests with grant_type=authorization_code and the same code concurrently.",
    "3. Both requests read the approved authorization row before either deletes it.",
    "4. Both requests pass validation and each receives an access/refresh token pair."
  ],
  "technical_details": "The authorization-code lookup runs outside the transaction with no FOR UPDATE lock, and tx.Destroy uses an unconditional DELETE ... WHERE id=? whose zero-rows-affected case is not checked. Because consumption is not atomic, both racers observe the row as valid and both proceed to token issuance before either delete takes effect. The consent path, by contrast, correctly uses FOR UPDATE SKIP LOCKED.",
  "title": "OAuth authorization code single-use not enforced atomically",
  "vendor_severity": null
}