You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: let rateLimit name the header carrying the visitor
`trustProxy: true` was not enough behind a CDN, and the gallery demo proved it
on the live site after #1388 shipped. The default chain starts at the leftmost
X-Forwarded-For entry, which behind Cloudflare is Cloudflare's egress address,
not the visitor. Cloudflare pins an egress IP per connection, so the limiter
handed out one bucket per connection: the count descended correctly for the
page's probe button, which holds one connection, and reset for every fresh one,
so no visitor was ever refused.
rateLimit and clientIp now take a header name. When set it is the only wire
header read, falling back to the stamped peer and then _anon_. A blank value
falls through rather than becoming a key every visitor shares, and a comma
chain is split so a proxy that appends cannot mint a bucket per hop. It needs
trustProxy: true, because naming a header to trust is the trust decision.
The framework does not prefer CF-Connecting-IP on its own. Cloudflare
overwrites that header, which makes it unforgeable behind Cloudflare and
forgeable everywhere else, so preferring it globally would let a client on an
nginx or bare-platform deploy outrank the header the real proxy sets. Which
header is trustworthy is a fact about the topology, so the app states it.
The route-handler demo now reports the socket peer and the forwarded client
side by side. The gap between those two was invisible from outside the app,
which is what made this take two attempts to diagnose.
Copy file name to clipboardExpand all lines: .agents/skills/webjs/references/built-ins.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,6 +113,8 @@ Options: `window` (ms or a string like `'1m'`), `max`, `key` (a string prefix or
113
113
114
114
**The default key is the socket PEER, which is the visitor only when the browser connects to you directly.** Deploy behind a CDN or a platform router and the peer is that proxy, so `trustProxy: true` is what a deployed limiter almost always wants. Get it wrong and nothing looks broken: a single shared proxy buckets every visitor together, and a proxy POOL (the common case) hands out one full allowance PER proxy, so the effective limit is multiplied by the pool size while `X-RateLimit-Remaining` still counts down convincingly inside each bucket. Diagnose it by sending the requests over ONE keep-alive connection, which pins them to one peer: counts that descend there but reset on a fresh connection mean you are bucketing proxies. `trustProxy: true` has one precondition, that the proxy in front strips an inbound `X-Forwarded-For` before adding its own (Cloudflare, Railway, Fly, Render, and Vercel do; nginx and Caddy only if configured), or a client can forge the header and choose its own bucket.
115
115
116
+
**Behind a CDN, `trustProxy: true` alone is usually still wrong, so name the header: `rateLimit({ trustProxy: true, clientIpHeader: 'cf-connecting-ip' })`.** The default chain starts at the leftmost `X-Forwarded-For` entry, which behind Cloudflare is Cloudflare's EGRESS address rather than the visitor. Cloudflare pins one egress IP per connection, so the symptom is a limiter that counts down correctly for a button that pings on one connection and never refuses anyone who opens a new one. When `clientIpHeader` is set it is the only wire header read (falling back to the peer, then `_anon_`), a blank value falls through rather than becoming a key every visitor shares, and a comma chain is split so an appending proxy cannot mint a bucket per hop. The framework will NOT prefer `CF-Connecting-IP` on its own, because Cloudflare overwrites it, which makes it unforgeable behind Cloudflare and forgeable anywhere else: on an nginx or bare-platform deploy a client could then send it and outrank the header the real proxy set. Name the header YOUR edge sets and overwrites.
117
+
116
118
## Broadcast
117
119
118
120
Send data to every WebSocket client connected to a route path, from inside that route's `WS` handler.
0 commit comments