@@ -62,7 +62,14 @@ func ClientIPFromHeader(trustedHeader string) func(http.Handler) http.Handler {
6262// set (fail-closed) — we can't safely trust anything left of garbage.
6363//
6464// Use this when you sit behind one or more reverse proxies whose IP ranges
65- // you can enumerate as CIDRs:
65+ // you can enumerate as CIDRs. Most CDNs publish their IPs:
66+ //
67+ // Cloudflare: https://www.cloudflare.com/ips/
68+ // AWS: https://ip-ranges.amazonaws.com/ip-ranges.json
69+ // Fastly: https://api.fastly.com/public-ip-list
70+ // Google Cloud: https://www.gstatic.com/ipranges/cloud.json
71+ //
72+ // Example (CloudFront):
6673//
6774// r.Use(middleware.ClientIPFromXFF(
6875// "13.32.0.0/15", // CloudFront IPv4
@@ -109,30 +116,34 @@ func ClientIPFromXFF(trustedIPPrefixes ...string) func(http.Handler) http.Handle
109116 }
110117}
111118
112- // ClientIPFromXFFTrustedProxies stores the client IP read from the
113- // X-Forwarded-For header, given the exact number of trusted reverse proxies
114- // between this server and the public internet. It returns the IP at position
115- // len(xff) - numTrustedProxies in the merged X-Forwarded-For list — the IP
116- // added by the outermost of your trusted proxies, the only IP in the chain
117- // that none of your proxies have allowed an attacker to forge. Read it with
118- // [GetClientIP].
119+ // ClientIPFromXFFTrustedProxies stores the client IP read from
120+ // X-Forwarded-For, given the exact number of trusted reverse proxies
121+ // between this server and the public internet. Read it with [GetClientIP].
122+ //
123+ // PREFER [ClientIPFromXFF] with explicit CIDRs whenever you can — it
124+ // cannot off-by-one and is robust to architecture changes. Most CDNs
125+ // publish their IP ranges (Cloudflare, AWS, Fastly, Google Cloud). Use
126+ // this counting variant only when proxy IPs are dynamic and unpublishable.
127+ //
128+ // numTrustedProxies = total proxy hops between the client and this server.
129+ // Count every hop in the request path:
119130//
120- // Use this when:
121- // - You know exactly how many proxies you sit behind, AND
122- // - Their IP addresses are dynamic (autoscaling proxy pools, ephemeral
123- // containers, dynamic CDN edges) so listing CIDRs with [ClientIPFromXFF]
124- // is impractical.
131+ // Single proxy (one LB / nginx / Heroku / Fly.io / Render) ....... 1
132+ // Two proxies (Cloudflare → ALB, CloudFront → ALB) .............. 2
133+ // Three proxies (CDN → API gateway → LB) ......................... 3
125134//
126- // WARNING: This variant is brittle to network architecture changes. If you
127- // add or remove a proxy level, numTrustedProxies silently becomes wrong and
128- // you may start trusting an attacker-supplied IP. Prefer [ClientIPFromXFF]
129- // with explicit trusted CIDRs whenever you can .
135+ // VERIFY BEFORE GOING LIVE: send a request from a known IP and confirm
136+ // [GetClientIP] returns that IP. If it returns a proxy IP, your count is
137+ // too LOW — a client can spoof their IP, fix immediately. If it returns
138+ // "", your count is too HIGH — no leak, but no client IP either .
130139//
131- // If the XFF chain has fewer than numTrustedProxies entries (header missing
132- // or architecture changed), no client IP is set and [GetClientIP] returns "".
140+ // This middleware reads ONLY X-Forwarded-For; it does not inspect
141+ // r.RemoteAddr. Guarantee at the network layer (security group / firewall)
142+ // that only your proxies can reach this server.
133143//
134- // Like [ClientIPFromXFF], v4-mapped IPv6 folds to plain v4 and IPv6 zones
135- // are stripped before storage.
144+ // If the XFF chain has fewer than numTrustedProxies entries, no client IP
145+ // is set (fail-closed). Like [ClientIPFromXFF], v4-mapped IPv6 folds to v4
146+ // and IPv6 zones are stripped before storage.
136147//
137148// Panics at startup if numTrustedProxies < 1.
138149func ClientIPFromXFFTrustedProxies (numTrustedProxies int ) func (http.Handler ) http.Handler {
0 commit comments