- limiter_key: an empty client_ip(req) under trust_proxy no longer keys
on the literal "ip:" -- falls through to net.peer(req.conn) instead,
same as the untrusted-default path
- the bug: every client omitting X-Forwarded-For shared ONE bucket,
so one could exhaust it and deny/hide the rest
- curl availability check added alongside the existing woc/wovm check
(the limiter gate legs drive the server with it)
- new gate leg: LIMIT+1 sequential no-XFF requests must all be 200
(own key per connection, via a fresh ephemeral port each time) --
confirmed it fails against the pre-fix code (6th comes back 429)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 831e9d8e6b1ef39cd938783dbc47c1abe6d53211)
- delete all RateLimitCounter access from limiter.wo: query, increment,
delete-then-insert, and the swallowing catch (e) nil -- the pool is now
the only writer, so its serialization guarantee actually holds
- Limiter gains pool/limit/trust_proxy fields; before() calls pool_count
and acts on the Verdict; make_limiter takes a pool
- key selection: req.principal first, else trust_proxy ? client_ip(req)
: net.peer(req.conn); delete the dead req.ctx["verified_proxy"] branch
- 429 on a spent window (Retry-After, X-RateLimit-*); 503 + Retry-After
on a caught actor trap (saturated pool), request never let through
- add Limiter.after(), registered alongside before() as both Mw and Aw
(Cors's own shape) so the allowed path's X-RateLimit-* headers reach
the response, not just req.ctx
- scripts/web-app-accept.sh: three new gate legs -- threshold (N pass,
N+1th 429), SIGTERM+restart (still limited from the WAL), and N
genuinely-parallel curl clients on one key with an exact-count assertion
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a653dd0aa64711c43461126d32b4632cc8f64c7a)