From 8cfeede1ca385b17be83f05eb1f4a9572099115f Mon Sep 17 00:00:00 2001 From: Matt Ford Date: Fri, 10 Jul 2026 10:42:02 +0100 Subject: [PATCH 1/3] Correct ordering of forwarded IP addresses in test Update TrustProxies to add all intermediate IP addresses to trusted list --- .../Http/Middleware/TrustProxies.php | 37 ++++++++++++++++++- tests/Http/Middleware/TrustProxiesTest.php | 28 ++++++++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index e7e526374fae..f49afc95bba4 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -4,6 +4,7 @@ use Closure; use Illuminate\Http\Request; +use Symfony\Component\HttpFoundation\HeaderUtils; class TrustProxies { @@ -112,14 +113,46 @@ protected function setTrustedProxyIpAddressesToSpecificIps(Request $request, arr } /** - * Set the trusted proxy to be the IP address calling this servers. + * Set the trusted proxies to all IP addresses in the forwarded chain + the calling IP. * * @param \Illuminate\Http\Request $request * @return void */ protected function setTrustedProxyIpAddressesToTheCallingIp(Request $request) { - $request->setTrustedProxies([$request->server->get('REMOTE_ADDR')], $this->getTrustedHeaderNames()); + $trustedIps = collect($this->gatherProxyIpAddresses($request)) + ->concat([$request->server->get('REMOTE_ADDR')]) + ->unique() + ->values() + ->all(); + $request->setTrustedProxies($trustedIps, $this->getTrustedHeaderNames()); + } + + /** + * Gather a list of IP addresses from the X-FORWARDED-FOR/X-FORWARDED headers. + * + * @param Request $request + * @return array + */ + private function gatherProxyIpAddresses(Request $request) + { + $forwardedForIps = collect(explode(',', $request->headers->get('X-FORWARDED-FOR', ''))) + ->map(fn ($v) => trim($v)) + ->filter(fn ($v) => ! empty($v)) + ->values() + ->all(); + $forwardedIps = []; + + $forwarded = $request->header('FORWARDED', ''); + $parts = HeaderUtils::split($forwarded, ',;='); + foreach ($parts as $subParts) { + if (null === $v = HeaderUtils::combine($subParts)['for'] ?? null) { + continue; + } + $forwardedIps[] = trim($v); + } + + return array_merge($forwardedIps, $forwardedForIps); } /** diff --git a/tests/Http/Middleware/TrustProxiesTest.php b/tests/Http/Middleware/TrustProxiesTest.php index eeb4dac7d5be..53b5e3bdc835 100644 --- a/tests/Http/Middleware/TrustProxiesTest.php +++ b/tests/Http/Middleware/TrustProxiesTest.php @@ -163,9 +163,9 @@ public function test_get_client_ip_with_multiple_ip_addresses_all_proxies_are_tr $forwardedFor = [ '192.0.2.2', - '192.0.2.199, 192.0.2.2', - '192.0.2.199,192.0.2.2', - '99.99.99.99,192.0.2.199,192.0.2.2', + '192.0.2.2, 192.0.2.199', + '192.0.2.2,192.0.2.199', + '192.0.2.2,99.99.99.99,192.0.2.199', ]; foreach ($forwardedFor as $forwardedForHeader) { @@ -177,6 +177,28 @@ public function test_get_client_ip_with_multiple_ip_addresses_all_proxies_are_tr } } + /** + * Test Forwarded header with multiple IP addresses, with * wildcard trusting of all proxies. + */ + public function test_get_client_ip_with_multiple_ip_addresses_all_proxies_are_trusted_using_forwarded_header() + { + $trustedProxy = $this->createTrustedProxy(Request::HEADER_FORWARDED, '*'); + + $forwarded = [ + 'for=192.0.2.2', + 'for=192.0.2.2,for=192.0.2.199', + 'for=192.0.2.2,for=99.99.99.99,for=192.0.2.199', + ]; + + foreach ($forwarded as $forwardedHeader) { + $request = $this->createProxiedRequest(['HTTP_FORWARDED' => $forwardedHeader, 'HTTP_X_FORWARDED_FOR' => '']); + + $trustedProxy->handle($request, function ($request) use ($forwardedHeader) { + $this->assertSame('192.0.2.2', $request->getClientIp(), 'Assert sets the '.$forwardedHeader); + }); + } + } + /** * Test distrusting a header. */ From 47d9959be1e1edf4e3c0ff0fb49ce72fa3bd5d33 Mon Sep 17 00:00:00 2001 From: Matt Ford Date: Tue, 14 Jul 2026 08:34:50 +0100 Subject: [PATCH 2/3] Switch to more efficient method, setting the trusted IPs to a catchall address instead of gathering all IPs from the request --- .../Http/Middleware/TrustProxies.php | 35 +------------------ tests/Http/Middleware/TrustProxiesTest.php | 18 ++++++++++ 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index f49afc95bba4..59c6c2b88fd7 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -4,7 +4,6 @@ use Closure; use Illuminate\Http\Request; -use Symfony\Component\HttpFoundation\HeaderUtils; class TrustProxies { @@ -120,39 +119,7 @@ protected function setTrustedProxyIpAddressesToSpecificIps(Request $request, arr */ protected function setTrustedProxyIpAddressesToTheCallingIp(Request $request) { - $trustedIps = collect($this->gatherProxyIpAddresses($request)) - ->concat([$request->server->get('REMOTE_ADDR')]) - ->unique() - ->values() - ->all(); - $request->setTrustedProxies($trustedIps, $this->getTrustedHeaderNames()); - } - - /** - * Gather a list of IP addresses from the X-FORWARDED-FOR/X-FORWARDED headers. - * - * @param Request $request - * @return array - */ - private function gatherProxyIpAddresses(Request $request) - { - $forwardedForIps = collect(explode(',', $request->headers->get('X-FORWARDED-FOR', ''))) - ->map(fn ($v) => trim($v)) - ->filter(fn ($v) => ! empty($v)) - ->values() - ->all(); - $forwardedIps = []; - - $forwarded = $request->header('FORWARDED', ''); - $parts = HeaderUtils::split($forwarded, ',;='); - foreach ($parts as $subParts) { - if (null === $v = HeaderUtils::combine($subParts)['for'] ?? null) { - continue; - } - $forwardedIps[] = trim($v); - } - - return array_merge($forwardedIps, $forwardedForIps); + $request->setTrustedProxies(['0.0.0.0/0', '::/0'], $this->getTrustedHeaderNames()); } /** diff --git a/tests/Http/Middleware/TrustProxiesTest.php b/tests/Http/Middleware/TrustProxiesTest.php index 53b5e3bdc835..8d83bb338249 100644 --- a/tests/Http/Middleware/TrustProxiesTest.php +++ b/tests/Http/Middleware/TrustProxiesTest.php @@ -79,6 +79,24 @@ public function test_trusted_proxy_sets_trusted_proxies_with_double_wildcard_for }); } + /** + * Test that the wildcard trusts proxies in an IPv6 forwarded chain, so the + * left-most client IP is returned rather than an intermediate IPv6 proxy. + */ + public function test_trusted_proxy_sets_trusted_proxies_with_wildcard_and_ipv6_chain() + { + $trustedProxy = $this->createTrustedProxy($this->headerAll, '*'); + $request = $this->createProxiedRequest([ + 'REMOTE_ADDR' => '2001:db8::1', + 'HTTP_X_FORWARDED_FOR' => '173.174.200.38, 2001:db8::2, 2001:db8::3', + ]); + + $trustedProxy->handle($request, function ($request) { + $this->assertSame('173.174.200.38', $request->getClientIp(), + 'Assert IPv6 proxies in the forwarded chain are trusted with wildcard proxy setting'); + }); + } + /** * Test the next most typical usage of TrustedProxies: * Trusted X-Forwarded-For header, REMOTE_ADDR for TrustedProxies. From 858f558933c7208b43db8abc8fc341199de24183 Mon Sep 17 00:00:00 2001 From: Matt Ford Date: Tue, 14 Jul 2026 08:47:54 +0100 Subject: [PATCH 3/3] Update comment --- src/Illuminate/Http/Middleware/TrustProxies.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Middleware/TrustProxies.php b/src/Illuminate/Http/Middleware/TrustProxies.php index 59c6c2b88fd7..d8a43bec3451 100644 --- a/src/Illuminate/Http/Middleware/TrustProxies.php +++ b/src/Illuminate/Http/Middleware/TrustProxies.php @@ -112,7 +112,7 @@ protected function setTrustedProxyIpAddressesToSpecificIps(Request $request, arr } /** - * Set the trusted proxies to all IP addresses in the forwarded chain + the calling IP. + * Set the trusted proxies to catchall addresses for IPv4 and IPv6. * * @param \Illuminate\Http\Request $request * @return void