ANT-2026-96NHD13X · supabase/auth
auth-bypass low
Severity Claude low · Security research firm - · Maintainer -
Discovered by Claude Mythos Preview
Anthropic's analysis of this finding, sealed at approval.
ANT-2026-96NHD13X: Factor-type string mismatch prevents session AAL downgrade
In supabase/auth, DowngradeSessionsToAAL1 (internal/models/factor.go:406) issues a DELETE on AMR claims with authentication_method = f.FactorType. FactorType is 'phone' or 'webauthn', but AMR rows are stored as 'mfa/phone' and 'mfa/webauthn' via AuthenticationMethod.String(), so the delete matches nothing for those factor types (TOTP matches by coincidence). Although sessions.aal is set to 'aal1', the next token issuance re-derives AAL from the surviving AMR claims and re-promotes the session to AAL2. An attacker who already holds a compromised AAL2 session elevated via phone or WebAuthn therefore keeps AAL2 access even after the victim unenrolls that factor.
Target
Project: supabase/auth
Location: internal/models/factor.go:406
Discovery: static analysis — not yet dynamically reproduced
Technical Details
The root cause is a string-constant mismatch between the FactorType values ('phone', 'webauthn') used in the DELETE WHERE clause and the AuthenticationMethod.String() values ('mfa/phone', 'mfa/webauthn') actually stored in the amr table (internal/models/factor.go:97-100, internal/models/amr.go:35). Because the WHERE clause never matches for phone/WebAuthn, the stale AMR claim survives and CalculateAALAndAMR in internal/models/sessions.go recomputes the session as AAL2 on every refresh, silently defeating the intended downgrade.
Reproduction
- Attacker compromises a victim session that has been elevated to AAL2 via phone or WebAuthn MFA
- Victim notices suspicious activity and unenrolls the phone/WebAuthn factor
- DowngradeSessionsToAAL1 runs but its AMR DELETE matches zero rows due to the 'phone' vs 'mfa/phone' mismatch
- On the attacker's next token refresh, CalculateAALAndAMR finds the surviving 'mfa/phone' or 'mfa/webauthn' AMR claim and re-promotes the session to AAL2
- Attacker continues performing AAL2-gated operations indefinitely
[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]
Suggested Fix
Ensure factor unenrollment deletes AMR claims using the same authentication_method string that was stored (e.g., map FactorType to its AuthenticationMethod.String() form before the DELETE) so session AAL is recomputed without the removed factor.
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-96NHD13X.
Reference: ANT-2026-96NHD13X
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-05 Patch released
- 2026-08-18 Publicly revealed
SHA-3-512 hash:
55fda01fbdd16921ede118882fab035328376e34384c613c34ab13fbf2b26e1e879dd81353fd7c92787e0720f2815f2acb669f640083962a208b0f40c898d365
Committed 2026-05-17 17:59 PT
Revealed 2026-08-18 07:12 PT
Verify (download preimage.json)
Show preimage JSON
{
"ant_id": "ANT-2026-96NHD13X",
"bug_class": "auth-bypass",
"claude_severity": "low",
"commit_sha": null,
"created_at": "2026-05-14T22:02:05+00:00",
"description": "In supabase/auth, DowngradeSessionsToAAL1 (internal/models/factor.go:406) issues a DELETE on AMR claims with `authentication_method = f.FactorType`. FactorType is 'phone' or 'webauthn', but AMR rows are stored as 'mfa/phone' and 'mfa/webauthn' via AuthenticationMethod.String(), so the delete matches nothing for those factor types (TOTP matches by coincidence). Although sessions.aal is set to 'aal1', the next token issuance re-derives AAL from the surviving AMR claims and re-promotes the session to AAL2. An attacker who already holds a compromised AAL2 session elevated via phone or WebAuthn therefore keeps AAL2 access even after the victim unenrolls that factor.",
"discovered_at": "2026-05-10T00:00:00+00:00",
"location": "internal/models/factor.go:406",
"poc_sha256": null,
"preimage_version": 1,
"project": "supabase/auth",
"reproduction": [
"1. Attacker compromises a victim session that has been elevated to AAL2 via phone or WebAuthn MFA",
"2. Victim notices suspicious activity and unenrolls the phone/WebAuthn factor",
"3. DowngradeSessionsToAAL1 runs but its AMR DELETE matches zero rows due to the 'phone' vs 'mfa/phone' mismatch",
"4. On the attacker's next token refresh, CalculateAALAndAMR finds the surviving 'mfa/phone' or 'mfa/webauthn' AMR claim and re-promotes the session to AAL2",
"5. Attacker continues performing AAL2-gated operations indefinitely"
],
"technical_details": "The root cause is a string-constant mismatch between the FactorType values ('phone', 'webauthn') used in the DELETE WHERE clause and the AuthenticationMethod.String() values ('mfa/phone', 'mfa/webauthn') actually stored in the amr table (internal/models/factor.go:97-100, internal/models/amr.go:35). Because the WHERE clause never matches for phone/WebAuthn, the stale AMR claim survives and CalculateAALAndAMR in internal/models/sessions.go recomputes the session as AAL2 on every refresh, silently defeating the intended downgrade.",
"title": "Factor-type string mismatch prevents session AAL downgrade",
"vendor_severity": null
}