highJun 25, 2026

go-chi/chi RealIP Middleware IP Spoofing via X-Forwarded-For

Shubham Kandhare
Security Engagement Manager, SecureLayer7

The chi RealIP middleware blindly trusts the leftmost value in the X-Forwarded-For header, letting any client forge their source IP and bypass access controls or corrupt audit logs.

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

The problem

The `middleware.RealIP()` function in go-chi/chi splits the `X-Forwarded-For` header on commas and unconditionally promotes the first (leftmost) entry into `r.RemoteAddr`.

Because a client controls everything to the left of what the immediate proxy appends, any caller can inject an arbitrary IP into that first position. Downstream code reading `r.RemoteAddr` for rate-limiting, geo-blocking, or audit logging will silently operate on the attacker-supplied value.

Proof of concept

bash
# 1. Start a chi server using middleware.RealIP (see advisory PoC)
# 2. Send a request with a forged leftmost XFF entry:
curl http://localhost:8080 -H 'X-Forwarded-For: 192.0.2.2, 192.0.2.1'

# Server responds:
# remote addr: 192.0.2.2 (want 192.0.2.1)
#
# 192.0.2.2 is the attacker-forged IP.
# 192.0.2.1 is the real client IP appended by the proxy.
# RealIP picks index 0 (leftmost), so the forged IP wins.

CWE-346 (Origin Validation Error). The root cause is a hard-coded left-to-right read of XFF with no concept of which proxies are trusted. A legitimate proxy appends the connecting IP to the *right* of the existing header value, so the leftmost entry is always under client control and must never be trusted without validating all entries to its right against a known-good proxy list.

The fix in v5.3.0 (PR #967) deprecated `RealIP` entirely because its two design choices, mutating `r.RemoteAddr` and applying a one-size-fits-all header priority list, are unfixable without a breaking API change. The replacement `ClientIP` family requires the operator to declare exactly which trust source to use (`ClientIPFromHeader`, `ClientIPFromXFF` with explicit CIDRs, `ClientIPFromXFFTrustedProxies`, or `ClientIPFromRemoteAddr`), and the XFF variant walks right-to-left, skipping trusted entries, to find the first untrusted IP.

The fix

Upgrade to go-chi/chi/v5 >= v5.3.0 and replace `middleware.RealIP` with exactly one of the new `ClientIPFrom*` middlewares suited to your infrastructure. Read the resolved IP with `middleware.GetClientIP(r)` instead of `r.RemoteAddr`. The old `RealIP` function is still present but marked deprecated and remains unsafe.

Reported by convto.

References: [1][2][3]