ANT-2026-J5P6B5WQ · supabase/auth
other medium
Severity Claude medium · Security research firm - · Maintainer -
Discovered by Claude Mythos Preview
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
- Admin sets banned_until on a misbehaving user.
- The user's existing access token remains valid for a few more minutes.
- User calls GET /user/identities/authorize?provider=google with that token and completes the Google OAuth flow.
- The callback reaches linkIdentityToUser, which then triggers issueRefreshToken.
- 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
Dates from discovery through public reveal.
- 2026-05-14 Reported to tracker
- 2026-05-14 Maintainer acknowledged
- 2026-05-15 Sent to maintainer
- 2026-07-27 Patch released
- 2026-08-18 Publicly revealed
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
}