ANT-2026-J5P6B5WQ · supabase/auth

other medium

Severity Claude medium · Security research firm - · Maintainer -

Discovered by Claude Mythos Preview

REPORT

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

ANT-2026-J5P6B5WQ: Banned user obtains fresh sessions via identity linking

In internal/api/identity.go, linkIdentityToUser loads the target user from the flow state and, via handleProviderCallback in internal/api/external.go, unconditionally calls issueRefreshToken. Unlike the password, OTP, and token-refresh paths, this branch never checks user.IsBanned(). A user who has been banned but still holds a not-yet-expired access token can call GET /user/identities/authorize, complete the external OAuth round-trip, and receive a brand-new access/refresh token pair. The result is a full bypass of banned_until, with the attacker able to keep refreshing indefinitely.

Target

Project: supabase/auth
Location: internal/api/identity.go:121
Discovery: static analysis — not yet dynamically reproduced

Technical Details

The root cause is a missing authorization check: the identity-linking callback path trusts the LinkingTargetID from the flow state and proceeds straight to issueRefreshToken without evaluating user.IsBanned(). Because ban enforcement is implemented per-path rather than centrally at token issuance, this branch slips through and mints credentials for a user who should be locked out.

Reproduction

  1. Admin sets banned_until on a misbehaving user.
  2. The user's existing access token remains valid for a few more minutes.
  3. User calls GET /user/identities/authorize?provider=google with that token and completes the Google OAuth flow.
  4. The callback reaches linkIdentityToUser, which then triggers issueRefreshToken.
  5. User receives a fresh refresh token and access token issued after the ban and can keep refreshing indefinitely.

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

Suggested Fix

Centralize the banned-user check so that every code path that issues tokens calls user.IsBanned() on the target user before minting credentials (e.g., enforce it inside issueRefreshToken or immediately before it in linkIdentityToUser/handleProviderCallback).

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-J5P6B5WQ.


Reference: ANT-2026-J5P6B5WQ
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-27 Patch released
  5. 2026-08-18 Publicly revealed
PROVENANCE

SHA-3-512 hash:

f4ae8eb21fc31172735c8c898c358ea26c0f0a576cac3b7b3872f71c33f2b58ed58a024e48ed2af87fd78242e01d5d6da94bb98cae47ba20acd7e67971b13486

Committed 2026-05-17 18:02 PT

Revealed 2026-08-18 07:12 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-J5P6B5WQ",
  "bug_class": "other",
  "claude_severity": "medium",
  "commit_sha": null,
  "created_at": "2026-05-14T22:00:13+00:00",
  "description": "In internal/api/identity.go, linkIdentityToUser loads the target user from the flow state and, via handleProviderCallback in internal/api/external.go, unconditionally calls issueRefreshToken. Unlike the password, OTP, and token-refresh paths, this branch never checks user.IsBanned(). A user who has been banned but still holds a not-yet-expired access token can call GET /user/identities/authorize, complete the external OAuth round-trip, and receive a brand-new access/refresh token pair. The result is a full bypass of banned_until, with the attacker able to keep refreshing indefinitely.",
  "discovered_at": "2026-05-10T00:00:00+00:00",
  "location": "internal/api/identity.go:121",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "supabase/auth",
  "reproduction": [
    "1. Admin sets banned_until on a misbehaving user.",
    "2. The user's existing access token remains valid for a few more minutes.",
    "3. User calls GET /user/identities/authorize?provider=google with that token and completes the Google OAuth flow.",
    "4. The callback reaches linkIdentityToUser, which then triggers issueRefreshToken.",
    "5. User receives a fresh refresh token and access token issued after the ban and can keep refreshing indefinitely."
  ],
  "technical_details": "The root cause is a missing authorization check: the identity-linking callback path trusts the LinkingTargetID from the flow state and proceeds straight to issueRefreshToken without evaluating user.IsBanned(). Because ban enforcement is implemented per-path rather than centrally at token issuance, this branch slips through and mints credentials for a user who should be locked out.",
  "title": "Banned user obtains fresh sessions via identity linking",
  "vendor_severity": null
}