From 45dab68371d333cffa936a9610a2650ae3317139 Mon Sep 17 00:00:00 2001 From: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:07:42 +0800 Subject: [PATCH] Exclude null and wildcard from external matching ExternalInetAddressMatcher negates InternalInetAddressMatcher, which returns false for a null address and for the wildcard addresses 0.0.0.0 and ::. Negating that classified all three as external, even though none of them identifies a host that a request could originate from. Return false for those cases before delegating to the internal matcher, and document the behavior on matchExternal. Closes gh-19072 Signed-off-by: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com> --- .../security/util/matcher/InetAddressMatchers.java | 10 ++++++++++ .../util/matcher/InetAddressMatchersTests.java | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/core/src/main/java/org/springframework/security/util/matcher/InetAddressMatchers.java b/core/src/main/java/org/springframework/security/util/matcher/InetAddressMatchers.java index dbe4e7be654..69d43823ea9 100644 --- a/core/src/main/java/org/springframework/security/util/matcher/InetAddressMatchers.java +++ b/core/src/main/java/org/springframework/security/util/matcher/InetAddressMatchers.java @@ -48,6 +48,9 @@ public static Builder builder() { /** * Creates a new builder configured to match external (non-private) IP addresses. + *

+ * A {@code null} address and the wildcard addresses ({@code 0.0.0.0} and {@code ::}) + * are not treated as external. * @return a {@link Builder} configured to match external addresses */ public static Builder matchExternal() { @@ -314,6 +317,10 @@ public String toString() { * External addresses are any addresses that are not internal (private) addresses. * This matcher delegates to {@link InternalInetAddressMatcher} and negates the * result. + *

+ * A {@code null} address and the wildcard addresses ({@code 0.0.0.0} and {@code ::}) + * are not external. They do not identify a host that a request could originate from, + * so negating the internal check is not meaningful for them. * * @author Gábor Vaspöri * @author Kian Jamali @@ -335,6 +342,9 @@ private ExternalInetAddressMatcher() { @Override public boolean matches(@Nullable InetAddress address) { + if (address == null || address.isAnyLocalAddress()) { + return false; + } return !this.internalMatcher.matches(address); } diff --git a/core/src/test/java/org/springframework/security/util/matcher/InetAddressMatchersTests.java b/core/src/test/java/org/springframework/security/util/matcher/InetAddressMatchersTests.java index 387af3c3814..3232cd44c05 100644 --- a/core/src/test/java/org/springframework/security/util/matcher/InetAddressMatchersTests.java +++ b/core/src/test/java/org/springframework/security/util/matcher/InetAddressMatchersTests.java @@ -461,6 +461,19 @@ void matchesWhenIpv6UniqueLocalThenReturnsFalse(String address) throws Exception assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); } + @Test + void matchesWhenNullThenReturnsFalse() { + InetAddressMatcher matcher = InetAddressMatchers.matchExternal().build(); + assertThat(matcher.matches((InetAddress) null)).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { "0.0.0.0", "::" }) + void matchesWhenWildcardThenReturnsFalse(String address) throws Exception { + InetAddressMatcher matcher = InetAddressMatchers.matchExternal().build(); + assertThat(matcher.matches(InetAddress.getByName(address))).isFalse(); + } + } @Nested