Per owner decision the optional proxy-header escape hatch is dead config: remove the env var, its plumbing (Config.TrustedIPHeader, SetTrustedIPHeader), and the README row. Rate-limit keying is always the peer address; no client-supplied IP header is ever trusted. Tests updated to assert headers (CF-Connecting-IP included) never influence clientIP.
28 lines
961 B
Go
28 lines
961 B
Go
// clientIP extracts the client IP for rate-limit keying.
|
|
//
|
|
// Trust boundary (issue #280): the bucket key MUST NOT come from any header a
|
|
// client can influence. The previous rightmost-X-Forwarded-For scheme (#85)
|
|
// assumed Traefik appends the real client IP, but the deployed ingress does
|
|
// not rewrite XFF, so a client rotating its own XFF value got a fresh bucket
|
|
// per request and the limit was unenforceable (pentest H1: 6x201 across 8
|
|
// rotating-XFF creates).
|
|
//
|
|
// The bucket key is always the actual peer address (RemoteAddr). Behind any
|
|
// reverse proxy this is the proxy's address, so all clients share one bucket
|
|
// per endpoint — coarse, but safe. Client-supplied IP headers
|
|
// (X-Forwarded-For, X-Real-Ip, and any others) are never trusted.
|
|
package api
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
)
|
|
|
|
func clientIP(r *http.Request) string {
|
|
host := r.RemoteAddr
|
|
if h, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
|
host = h
|
|
}
|
|
return host
|
|
}
|