From e647880669b79cd968231cf85dc037a18e8bfd9c Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 14 Feb 2026 21:50:10 -0800 Subject: fix: prioritize proxy headers for rate limiting Check X-Forwarded-For and X-Real-IP headers before peer info to correctly identify clients behind reverse proxies. Previously, rate limiting would apply globally when behind Caddy/nginx because all requests appeared to come from the proxy's IP address. This fix is critical for production deployments behind reverse proxies. --- internal/ratelimit/interceptor.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'internal/ratelimit') diff --git a/internal/ratelimit/interceptor.go b/internal/ratelimit/interceptor.go index b27fe7e..57721a7 100644 --- a/internal/ratelimit/interceptor.go +++ b/internal/ratelimit/interceptor.go @@ -59,14 +59,11 @@ func getIdentifier(ctx context.Context) string { } // getIPAddress extracts the client IP address from the context. +// When behind a reverse proxy, this checks X-Forwarded-For and X-Real-IP headers first +// to get the real client IP, then falls back to peer info (which would be the proxy IP). func getIPAddress(ctx context.Context) string { - // Try to get from peer info - p, ok := peer.FromContext(ctx) - if ok && p.Addr != nil { - return p.Addr.String() - } - - // Try to get from metadata (X-Forwarded-For header) + // Check proxy headers first (X-Forwarded-For, X-Real-IP) + // This is critical for rate limiting behind reverse proxies like Caddy/nginx md, ok := metadata.FromIncomingContext(ctx) if ok { if xff := md.Get("x-forwarded-for"); len(xff) > 0 { @@ -77,6 +74,12 @@ func getIPAddress(ctx context.Context) string { } } + // Fall back to peer info (direct connection or proxy IP) + p, ok := peer.FromContext(ctx) + if ok && p.Addr != nil { + return p.Addr.String() + } + return "unknown" } -- cgit v1.2.3