Skip to content

test: catch the NextAuth signout loop and guard the shared request budget - #24

Merged
saqibmanan merged 1 commit into
CIfrom
test/nextauth-loop-regression
Sep 3, 2026
Merged

test: catch the NextAuth signout loop and guard the shared request budget#24
saqibmanan merged 1 commit into
CIfrom
test/nextauth-loop-regression

Conversation

@saqibmanan

Copy link
Copy Markdown
Contributor

Coverage for the incident fixed in DataSpaceFrontend#453.

What happened, and why nothing caught it

One client issued 228,211 requests in a day, peaking at 6,230/minute, exhausting the backend's per-IP rate limit and returning 429 to every other user behind the same NAT address.

Every page rendered. Every element was present. No console error appeared. The damage was entirely in the volume of requests, and no test asserted on that. The existing suite would have stayed green throughout.

Tests

1. Page loads must not loop on auth requests (smoke, regression)

Reads Chrome's performance log and counts /api/auth/ requests over a 12s window.

Measured on healthy dev
/ 4
/datasets 1
Ceiling 15

Well above healthy, orders of magnitude below a loop.

A second assertion covers signout specifically and requires zero — an anonymous page load has no session to end, so any signout at all is the bug. Split out deliberately: a future raised threshold could mask the count assertion, but cannot mask this one.

Counts what the browser sent, not what succeeded — a loop that is being rate-limited is still sending, so counting successful responses would hide exactly the case that matters.

2. Ordinary request volume is not throttled (api, smoke, regression)

A 20-request GraphQL burst — a few page views' worth — must not be throttled. The POST budget is shared per IP, so it is shared by everyone behind a gateway; if this fails, the limit has been set low enough to break normal browsing from a shared office address.

Deliberately does not try to reach the limit: doing so would spend the budget of every other client behind this address, which is the failure being guarded against. A companion assertion confirms the endpoint answers normally, so a 500 on every request cannot produce zero 429s and pass vacuously.

It also documents where limiting actually lives (api/middleware/rate_limit.py) — DRF's DEFAULT_THROTTLE_RATES was dead config and was misread as the cause during the incident (removed in DataSpaceBackend#143).

Verification

6 passed against dev. Both proven able to fail: the volume test against a lowered ceiling (reports the real counts and names the loop), the burst test against a 429-returning host. Green again on revert.

Targets CI because that is the ref the workflow checks out and the one callers use.

On 2026-09-03 one client issued 228,211 requests in a day, peaking at
6,230 per minute, to /api/auth/session, /api/auth/csrf and
/api/auth/signout. It exhausted the backend's per-IP rate limit and
returned 429s to every other user behind the same NAT address.

No test caught it. Every page rendered, every element was present, no
console error appeared - the damage was entirely in the volume of
requests, and nothing asserted on that.

Two regression tests:

- Page loads must not issue a runaway number of /api/auth/ requests.
  Measured on a healthy dev over 12s: 4 on the homepage, 1 on /datasets.
  The ceiling is 15 - well above healthy, orders of magnitude below a
  loop. A second assertion covers signout specifically, because an
  anonymous page load has no session to end, so any signout at all is
  the bug and no raised threshold can mask it.

- A 20-request burst of GraphQL POSTs must not be throttled. That is a
  few page views' worth, and the budget is shared per IP - so it is
  shared by everyone behind a gateway. Deliberately does not try to
  reach the limit: doing so would spend the budget of every other client
  behind this address, which is the failure being guarded against.

Reading the performance log rather than the DOM, and counting what the
browser sent rather than what succeeded - a loop being rate-limited is
still sending, so counting successful responses would hide it.

The rate-limit test also documents where limiting actually lives
(api/middleware/rate_limit.py). DRF's DEFAULT_THROTTLE_RATES was dead
config and was misread as the cause during the incident.

Verified against dev: 6 passed. Both proven able to fail - the volume
test against a lowered ceiling, the burst test against a 429 host.
@saqibmanan
saqibmanan merged commit ca936d6 into CI Sep 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant