Retracted and rewritten 2026-08-07. The original body claimed confirmed
credential stuffing and a rate limiter that had "never recorded a single row."
Both claims were wrong, for the reasons in "What the evidence does not show"
below. The remaining anomaly is real but unattributed. Correcting the body rather
than appending, per CLAUDE.md.
What is actually observed
auth_audit_logs holds 4,172 sign_in_failed and 934 password_reset_request
rows. For a base of 21 real accounts that is a lot, and 4,106 of the failures carry
one identical user-agent — Chrome/140.0.7339.16 on Windows, no automation markers —
spread fairly evenly across all 24 hours.
Daily peaks reached 773. Volume collapses to single digits after 2026-08-05, which is
when the project hit HTTP 402.
That is the whole of the signal. It is unexplained and worth explaining.
What the evidence does NOT show
Two things I asserted here originally do not hold, and anyone reading this should not
repeat them:
1. "Zero of 4,172 map to an existing account" is not evidence of anything.
log_auth_event(p_user_id UUID DEFAULT NULL, ...) records the caller's uid, and its
own comment states auth.uid() is NULL for anon. A failed sign-in has no session, so
there is no uid to attribute. Every failed sign-in in this table is unattributed by
construction, whether it came from an attacker or from the owner mistyping a
password.
2. "rate_limit_attempts has 0 rows, so the limiter never fired" is wrong.
That table carries CREATE UNIQUE INDEX idx_rate_limit_unique ON rate_limit_attempts(identifier, attempt_type) — one row per identifier, not one per
attempt. It is a live counter that clears when the window lapses, not a historical
log. Zero rows means "nobody is currently inside a lockout window", which is the
correct state for a project that has been returning 402 for two days.
The write path was tested end-to-end against the local stack and works: calling
record_failed_attempt as anon through PostgREST returned HTTP 204 and the row
landed. check_rate_limit and record_failed_attempt are both SECURITY DEFINER,
both granted to anon and authenticated, and all three auth forms call them
(SignInForm.tsx:77/119, SignUpForm.tsx:90/126, ForgotPasswordForm.tsx:58/92).
What cannot be determined, and why
Whether this is an attack, one user in a retry loop, or a test harness. There is no
IP on any row — see #609, which is the hard blocker here. Rate limiting today keys on
email, so anything rotating addresses is unaffected regardless.
Next step
Establish attribution before designing a defence. That means #609 first. A limiter
built now could only key on email or a global counter, and neither survives contact
with an address-rotating client.
Related: #567 (CI drove its E2E suite against production Supabase — a plausible source
of a large share of these), #575 (moving E2E off cloud entirely).
What is actually observed
auth_audit_logsholds 4,172sign_in_failedand 934password_reset_requestrows. For a base of 21 real accounts that is a lot, and 4,106 of the failures carry
one identical user-agent —
Chrome/140.0.7339.16on Windows, no automation markers —spread fairly evenly across all 24 hours.
Daily peaks reached 773. Volume collapses to single digits after 2026-08-05, which is
when the project hit HTTP 402.
That is the whole of the signal. It is unexplained and worth explaining.
What the evidence does NOT show
Two things I asserted here originally do not hold, and anyone reading this should not
repeat them:
1. "Zero of 4,172 map to an existing account" is not evidence of anything.
log_auth_event(p_user_id UUID DEFAULT NULL, ...)records the caller's uid, and itsown comment states
auth.uid()is NULL for anon. A failed sign-in has no session, sothere is no uid to attribute. Every failed sign-in in this table is unattributed by
construction, whether it came from an attacker or from the owner mistyping a
password.
2. "
rate_limit_attemptshas 0 rows, so the limiter never fired" is wrong.That table carries
CREATE UNIQUE INDEX idx_rate_limit_unique ON rate_limit_attempts(identifier, attempt_type)— one row per identifier, not one perattempt. It is a live counter that clears when the window lapses, not a historical
log. Zero rows means "nobody is currently inside a lockout window", which is the
correct state for a project that has been returning 402 for two days.
The write path was tested end-to-end against the local stack and works: calling
record_failed_attemptasanonthrough PostgREST returned HTTP 204 and the rowlanded.
check_rate_limitandrecord_failed_attemptare bothSECURITY DEFINER,both granted to
anonandauthenticated, and all three auth forms call them(
SignInForm.tsx:77/119,SignUpForm.tsx:90/126,ForgotPasswordForm.tsx:58/92).What cannot be determined, and why
Whether this is an attack, one user in a retry loop, or a test harness. There is no
IP on any row — see #609, which is the hard blocker here. Rate limiting today keys on
email, so anything rotating addresses is unaffected regardless.
Next step
Establish attribution before designing a defence. That means #609 first. A limiter
built now could only key on email or a global counter, and neither survives contact
with an address-rotating client.
Related: #567 (CI drove its E2E suite against production Supabase — a plausible source
of a large share of these), #575 (moving E2E off cloud entirely).