The address limits believe the proxy, not the client
X-Forwarded-For's first entry is whatever the client wrote; Caddy appends the true peer last. The limiter now reads the last entry, and only when the socket peer is loopback — the proxy — so a request that somehow reaches the server directly is keyed by its own address. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
ca108f83a6
commit
ddd2e347bd
@@ -31,10 +31,17 @@ export class SlidingLimit {
|
||||
}
|
||||
}
|
||||
|
||||
/** The client's address as Caddy reports it — the first hop in
|
||||
* X-Forwarded-For — falling back to the socket's own peer. */
|
||||
/** The client's address as Caddy reports it. The proxy APPENDS the true
|
||||
* peer to X-Forwarded-For, so the last entry is the trustworthy one; a
|
||||
* client can write anything into the first. And the header is believed
|
||||
* only when the socket peer is the proxy itself (loopback) — reached
|
||||
* any other way, the peer address is the client. */
|
||||
export function clientAddress(headers: Record<string, string | string[] | undefined>, remote: string | undefined): string {
|
||||
const peer = remote || "unknown";
|
||||
if (!LOOPBACK.has(peer)) return peer;
|
||||
const fwd = headers["x-forwarded-for"];
|
||||
const first = (Array.isArray(fwd) ? fwd[0] : fwd)?.split(",")[0]?.trim();
|
||||
return first || remote || "unknown";
|
||||
const parts = (Array.isArray(fwd) ? fwd.join(",") : fwd ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
||||
return parts[parts.length - 1] || peer;
|
||||
}
|
||||
|
||||
const LOOPBACK = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
||||
|
||||
Reference in New Issue
Block a user