Repository navigation
fix(runtime-node): relay rate limit keys the real visitor on any host - #33
Merged
Merged
Conversation
…by default The browser relay keyed its per-IP rate limit on the first X-Forwarded-For entry, which any caller can set. Rotating it gave a fresh window on every request, so the limit did not bound parsing and forwarding under the server's ingest key. New option `trustProxy` (only the boolean `true` enables it). Without it the Node handler keys the real socket peer and the fetch handler keys one shared bucket for the route. Behind a proxy that overwrites the header, set `trustProxy: true` to key each client. Ported from the relay hardening by rajeshaipython-stack that followed #20 on the fork and never reached main. Its context redaction and detached oversize cancel already landed through #26 and #20. Co-authored-by: rajeshaipython-stack <rajesh.ai.python@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n any host The strict default from the previous commit kept the limit safe but put every visitor behind a proxy into one window. `trustProxy` now defaults to "auto": read X-Forwarded-For right to left and take the first public address, which the nearest load balancer, CDN or platform appended or wrote. In Node, a connection straight from a public address is the visitor and its header is ignored. On Cloudflare Workers, use CF-Connecting-IP when the runtime set request.cf. `trustProxy` also takes a hop count, `true` (leftmost entry) and `false`, including the same values as strings from env vars, and a new `clientIp(request)` option supplies the address directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes for users
The browser relay's rate limit can no longer be dodged, and it works out who each visitor is on any host without setup. Today the relay keys each visitor on the first
X-Forwarded-Forentry, which any caller sets freely. A script that sends a new value on every request gets a fresh 120-a-minute window each time, so it can push unbounded traffic through the relay under the site's private ingest key and burn its telemetry quota.How the relay now picks the visitor
New option
trustProxy, default"auto". Proxies append the address they saw to the right ofX-Forwarded-For, or overwrite the header, so the relay reads it right to left and takes the first public address, skipping hops on the customer's own network (private, loopback, link-local, carrier-grade NAT, unique-local). A caller can only add entries to the left of what its proxy appended, so it cannot pick its own key."auto"keysCF-Connecting-IP, trusted only when the runtime setrequest.cfOther settings for setups
"auto"cannot see:trustProxy: <count>when exactly that many proxies the customer controls sit in front, with Expresstrust proxysemantics.1covers a Node origin behind Cloudflare with no load balancer.trustProxy: truekeeps the old leftmost behaviour, for chains that strip client values.trustProxy: falsenever reads the header: the socket address in Node, one shared bucket in fetch runtimes.clientIp(request)supplies the address directly, for example Deno'sinfo.remoteAddror Bun'sserver.requestIP."auto","false","0","2"). Anything unrecognised falls back to"auto"with one warning, so"false"can no longer turn trust on by being truthy.Known limit. A fetch handler exposed straight to the internet with no proxy in front receives the caller's header untouched, so it can still be fooled. The README says to use
trustProxy: falseorclientIpthere.Platform facts relied on: Vercel overwrites
X-Forwarded-For, Cloudflare appends the connecting address and setsCF-Connecting-IP, AWS load balancers append the last hop by default.Where this came from
@rajeshaipython-stack wrote the original hardening after #20 merged. The follow-up commits went to their fork branch and never reached
main.X-Forwarded-Forentrymainthrough #26, with testsmainthrough #20, with testsmainThe first commit ports the contributor's strict version. The second replaces its shared-bucket default with
"auto", because the strict default put every visitor behind a load balancer or platform proxy into one 120-a-minute window.Tests
11 new relay cases cover:
request.cf;falseand its env strings;true;clientIpresolver;false, and behind Cloudflare with1.npm run test -w @autter/runtime-nextnpm run test -w @autter/runtime-node, locallyBoth local failures are process-exit timing tests:
autoflush"second signal exits immediately" andcompat"a hanging compat request never delays process exit". Each spawns a child Node process with a 2.5 s limit, and both missed it while the machine's load average was 43. They do not touch the relay, and both passed in this PR's CI on the first commit. CI on this commit is the check.No version bump or release is included.
🤖 Generated with Claude Code