Skip to content

fix(server): rate-limit requests before token verification - #2372

Merged
felladrin merged 1 commit into
felladrin:mainfrom
uuzzrm:fix/rate-limit-before-token-verification
Aug 15, 2026
Merged

fix(server): rate-limit requests before token verification#2372
felladrin merged 1 commit into
felladrin:mainfrom
uuzzrm:fix/rate-limit-before-token-verification

Conversation

@uuzzrm

@uuzzrm uuzzrm commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Description

The rate limiter was only applied after a token passed verification, so requests carrying an invalid or missing token were rejected without ever consuming a rate-limit point. Each rejected request still paid for a full argon2 verification though - and that verification is the expensive part - which means a caller could stream bogus tokens and keep the server busy doing memory-heavy checks with no throttling. On a publicly reachable instance that is a cheap way to pin the CPU.

verifyTokenAndRateLimit now consumes a rate-limit point before doing anything else, keyed on the client IP when a request is present. Invalid and missing tokens are throttled like every other request, and the verification is skipped entirely once the limiter has said no. This also lines up with the intent already documented at the /page-content endpoint, which verifies before reading the parameters so that malformed requests from unauthenticated callers count against the rate limiter.

How to test

  • npm test - added three regression tests: an invalid token under a tripped limiter gets 429 instead of 401, no argon2 verification runs when the limiter already rejected the request, and a request with a missing token is rate limited too.
  • The existing tests for valid tokens, verified-token caching and socket-address keying are unchanged and still pass.

Invalid or missing tokens used to skip the rate limiter entirely, so a
caller could send an endless stream of bogus tokens and force a full
argon2 verification for each one without ever hitting the limiter. On a
publicly reachable instance that is a cheap way to pin the server's CPU.

The limiter now runs before verification, keyed on the client IP when a
request is present, so unauthenticated callers are throttled too. This
matches the intent already documented at the page-content endpoint, where
verification is performed before the parameters are read so that malformed
requests from unauthenticated callers count against the rate limiter.

@felladrin felladrin left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ordering fix is correct: the rate-limit point is consumed before the missing-token check and before argon2, so invalid/missing tokens are throttled and no argon2 work happens once the limiter trips. Rate-limit key is unchanged on the normal path (client IP), valid tokens still consume exactly one point per request, and the verified-token cache is untouched. The three new tests fail on the pre-fix code for the right reason. Tests pass locally (19/19) and CI is green.

Non-blocking follow-ups for later: (1) the per-request argon2 cost is still attacker-chosen - the client sends an encoded argon2id hash and argon2Verify reads m=/t=/p= from that caller-supplied string, so a caller can buy ~500x the legitimate work per request; this PR caps the rate but not the cost, so a shape/param-bounds guard before argon2Verify is worth a separate PR; (2) the no-request fallback key (token ?? "anonymous") gives each bogus token its own bucket - unreachable today since all call sites pass request, but it fails open for a future call site; (3) /search/ still runs its zod parse before handleTokenVerification, so malformed unauthenticated requests there consume no point (only a zod parse, not a DoS lever, but it contradicts the /page-content principle this PR cites); (4) behind a reverse proxy with TRUST_PROXY off, unauthenticated noise now drains the shared per-proxy-IP bucket and can 429 real users - right trade, but worth a deployment-docs line.

@felladrin
felladrin merged commit 9fb1106 into felladrin:main Aug 15, 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.

2 participants