From 2f0659b2cc22a05a68c5d6c7e628f0a263ce44c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20L=C3=B3pez=20O?= Date: Sat, 31 Oct 2026 22:04:59 -0500 Subject: [PATCH] Guard client IP extraction against spoofed forwarding headers [skip deploy] #no-deploy --- internal/httpserver/server_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/internal/httpserver/server_test.go b/internal/httpserver/server_test.go index 4493971..b092ec6 100644 --- a/internal/httpserver/server_test.go +++ b/internal/httpserver/server_test.go @@ -303,3 +303,30 @@ func TestHTTPServer_ClientIPBehindTrustedProxy(t *testing.T) { }) } } + +// Echo ≤ 4.16.2 confía por defecto en X-Forwarded-For y X-Real-IP (GHSA-246p-cpwv-v3jq y GHSA-99jh-6h7p-pp36). +// La API fija su propio IPExtractor: ninguna de las dos cabeceras falsas cambia la IP que usa el límite de tasa. +func TestHTTPServer_SpoofedForwardingHeadersAreIgnored(t *testing.T) { + cases := []struct { + name string + opts Options + remote string + want string + }{ + {"sin proxy", Options{}, "203.0.113.9:4000", "203.0.113.9"}, + {"proxy activado pero par público", Options{TrustProxy: true}, "203.0.113.9:4000", "203.0.113.9"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + e := New(testLogger(), tc.opts) + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.RemoteAddr = tc.remote + req.Header.Set(echo.HeaderXForwardedFor, "1.2.3.4") + req.Header.Set(echo.HeaderXRealIP, "5.6.7.8") + c := e.NewContext(req, httptest.NewRecorder()) + if got := c.RealIP(); got != tc.want { + t.Fatalf("IP del cliente %q, se esperaba %q", got, tc.want) + } + }) + } +}