VYPR
Low severity3.3NVD Advisory· Published Feb 27, 2026· Updated Apr 29, 2026

CVE-2026-3293

CVE-2026-3293

Description

A weakness has been identified in snowflakedb snowflake-jdbc up to 4.0.1. Impacted is the function SdkProxyRoutePlanner of the file src/main/java/net/snowflake/client/internal/core/SdkProxyRoutePlanner.java of the component JDBC URL Handler. Executing a manipulation of the argument nonProxyHosts can lead to inefficient regular expression complexity. The attack can only be executed locally. The exploit has been made available to the public and could be used for attacks. This patch is called 5fb0a8a318a2ed87f4022a1f56e742424ba94052. A patch should be applied to remediate this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
net.snowflake:snowflake-jdbcMaven
<= 4.0.1

Affected products

1

Patches

1
5fb0a8a318a2

SNOW-3104251: Add sanitization for nonProxyHost RegEx patterns (#2506)

https://github.com/snowflakedb/snowflake-jdbcDawid HeymanFeb 18, 2026via ghsa
8 files changed · +133 26
  • CHANGELOG.md+1 0 modified
    @@ -5,6 +5,7 @@
         - Fix expired session token renewal when polling results (snowflakedb/snowflake-jdbc#2489)   
         - Fix missing minicore async initialization that was dropped during public API restructuring in v4.0.0
         - Adjust level of logging during Driver initialization
    +    - Add sanitization for nonProxyHosts RegEx patterns
     
     - v4.0.1
         - Add /etc/os-release data to Minicore telemetry
    
  • src/main/java/net/snowflake/client/internal/core/SdkProxyRoutePlanner.java+3 7 modified
    @@ -22,13 +22,8 @@ public SdkProxyRoutePlanner(
           String proxyHost, int proxyPort, HttpProtocol proxyProtocol, String nonProxyHosts) {
         super(DefaultSchemePortResolver.INSTANCE);
         proxy = new HttpHost(proxyHost, proxyPort, proxyProtocol.toString());
    -    // parseNonProxyHosts
         if (!SnowflakeUtil.isNullOrEmpty(nonProxyHosts)) {
    -      String[] hosts = nonProxyHosts.split("\\|");
    -      hostPatterns = new String[hosts.length];
    -      for (int i = 0; i < hosts.length; ++i) {
    -        hostPatterns[i] = hosts[i].toLowerCase().replace("*", ".*?");
    -      }
    +      hostPatterns = nonProxyHosts.split("\\|");
         } else {
           hostPatterns = null;
         }
    @@ -39,7 +34,8 @@ private boolean doesTargetMatchNonProxyHosts(HttpHost target) {
           return false;
         }
         String targetHost = target.getHostName().toLowerCase();
    -    return Arrays.stream(hostPatterns).anyMatch(targetHost::matches);
    +    return Arrays.stream(hostPatterns)
    +        .anyMatch(pattern -> SnowflakeUtil.hostnameMatchesGlob(targetHost, pattern));
       }
     
       @Override
    
  • src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/S3HttpUtil.java+2 9 modified
    @@ -11,6 +11,7 @@
     import net.snowflake.client.internal.core.HttpClientSettingsKey;
     import net.snowflake.client.internal.core.HttpUtil;
     import net.snowflake.client.internal.core.SFSessionProperty;
    +import net.snowflake.client.internal.jdbc.SnowflakeUtil;
     import net.snowflake.client.internal.log.SFLogger;
     import net.snowflake.client.internal.log.SFLoggerFactory;
     import net.snowflake.client.internal.log.SFLoggerUtil;
    @@ -150,15 +151,7 @@ public static ProxyConfiguration createSessionlessProxyConfigurationForS3(
       static Set<String> prepareNonProxyHosts(String nonProxyHosts) {
         return Arrays.stream(nonProxyHosts.split("\\|"))
             .map(String::trim)
    -        .map(
    -            host -> {
    -              // AWS SDK v2 Netty client expects proper Java regex patterns for non-proxy hosts.
    -              // Transform traditional proxy patterns to valid regex:
    -              // 1. Escape dots to match literal periods
    -              // 2. Replace wildcards (*) with regex equivalent (.*)
    -              // This differs from SdkProxyRoutePlanner which uses simple pattern matching
    -              return host.replace(".", "\\.").replace("*", ".*?");
    -            })
    +        .map(host -> SnowflakeUtil.globToSafePattern(host).pattern())
             .collect(Collectors.toSet());
       }
     }
    
  • src/main/java/net/snowflake/client/internal/jdbc/diagnostic/ProxyConfig.java+7 4 modified
    @@ -2,7 +2,7 @@
     
     import java.net.InetSocketAddress;
     import java.net.Proxy;
    -import java.util.regex.Pattern;
    +import net.snowflake.client.internal.jdbc.SnowflakeUtil;
     import net.snowflake.client.internal.log.SFLogger;
     import net.snowflake.client.internal.log.SFLoggerFactory;
     
    @@ -172,10 +172,13 @@ private void resolveProxyConfigurations() {
       }
     
       protected boolean isBypassProxy(String hostname) {
    -    String nonProxyHosts = getNonProxyHosts().replace(".", "\\.").replace("*", ".*");
    +    String nonProxyHosts = getNonProxyHosts();
    +    if (nonProxyHosts == null || nonProxyHosts.isEmpty()) {
    +      return false;
    +    }
         String[] nonProxyHostsArray = nonProxyHosts.split("\\|");
    -    for (String i : nonProxyHostsArray) {
    -      if (Pattern.compile(i).matcher(hostname).matches()) {
    +    for (String pattern : nonProxyHostsArray) {
    +      if (SnowflakeUtil.hostnameMatchesGlob(hostname, pattern)) {
             return true;
           }
         }
    
  • src/main/java/net/snowflake/client/internal/jdbc/SnowflakeUtil.java+19 0 modified
    @@ -41,6 +41,7 @@
     import java.util.concurrent.ThreadFactory;
     import java.util.concurrent.ThreadPoolExecutor;
     import java.util.concurrent.TimeUnit;
    +import java.util.regex.Pattern;
     import java.util.stream.Collectors;
     import net.snowflake.client.api.exception.ErrorCode;
     import net.snowflake.client.api.exception.SnowflakeSQLException;
    @@ -1006,4 +1007,22 @@ public static String byteToHexString(byte[] bytes) {
         }
         return new String(hexChars);
       }
    +
    +  /**
    +   * Converts a simple wildcard pattern (where only '*' is special) into a safe regex string by
    +   * quoting all literal parts via Pattern.quote(), preventing any regex metacharacters from being
    +   * interpreted. This avoids ReDoS vulnerabilities when patterns originate from user input.
    +   */
    +  public static Pattern globToSafePattern(String glob) {
    +    String safeRegex =
    +        Arrays.stream(glob.split("\\*", -1)).map(Pattern::quote).collect(Collectors.joining(".*"));
    +    return Pattern.compile(safeRegex, Pattern.CASE_INSENSITIVE);
    +  }
    +
    +  public static boolean hostnameMatchesGlob(String hostname, String pattern) {
    +    if (hostname == null || pattern == null) {
    +      return false;
    +    }
    +    return globToSafePattern(pattern.trim()).matcher(hostname).matches();
    +  }
     }
    
  • src/test/java/net/snowflake/client/internal/core/CoreUtilsMiscellaneousTest.java+37 2 modified
    @@ -3,6 +3,7 @@
     import static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;
     import static org.junit.jupiter.api.Assertions.assertEquals;
     import static org.junit.jupiter.api.Assertions.assertFalse;
    +import static org.junit.jupiter.api.Assertions.assertNotNull;
     import static org.junit.jupiter.api.Assertions.assertNull;
     import static org.junit.jupiter.api.Assertions.assertThrows;
     import static org.junit.jupiter.api.Assertions.assertTrue;
    @@ -19,6 +20,7 @@
     import net.snowflake.client.api.exception.SnowflakeSQLException;
     import net.snowflake.client.internal.jdbc.SnowflakeUtil;
     import net.snowflake.client.internal.jdbc.cloud.storage.S3HttpUtil;
    +import org.apache.http.HttpHost;
     import org.junit.jupiter.api.Test;
     import software.amazon.awssdk.http.nio.netty.ProxyConfiguration;
     
    @@ -114,7 +116,7 @@ public void testSetProxyForS3() {
         assertEquals("snowflakecomputing.com", proxyConfiguration.host());
         assertEquals(443, proxyConfiguration.port());
         assertEquals(
    -        new HashSet<>(Collections.singletonList(".*?\\.foo\\.com")),
    +        new HashSet<>(Collections.singletonList("\\Q\\E.*\\Q.foo.com\\E")),
             proxyConfiguration.nonProxyHosts());
         assertEquals("pw", proxyConfiguration.password());
         assertEquals("testuser", proxyConfiguration.username());
    @@ -136,7 +138,8 @@ public void testSetSessionlessProxyForS3() throws SnowflakeSQLException {
         assertEquals("localhost", proxyConfiguration.host());
         assertEquals(8084, proxyConfiguration.port());
         assertEquals(
    -        new HashSet<>(Arrays.asList("baz\\.com", "foo\\.com")), proxyConfiguration.nonProxyHosts());
    +        new HashSet<>(Arrays.asList("\\Qbaz.com\\E", "\\Qfoo.com\\E")),
    +        proxyConfiguration.nonProxyHosts());
         assertEquals("pw", proxyConfiguration.password());
         assertEquals("testuser", proxyConfiguration.username());
         // Test that exception is thrown when port number is invalid
    @@ -290,6 +293,38 @@ public void testSizeOfHttpClientMapWithGzipAndUserAgentSuffix() {
         assertEquals(3, HttpUtil.httpClient.size());
       }
     
    +  @Test
    +  public void testSdkProxyRoutePlannerNonProxyHostsBypassesProxy() throws Exception {
    +    SdkProxyRoutePlanner planner =
    +        new SdkProxyRoutePlanner(
    +            "proxy.example.com", 8080, HttpProtocol.HTTP, "*.internal.com|localhost");
    +    // Hosts matching nonProxyHosts should bypass proxy (determineProxy returns null)
    +    HttpHost internalHost = new HttpHost("app.internal.com");
    +    HttpHost localhostHost = new HttpHost("localhost");
    +    HttpHost externalHost = new HttpHost("external.com");
    +
    +    assertNull(planner.determineProxy(internalHost, null, null));
    +    assertNull(planner.determineProxy(localhostHost, null, null));
    +    assertNotNull(planner.determineProxy(externalHost, null, null));
    +  }
    +
    +  @Test
    +  public void testSdkProxyRoutePlannerReDoSPatternDoesNotHang() throws Exception {
    +    // The exact ReDoS payload from the vulnerability report
    +    SdkProxyRoutePlanner planner =
    +        new SdkProxyRoutePlanner("proxy.example.com", 8080, HttpProtocol.HTTP, "(a+)+");
    +    String maliciousHost =
    +        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac";
    +    HttpHost target = new HttpHost(maliciousHost);
    +
    +    long start = System.currentTimeMillis();
    +    assertNotNull(planner.determineProxy(target, null, null));
    +    long elapsed = System.currentTimeMillis() - start;
    +    assertTrue(
    +        elapsed < 1000,
    +        "Non-proxy host matching should complete nearly instantly, took " + elapsed + "ms");
    +  }
    +
       @Test
       public void testConvertProxyPropertiesToHttpClientKey() throws SnowflakeSQLException {
         OCSPMode mode = OCSPMode.FAIL_OPEN;
    
  • src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/S3HttpUtilTest.java+7 4 modified
    @@ -13,16 +13,19 @@
     class S3HttpUtilTest {
       static Stream<Arguments> prepareNonProxyHostsTestCases() {
         return Stream.of(
    -        Arguments.of("example.com", new HashSet<>(Arrays.asList("example\\.com"))),
    +        Arguments.of("example.com", new HashSet<>(Arrays.asList("\\Qexample.com\\E"))),
             Arguments.of(
                 "example.com|test.org | localhost",
    -            new HashSet<>(Arrays.asList("example\\.com", "test\\.org", "localhost"))),
    -        Arguments.of("*.example.com", new HashSet<>(Arrays.asList(".*?\\.example\\.com"))),
    +            new HashSet<>(Arrays.asList("\\Qexample.com\\E", "\\Qtest.org\\E", "\\Qlocalhost\\E"))),
    +        Arguments.of("*.example.com", new HashSet<>(Arrays.asList("\\Q\\E.*\\Q.example.com\\E"))),
             Arguments.of(
                 "example.com|*.test.org|localhost|*.internal.*",
                 new HashSet<>(
                     Arrays.asList(
    -                    "example\\.com", ".*?\\.test\\.org", "localhost", ".*?\\.internal\\..*?"))));
    +                    "\\Qexample.com\\E",
    +                    "\\Q\\E.*\\Q.test.org\\E",
    +                    "\\Qlocalhost\\E",
    +                    "\\Q\\E.*\\Q.internal.\\E.*\\Q\\E"))));
       }
     
       @ParameterizedTest
    
  • src/test/java/net/snowflake/client/internal/jdbc/SnowflakeUtilTest.java+57 0 modified
    @@ -4,6 +4,7 @@
     import static net.snowflake.client.internal.jdbc.SnowflakeUtil.createOwnerOnlyPermissionDir;
     import static net.snowflake.client.internal.jdbc.SnowflakeUtil.extractColumnMetadata;
     import static net.snowflake.client.internal.jdbc.SnowflakeUtil.getSnowflakeType;
    +import static net.snowflake.client.internal.jdbc.SnowflakeUtil.hostnameMatchesGlob;
     import static org.junit.jupiter.api.Assertions.assertEquals;
     import static org.junit.jupiter.api.Assertions.assertFalse;
     import static org.junit.jupiter.api.Assertions.assertNotNull;
    @@ -239,6 +240,62 @@ private static ObjectNode createFieldNode(
         return fieldNode;
       }
     
    +  @Test
    +  public void testHostnameMatchesGlob() {
    +    // Exact match
    +    assertTrue(hostnameMatchesGlob("localhost", "localhost"));
    +    assertTrue(hostnameMatchesGlob("example.com", "example.com"));
    +    assertFalse(hostnameMatchesGlob("example.com", "other.com"));
    +
    +    // Case insensitive
    +    assertTrue(hostnameMatchesGlob("EXAMPLE.COM", "example.com"));
    +    assertTrue(hostnameMatchesGlob("example.com", "EXAMPLE.COM"));
    +
    +    // Leading wildcard
    +    assertTrue(hostnameMatchesGlob("foo.example.com", "*.example.com"));
    +    assertTrue(hostnameMatchesGlob("bar.baz.example.com", "*.example.com"));
    +    assertFalse(hostnameMatchesGlob("example.com", "*.example.com"));
    +    assertFalse(hostnameMatchesGlob("notexample.com", "*.example.com"));
    +
    +    // Trailing wildcard
    +    assertTrue(hostnameMatchesGlob("192.168.1.1", "192.168.*"));
    +    assertFalse(hostnameMatchesGlob("10.0.0.1", "192.168.*"));
    +
    +    // Middle wildcard
    +    assertTrue(hostnameMatchesGlob("foo.bar.com", "foo.*.com"));
    +    assertFalse(hostnameMatchesGlob("bar.baz.com", "foo.*.com"));
    +
    +    // Wildcard only
    +    assertTrue(hostnameMatchesGlob("anything.example.com", "*"));
    +    assertTrue(hostnameMatchesGlob("", "*"));
    +
    +    // Whitespace trimming
    +    assertTrue(hostnameMatchesGlob("example.com", "  example.com  "));
    +
    +    // Null safety
    +    assertFalse(hostnameMatchesGlob(null, "*.example.com"));
    +    assertFalse(hostnameMatchesGlob("example.com", null));
    +    assertFalse(hostnameMatchesGlob(null, null));
    +
    +    // Regex metacharacters are treated as literals, not regex
    +    assertFalse(hostnameMatchesGlob("aaa", "(a+)+"));
    +    assertFalse(hostnameMatchesGlob("exampleXcom", "example.com"));
    +    assertTrue(hostnameMatchesGlob("example.com", "example.com"));
    +  }
    +
    +  @Test
    +  public void testHostnameMatchesGlobReDoSPatternDoesNotHang() {
    +    // Exact ReDoS payload from the vulnerability report (SNOW-3104251).
    +    // With unquoted regex this would cause catastrophic backtracking.
    +    String maliciousHost =
    +        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac";
    +    long start = System.currentTimeMillis();
    +    assertFalse(hostnameMatchesGlob(maliciousHost, "(a+)+"));
    +    long elapsed = System.currentTimeMillis() - start;
    +    assertTrue(
    +        elapsed < 1000, "Glob matching should complete nearly instantly, took " + elapsed + "ms");
    +  }
    +
       @Test
       public void testFieldMetadataSetterMethods() {
         FieldMetadataImpl fieldMetadata = new FieldMetadataImpl();
    

Vulnerability mechanics

Synthesis attempt was rejected by the grounding validator. Re-run pending.

References

9

News mentions

0

No linked articles in our index yet.