rate limiting: the in-process limiter is keyed per full IPv6 address and was looser than the edge #142

Stängd
öppnade 2026-08-01 14:35:42 +00:00 av supernaut · 0 kommentarer
Ägare

Found while tuning the edge rate limits in bitborg-infra (#302/#304 there). The edge zone is documented as
defence-in-depth over this limiter, so the two have to be sized as a pair — and they were not.

Two defects

1. Same shared-egress problem as the edge had. src/lib/rate-limit.ts keys on clientIp() (the last
entry of X-Forwarded-For) with no IPv6 prefixing. So:

  • on IPv4, everyone behind a corporate, university or mobile CGNAT egress shares one budget — the burst
    shape an announcement produces;
  • on IPv6 it is keyed per full address, which is the opposite failure: a client can rotate through a
    /64 it already controls and get a fresh budget every time. The edge zone groups IPv6 by /56 precisely
    to stop that.

2. The layering was upside down. Budgets here are signup 5/min, captcha 30/min (shared across
challenge and redeem) and resend 3/min. The edge zone was set to 10 events/min while counting four
requests per sign-up, so the outer ring was tighter than the inner one it exists to back up — the app
limiter never engaged at all. The edge fix raises that to 30 on the state-changing endpoints only, which
puts the ordering back the right way up. This issue is the other half.

Done when

  • The key is prefix-grouped for IPv6, matching the edge's /56, so an address rotation does not mint a
    new budget.
  • The IPv4 shared-egress consequence is a recorded decision rather than an accident: either accept it with
    a budget sized for a shared egress, or key on something better than the address for the endpoints where
    it matters.
  • The relationship to the edge budgets is written down next to the numbers, so the next person to change
    either one can see that they are a pair. Right now nothing in this file mentions the edge zone at all,
    which is how they got inverted.

Worth doing carefully rather than quickly: making a limiter stricter on the sign-up path is a way to break
sign-up, and making it looser is a way to invite the abuse it exists to stop.

Found while tuning the edge rate limits in bitborg-infra (#302/#304 there). The edge zone is documented as defence-in-depth **over** this limiter, so the two have to be sized as a pair — and they were not. ## Two defects **1. Same shared-egress problem as the edge had.** `src/lib/rate-limit.ts` keys on `clientIp()` (the last entry of `X-Forwarded-For`) with **no IPv6 prefixing**. So: - on IPv4, everyone behind a corporate, university or mobile CGNAT egress shares one budget — the burst shape an announcement produces; - on IPv6 it is keyed per **full address**, which is the opposite failure: a client can rotate through a /64 it already controls and get a fresh budget every time. The edge zone groups IPv6 by `/56` precisely to stop that. **2. The layering was upside down.** Budgets here are `signup` 5/min, `captcha` 30/min (shared across challenge and redeem) and `resend` 3/min. The edge zone was set to 10 events/min while counting *four* requests per sign-up, so **the outer ring was tighter than the inner one it exists to back up** — the app limiter never engaged at all. The edge fix raises that to 30 on the state-changing endpoints only, which puts the ordering back the right way up. This issue is the other half. ## Done when - The key is prefix-grouped for IPv6, matching the edge's `/56`, so an address rotation does not mint a new budget. - The IPv4 shared-egress consequence is a recorded decision rather than an accident: either accept it with a budget sized for a shared egress, or key on something better than the address for the endpoints where it matters. - The relationship to the edge budgets is written down next to the numbers, so the next person to change either one can see that they are a pair. Right now nothing in this file mentions the edge zone at all, which is how they got inverted. Worth doing carefully rather than quickly: making a limiter stricter on the sign-up path is a way to break sign-up, and making it looser is a way to invite the abuse it exists to stop.
supernaut lade till detta till projektet Bitborg Web 2026-08-01 14:35:53 +00:00
Logga in för att delta i denna konversation.
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-web#142
Ingen beskrivning angiven.