ANT-2026-CT4ZXP4Z · supabase/auth

privilege-escalation 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-CT4ZXP4Z: Parent refresh token of post-MFA token yields AAL2 without MFA

In supabase/auth's refresh-token v1 flow, completing MFA issues a new AAL2 refresh token whose parent is the pre-MFA AAL1 token. RefreshTokenGrant has a grace path that, when presented with a revoked token that is the parent of the currently-active token, returns the active child. An attacker who captured the AAL1 refresh token before the victim completed MFA can replay it afterward; the server walks to the AAL2 child and mints an AAL2 access token. The attacker thus obtains a post-MFA session without ever performing MFA.

Target

Project: supabase/auth
Location: internal/tokens/service.go:376
Discovery: static analysis — not yet dynamically reproduced

Technical Details

The parent-chain grace logic in RefreshTokenGrant does not compare the authentication assurance level of the presented parent token against that of the active child before returning the child. Because the post-MFA token is chained (parent = pre-MFA token) rather than starting a fresh chain, a lower-AAL revoked parent is accepted as proof of ownership of a higher-AAL child.

Reproduction

  1. Attacker steals victim's refresh token R1 issued at password login (AAL1).
  2. Victim completes MFA; server issues R2 (AAL2) with parent=R1 and revokes R1.
  3. Attacker POSTs /token?grant_type=refresh_token with R1.
  4. Service sees R1 is revoked, finds active child R2, and returns a new token pair derived from R2's session with aal2.
  5. Attacker now holds an AAL2 session despite never performing MFA.

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

Suggested Fix

On AAL step-up, either start a new refresh-token chain with no parent, or record the AAL on each refresh token and refuse to return a child whose AAL is higher than that of the presented parent.

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


Reference: ANT-2026-CT4ZXP4Z
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:

ae3c71ba7736245576faf907403ec3bff4c5ef68974f237b162ac51864c126598f6665d471646fe4f1c626050400d434b4649eec1d04ac38d2db4ffba416987b

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-CT4ZXP4Z",
  "bug_class": "privilege-escalation",
  "claude_severity": "medium",
  "commit_sha": null,
  "created_at": "2026-05-14T22:01:08+00:00",
  "description": "In supabase/auth's refresh-token v1 flow, completing MFA issues a new AAL2 refresh token whose parent is the pre-MFA AAL1 token. RefreshTokenGrant has a grace path that, when presented with a revoked token that is the parent of the currently-active token, returns the active child. An attacker who captured the AAL1 refresh token before the victim completed MFA can replay it afterward; the server walks to the AAL2 child and mints an AAL2 access token. The attacker thus obtains a post-MFA session without ever performing MFA.",
  "discovered_at": "2026-05-10T00:00:00+00:00",
  "location": "internal/tokens/service.go:376",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "supabase/auth",
  "reproduction": [
    "1. Attacker steals victim's refresh token R1 issued at password login (AAL1).",
    "2. Victim completes MFA; server issues R2 (AAL2) with parent=R1 and revokes R1.",
    "3. Attacker POSTs /token?grant_type=refresh_token with R1.",
    "4. Service sees R1 is revoked, finds active child R2, and returns a new token pair derived from R2's session with aal2.",
    "5. Attacker now holds an AAL2 session despite never performing MFA."
  ],
  "technical_details": "The parent-chain grace logic in RefreshTokenGrant does not compare the authentication assurance level of the presented parent token against that of the active child before returning the child. Because the post-MFA token is chained (parent = pre-MFA token) rather than starting a fresh chain, a lower-AAL revoked parent is accepted as proof of ownership of a higher-AAL child.",
  "title": "Parent refresh token of post-MFA token yields AAL2 without MFA",
  "vendor_severity": null
}