ANT-2026-GQ8G77M4 · supabase/auth

broken-access-control 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-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

  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.

[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

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:

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
}