@@ -96,26 +96,29 @@ allowed past the DNS-rebinding guard (extra names go in `CG_ALLOWED_HOSTS`).
9696** Client addresses behind that hop.** Every request then arrives on a loopback socket, so
9797` req.ip ` — and Express's idea of the client — is ` 127.0.0.1 ` for every caller on earth. Two things
9898count failures per address: the admin login backoff and the approval-link token backoff. Keyed on
99- the socket they would share ONE bucket, and a handful of bad tokens or passwords from anywhere
100- would lock out every legitimate admin and every valid approval link. So both read the real client
101- through one helper (` clientKey ` in ` src/web/security.js ` ): ` CF-Connecting-IP ` first, then the first
102- hop of ` X-Forwarded-For ` , and ** only when the socket itself is loopback** — from a non-loopback
103- socket those headers are whatever the client typed and are ignored. A value that is not a valid IP
104- address is ignored too.
99+ the socket they share ONE bucket, and a handful of bad tokens or passwords from anywhere delays
100+ every legitimate admin and every valid approval link. So ** declare the proxy** in * Settings →
101+ Connection → Behind a reverse proxy* (` trustProxy ` in settings.json, or ` CG_TRUST_PROXY ` in
102+ ` .env ` ; the setting wins): ` cloudflare ` makes both limiters key on ` CF-Connecting-IP ` , which
103+ cloudflared overwrites on every request; ` xff ` keys on the ** last** hop of ` X-Forwarded-For ` , the
104+ one your nginx/caddy appended (never the first, which the client typed). Without it, all remote
105+ logins share one backoff bucket. A loopback socket on its own is deliberately NOT taken as proof
106+ of a proxy — any process on the host or any tunnel opens one — so an undeclared header is ignored,
107+ and a value that is not a valid IP address is ignored too. Independently of the key, more than 50
108+ failed attempts in ten minutes across all clients puts every login on the maximum delay until the
109+ window drains, which bounds guessing even where a header is misdeclared.
105110
106111Notes for operators:
107112
108113- Express's app-wide ` trust proxy ` is deliberately NOT enabled: it would also re-point
109114 ` req.secure ` / ` req.protocol ` / ` req.hostname ` at client-supplied headers (the Host/Origin guard
110115 and the session cookie's ` Secure ` flag read those headers themselves, deliberately), and it knows
111- nothing about ` CF-Connecting-IP ` .
112- - If your proxy runs on ANOTHER host, the socket is not loopback and the headers are ignored by
113- default. Set ` CG_TRUST_PROXY=1 ` in ` .env ` to honour ` X-Forwarded-For ` there — only do this when a
114- proxy you control rewrites that header, or clients can pick their own limiter bucket.
115- - Make the proxy set ` X-Forwarded-For ` rather than append to a client-supplied one. Where it
116- appends (Cloudflare does), ` CF-Connecting-IP ` is the unforgeable value and is preferred here; a
117- caller who spoofs the first XFF hop can only shed its own backoff, never push someone else into
118- one.
116+ nothing about ` CF-Connecting-IP ` . The ` Secure ` cookie decision still reads ` x-forwarded-proto `
117+ on its own; that only ever makes the cookie stricter.
118+ - Only declare a proxy you control and that sets the header on EVERY request. If clients can reach
119+ the daemon's port directly (a LAN bind) while ` xff ` is declared, a caller could append its own
120+ hop; keep the bind on loopback or firewall the port, as the rest of this guide assumes.
121+ - The legacy ` CG_TRUST_PROXY=1 ` still works and means ` xff ` .
119122
120123## The ChannelGate rename migration
121124
0 commit comments