ANT-2026-GQ8G77M4 · supabase/auth
broken-access-control medium
Severity Claude medium · Security research firm - · Maintainer -
Discovered by Claude Mythos Preview
Anthropic's analysis of this finding, sealed at approval.
ANT-2026-GQ8G77M4: Banned users retain authenticated endpoint access
maybeLoadUserOrSession in internal/api/auth.go is the central middleware run on every authenticated request; it fetches the user from the DB (and thus has the fresh banned_until value) but does not call user.IsBanned(). Ban enforcement only happens at credential-issuing moments (password login, refresh-token grant, OTP verify). As a result, a user who is banned by an admin keeps a working access token for the rest of its lifetime and can still hit /user, /logout, MFA factor enrollment/unenrollment, identity link/unlink, /oauth/consent, and any downstream service trusting the GoTrue JWT. Since the middleware already performs a DB round-trip per request, adding the ban check there would cost nothing extra.
Target
Project: supabase/auth
Location: internal/api/auth.go:130
Discovery: static analysis — not yet dynamically reproduced
Technical Details
The root cause is a missing authorization check: maybeLoadUserOrSession returns errors for unknown users but omits the user.IsBanned() check after loading the row. Because bans are only evaluated when new tokens are issued, revocation is deferred to the next token issuance rather than the next request, leaving a window equal to the remaining JWT lifetime.
Reproduction
- Admin bans abusive user U via PUT /admin/users/{id} with ban_duration.
- U's browser still holds an access token valid for its remaining lifetime (default ~1 hour).
- U continues calling PUT /user to change email, DELETE /user/identities/{id} to unlink identities, POST /factors to enroll MFA, and any application API that trusts the GoTrue JWT.
- The ban only takes effect when U next attempts to refresh the token or re-login.
[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]
Suggested Fix
In the authentication middleware that loads the user from the database, deny the request when the loaded user is currently banned (call user.IsBanned() and return an auth error), so bans take effect on the very next request instead of the next token issuance.
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-GQ8G77M4.
Reference: ANT-2026-GQ8G77M4
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:
4cc104b66f304334de608e7b1d83258d56a4074462f120769c76b0b19de3b85259e44fd872fb497910d5a4ec7fed5fcbddf8392dfd896d018106f14850fc1537
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-GQ8G77M4",
"bug_class": "broken-access-control",
"claude_severity": "medium",
"commit_sha": null,
"created_at": "2026-05-14T22:00:06+00:00",
"description": "maybeLoadUserOrSession in internal/api/auth.go is the central middleware run on every authenticated request; it fetches the user from the DB (and thus has the fresh banned_until value) but does not call user.IsBanned(). Ban enforcement only happens at credential-issuing moments (password login, refresh-token grant, OTP verify). As a result, a user who is banned by an admin keeps a working access token for the rest of its lifetime and can still hit /user, /logout, MFA factor enrollment/unenrollment, identity link/unlink, /oauth/consent, and any downstream service trusting the GoTrue JWT. Since the middleware already performs a DB round-trip per request, adding the ban check there would cost nothing extra.",
"discovered_at": "2026-05-10T00:00:00+00:00",
"location": "internal/api/auth.go:130",
"poc_sha256": null,
"preimage_version": 1,
"project": "supabase/auth",
"reproduction": [
"1. Admin bans abusive user U via PUT /admin/users/{id} with ban_duration.",
"2. U's browser still holds an access token valid for its remaining lifetime (default ~1 hour).",
"3. U continues calling PUT /user to change email, DELETE /user/identities/{id} to unlink identities, POST /factors to enroll MFA, and any application API that trusts the GoTrue JWT.",
"4. The ban only takes effect when U next attempts to refresh the token or re-login."
],
"technical_details": "The root cause is a missing authorization check: maybeLoadUserOrSession returns errors for unknown users but omits the user.IsBanned() check after loading the row. Because bans are only evaluated when new tokens are issued, revocation is deferred to the next token issuance rather than the next request, leaving a window equal to the remaining JWT lifetime.",
"title": "Banned users retain authenticated endpoint access",
"vendor_severity": null
}