Skip to content

fix(auth): make the rate limiter engage by making the failure budget global - #10

Merged
Ho1yShif merged 3 commits into
mainfrom
fix-auth-rate-limit
Jul 30, 2026
Merged

fix(auth): make the rate limiter engage by making the failure budget global#10
Ho1yShif merged 3 commits into
mainfrom
fix-auth-rate-limit

Conversation

@Ho1yShif

Copy link
Copy Markdown
Collaborator

Summary

The gate's rate limiter never engaged in production. It keyed its failure counter on the rightmost X-Forwarded-For entry, which behind Render's edge is a proxy hop that varies per request — so every failed attempt landed on a fresh counter and the 429 never came.

Failed attempts now share one global budget (10 per minute), extracted into render-auth-limiter.mjs so the stateful part is testable on its own with an injected clock.

A per-address budget can't provide the property worth guaranteeing here anyway: this gate fronts a single shared secret, so what matters is a ceiling on the total guess rate, and an attacker rotating source addresses just gets a fresh allowance. Only requests that were going to be refused anyway spend the budget, so a valid token never consumes it and is never subject to it — the lockout can't be aimed at the token holder.

Tests

Adds npm run test:wrapper (node:test, stdlib only, no Docker) and a CI job that runs it ahead of the smoke test, so a broken gate fails in seconds instead of after pulling the base image. Covers the failure budget's window arithmetic and the real proxy process answering 401/429/200/501 over a stub upstream — including a direct regression test that 429 still arrives when the rightmost X-Forwarded-For varies per request.

Verified end to end: ./render-smoke-test.sh passes all six checks against the built image, including bad tokens getting 401 up to the limit then 429, with the valid token still returning 200.

Notes

  • Dockerfile.render must copy the new module too, or the container crashes at startup on a missing import.
  • Trade-off documented in the README: anyone can hold the shared budget empty, and while it is empty a wrong token is answered 429 rather than 401 — so a stale token looks like rate limiting. A request with the right token is unaffected.

Ho1yShif added 3 commits July 29, 2026 22:03
The limiter keyed on the rightmost X-Forwarded-For entry, which on Render is a
proxy hop that varies per request, so every failed attempt landed on a fresh
counter and 429 never arrived — 25 bad tokens in a row all got 401 in
production.

Replace per-client tracking with one global fixed-window failure budget
(render-auth-limiter.mjs): it needs no client address, cannot be evaded by
rotating source addresses, and a valid token never touches it, so the lockout
still can't be aimed at the token holder. Recording and deciding are one step,
so the 10th failure is the first 429 rather than the 11th.

Add node:test coverage for both the budget and the proxy's wiring, including
the varying-X-Forwarded-For regression, and run it in CI ahead of the Docker
smoke test.
@Ho1yShif
Ho1yShif merged commit 40484a7 into main Jul 30, 2026
2 checks passed
@Ho1yShif
Ho1yShif deleted the fix-auth-rate-limit branch July 30, 2026 05:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant