fix(handlers): resolve client IP via chi ClientIP, drop deprecated RealIP

chi v5.3.0 deprecates middleware.RealIP (IP-spoofing advisories), which
failed the Lint and Static Security Analysis CI jobs (SA1019). Replace the
RealIP wrapper with chi's middleware.ClientIP: ClientIPFromRemoteAddr is
always applied so middleware.GetClientIP is populated, and when
trust_forwarded_headers is set and the immediate peer is a trusted-proxy
CIDR, ClientIPFromXFF resolves the real client from X-Forwarded-For
(rightmost entry outside the trusted CIDRs). The immediate-peer trust gate
is preserved, so a non-trusted peer's XFF is ignored. CIDR strings are
validated with netip.ParsePrefix first to avoid ClientIPFromXFF's panic.

Behavior change: only X-Forwarded-For is honored now (RealIP also read
X-Real-IP / True-Client-IP). Docs and a release note follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-06-28 13:15:49 +02:00
co-authored by Claude Opus 4.8
parent 28d7675fc4
commit 67c30850cd
7 changed files with 345 additions and 278 deletions
+5 -7
View File
@@ -1232,14 +1232,12 @@ func setupRouter(server *handlers.Server, stockholmHandler *stockholm.Handler, w
// every downstream middleware and the recorder see the cleaned path.
r.Use(middleware.CleanPath)
// TrustedRealIP must run before any handler that reads r.RemoteAddr
// ClientIPMiddleware must run before any handler that reads the client IP
// SnapshotMiddleware captures the request, and several handlers
// (HandleMargePowerOn, etc.) inspect the source IP. The middleware is
// gated on Settings.TrustForwardedHeaders; when off (the safe default),
// it returns nil and we skip Use'ing it entirely.
if mw := server.TrustedRealIPMiddleware(); mw != nil {
r.Use(mw)
}
// (HandleMargePowerOn, etc.) inspect the source IP via middleware.GetClientIP.
// Always wired: at minimum the socket peer is recorded; when
// TrustForwardedHeaders is on and the peer is trusted, XFF is resolved.
r.Use(server.ClientIPMiddleware())
r.Use(server.SnapshotMiddleware)
r.Use(server.OriginMiddleware)