highJun 25, 2026

chi RealIP Middleware IP Spoofing via Unvalidated X-Forwarded-For Header

Shubham Kandhare
Security Engagement Manager, SecureLayer7

chi's built-in RealIP middleware blindly trusts client-supplied headers like X-Forwarded-For and X-Real-IP without verifying the request came from a trusted proxy, letting any attacker fake their IP a

Packagegithub.com/go-chi/chi/middleware
Ecosystemgo
Affected<= 1.5.5
chi RealIP Middleware IP Spoofing via Unvalidated X-Forwarded-For Header

The problem

The `middleware.RealIP` function in go-chi/chi reads `True-Client-IP`, `X-Real-IP`, and `X-Forwarded-For` headers in that order and unconditionally overwrites `r.RemoteAddr` with whatever value the client supplied. No check is made to confirm the request actually arrived from a known, trusted proxy.

Any attacker who can reach the chi server directly, or whose upstream proxy does not sanitize those headers, can set an arbitrary IP. This breaks IP-based access controls, rate limiting, geo-fencing, and produces falsified audit logs.

Proof of concept

bash
curl -H "X-Forwarded-For: 127.0.0.1" http://target:8080/admin

The old `realIP()` function called `r.Header.Get(xForwardedFor)` and took the leftmost comma-separated value, then assigned it directly to `r.RemoteAddr`. HTTP headers are fully attacker-controlled, so the leftmost XFF entry is always the one the client wrote.

The patch (v5.3.0, PR #967) removes `RealIP` as a safe default and replaces it with four new middlewares (`ClientIPFromHeader`, `ClientIPFromXFF`, `ClientIPFromXFFTrustedProxies`, `ClientIPFromRemoteAddr`) that require the operator to explicitly declare trusted proxy CIDRs or a trusted header name. `ClientIPFromXFF` walks the XFF chain right-to-left, stopping at the first IP not covered by a declared trusted CIDR, so attacker-appended leftmost entries are never reached.

The result is stored in request context via `GetClientIP`/`GetClientIPAddr` and `r.RemoteAddr` is never mutated.

The fix

Upgrade to go-chi/chi v5.3.0 or later. Replace `middleware.RealIP` with exactly one of the new `ClientIPFrom*` middlewares that matches your deployment topology. For a server behind Cloudflare use `middleware.ClientIPFromHeader("CF-Connecting-IP")`; for a proxy fleet with known CIDRs use `middleware.ClientIPFromXFF("<cidr>", ...)`.

For direct internet exposure with no proxy use `middleware.ClientIPFromRemoteAddr`. Read the resolved IP with `middleware.GetClientIP(ctx)` instead of `r.RemoteAddr`.

Reported by rezmoss.

References: [1][2][3]