From 2c8b71c7da47cd2da056a22ab056a94a4b0ae992 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Tue, 9 Jun 2026 12:07:00 +0700 Subject: [PATCH 01/21] IGNITE-28743: Block remote HTTP/HTTPS/FTP URLs in resolveSpringUrl to prevent RCE via JDBC cfg:// --- .../ignite/internal/util/IgniteUtils.java | 40 +++++++++ .../internal/util/IgniteUtilsSelfTest.java | 89 +++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index d3770f81e541e..cd78ba46400db 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -375,6 +375,21 @@ public abstract class IgniteUtils extends CommonUtils { /** Ignite Work Directory. */ public static final String IGNITE_WORK_DIR = System.getenv(IgniteSystemProperties.IGNITE_WORK_DIR); + /** + * System property to allow remote HTTP/HTTPS URLs when loading Spring XML configuration. + * Remote URLs are blocked by default to prevent RCE via attacker-controlled Spring XML. + * FTP is always blocked regardless of this property due to MITM risk. + */ + public static final String IGNITE_ALLOW_REMOTE_SPRING_CFG_URL = "ignite.spring.cfg.allowRemoteUrl"; + + /** URL schemes that load remote content and are blocked by default in Spring configuration. */ + private static final Set REMOTE_CFG_SCHEMES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList("http", "https", "ftp", "ftps"))); + + /** URL schemes that are always blocked regardless of system property due to security risk. */ + private static final Set ALWAYS_BLOCKED_CFG_SCHEMES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList("ftp", "ftps"))); + /** Random is used to get random server node to authentication from client node. */ private static final Random RND = new Random(System.currentTimeMillis()); @@ -2562,6 +2577,31 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc try { url = new URL(springCfgPath); + + String scheme = url.getProtocol().toLowerCase(); + + if (REMOTE_CFG_SCHEMES.contains(scheme)) { + // FTP is always blocked — unencrypted, susceptible to MITM + if (ALWAYS_BLOCKED_CFG_SCHEMES.contains(scheme)) + throw new IgniteCheckedException( + "Spring configuration URLs with scheme '" + scheme + "' are always blocked " + + "due to security risk (unencrypted transfer, MITM vulnerability). " + + "Use HTTPS or a local file/classpath reference instead. " + + "Provided host: " + url.getHost() + ); + + // HTTP/HTTPS blocked by default, allowed via system property + boolean allowRemote = Boolean.getBoolean(IGNITE_ALLOW_REMOTE_SPRING_CFG_URL); + + if (!allowRemote) + throw new IgniteCheckedException( + "Remote Spring configuration URLs (http/https) are not allowed by default " + + "to prevent remote code execution via attacker-controlled Spring XML. " + + "Provided host: " + url.getHost() + ". " + + "To allow remote URLs set system property: -D" + + IGNITE_ALLOW_REMOTE_SPRING_CFG_URL + "=true" + ); + } } catch (MalformedURLException e) { url = resolveIgniteUrl(springCfgPath); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java index 19f6ad17c38d8..6ed838c68ec67 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java @@ -1615,6 +1615,95 @@ public void testLongToBytes() { } } + /** + * Test that remote HTTP URL in Spring cfg is blocked by default. + */ + @Test + public void testResolveSpringUrlBlocksHttpByDefault() { + assertThrows(log, () -> { + IgniteUtils.resolveSpringUrl("http://attacker.example.com/evil.xml"); + return null; + }, IgniteCheckedException.class, "Remote Spring configuration URLs"); + } + + /** + * Test that remote HTTPS URL in Spring cfg is blocked by default. + */ + @Test + public void testResolveSpringUrlBlocksHttpsByDefault() { + assertThrows(log, () -> { + IgniteUtils.resolveSpringUrl("https://attacker.example.com/evil.xml"); + return null; + }, IgniteCheckedException.class, "Remote Spring configuration URLs"); + } + + /** + * Test that remote FTP URL in Spring cfg is blocked by default. + */ + @Test + public void testResolveSpringUrlBlocksFtpByDefault() { + assertThrows(log, () -> { + IgniteUtils.resolveSpringUrl("ftp://attacker.example.com/evil.xml"); + return null; + }, IgniteCheckedException.class, "always blocked"); + } + + /** + * Test that error message contains guidance on how to enable remote URLs. + */ + @Test + public void testResolveSpringUrlErrorMessageContainsGuidance() { + try { + IgniteUtils.resolveSpringUrl("http://attacker.example.com/evil.xml"); + fail("Expected IgniteCheckedException"); + } + catch (IgniteCheckedException e) { + assertTrue( + "Error message should contain system property name", + e.getMessage().contains(IgniteUtils.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL) + ); + assertFalse( + "Error message should not contain full URL to avoid credential leak", + e.getMessage().contains("http://attacker.example.com/evil.xml") + ); + assertTrue( + "Error message should contain host", + e.getMessage().contains("attacker.example.com") + ); + } + } + + /** + * Test that remote HTTP URL is allowed when system property is set. + */ + @Test + @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") + public void testResolveSpringUrlAllowsHttpWhenPropertySet() { + // Should not throw — validation passes when flag is true. + // Will throw MalformedURLException or connection error, not our security check. + try { + IgniteUtils.resolveSpringUrl("http://127.0.0.1:1/nonexistent.xml"); + } + catch (IgniteCheckedException e) { + assertFalse( + "Should not throw security exception when flag is enabled", + e.getMessage().contains("Remote Spring configuration URLs") + ); + } + } + + /** + * Test that FTP is always blocked even when remote URL property is set. + */ + @Test + @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") + public void testResolveSpringUrlFtpAlwaysBlocked() { + assertThrows(log, () -> { + IgniteUtils.resolveSpringUrl("ftp://attacker.example.com/evil.xml"); + return null; + }, IgniteCheckedException.class, "always blocked"); + } + /** */ private byte[] asByteArray(String text) { String[] split = text.split("-"); From 6359859a349b052346c0ed3401bb1567dd877e60 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Tue, 9 Jun 2026 12:37:44 +0700 Subject: [PATCH 02/21] IGNITE-28743 Validate URL scheme in resolveSpringUrl --- .../apache/ignite/IgniteSystemProperties.java | 9 +++++++++ .../ignite/internal/util/IgniteUtils.java | 11 ++--------- .../internal/util/IgniteUtilsSelfTest.java | 18 ++++-------------- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java index 430f5bfbf734f..2e402a946abb1 100644 --- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java +++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java @@ -1908,6 +1908,15 @@ public final class IgniteSystemProperties extends IgniteCommonsSystemProperties @SystemProperty(value = "Packages list to expose in configuration view") public static final String IGNITE_CONFIGURATION_VIEW_PACKAGES = "IGNITE_CONFIGURATION_VIEW_PACKAGES"; + + /** + * System property to allow remote HTTP/HTTPS URLs when loading Spring XML configuration. + * Remote URLs are blocked by default to prevent RCE via attacker-controlled Spring XML. + * FTP is always blocked regardless of this property due to MITM risk. + */ + @SystemProperty(value = "Allow remote HTTP/HTTPS URLs when loading Spring XML configuration") + public static final String IGNITE_ALLOW_REMOTE_SPRING_CFG_URL = "ignite.spring.cfg.allowRemoteUrl"; + /** * Enforces singleton. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index cd78ba46400db..e83d9a411d75b 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -375,13 +375,6 @@ public abstract class IgniteUtils extends CommonUtils { /** Ignite Work Directory. */ public static final String IGNITE_WORK_DIR = System.getenv(IgniteSystemProperties.IGNITE_WORK_DIR); - /** - * System property to allow remote HTTP/HTTPS URLs when loading Spring XML configuration. - * Remote URLs are blocked by default to prevent RCE via attacker-controlled Spring XML. - * FTP is always blocked regardless of this property due to MITM risk. - */ - public static final String IGNITE_ALLOW_REMOTE_SPRING_CFG_URL = "ignite.spring.cfg.allowRemoteUrl"; - /** URL schemes that load remote content and are blocked by default in Spring configuration. */ private static final Set REMOTE_CFG_SCHEMES = Collections.unmodifiableSet( new HashSet<>(Arrays.asList("http", "https", "ftp", "ftps"))); @@ -2591,7 +2584,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc ); // HTTP/HTTPS blocked by default, allowed via system property - boolean allowRemote = Boolean.getBoolean(IGNITE_ALLOW_REMOTE_SPRING_CFG_URL); + boolean allowRemote = Boolean.getBoolean(IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL); if (!allowRemote) throw new IgniteCheckedException( @@ -2599,7 +2592,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc "to prevent remote code execution via attacker-controlled Spring XML. " + "Provided host: " + url.getHost() + ". " + "To allow remote URLs set system property: -D" + - IGNITE_ALLOW_REMOTE_SPRING_CFG_URL + "=true" + IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL + "=true" ); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java index 6ed838c68ec67..e0149ef69bd41 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java @@ -1615,17 +1615,6 @@ public void testLongToBytes() { } } - /** - * Test that remote HTTP URL in Spring cfg is blocked by default. - */ - @Test - public void testResolveSpringUrlBlocksHttpByDefault() { - assertThrows(log, () -> { - IgniteUtils.resolveSpringUrl("http://attacker.example.com/evil.xml"); - return null; - }, IgniteCheckedException.class, "Remote Spring configuration URLs"); - } - /** * Test that remote HTTPS URL in Spring cfg is blocked by default. */ @@ -1649,10 +1638,11 @@ public void testResolveSpringUrlBlocksFtpByDefault() { } /** - * Test that error message contains guidance on how to enable remote URLs. + * Test that remote HTTP URL in Spring cfg is blocked by default + * and error message contains guidance on how to enable remote URLs. */ @Test - public void testResolveSpringUrlErrorMessageContainsGuidance() { + public void testResolveSpringUrlBlocksHttpByDefault() { try { IgniteUtils.resolveSpringUrl("http://attacker.example.com/evil.xml"); fail("Expected IgniteCheckedException"); @@ -1660,7 +1650,7 @@ public void testResolveSpringUrlErrorMessageContainsGuidance() { catch (IgniteCheckedException e) { assertTrue( "Error message should contain system property name", - e.getMessage().contains(IgniteUtils.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL) + e.getMessage().contains(IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL) ); assertFalse( "Error message should not contain full URL to avoid credential leak", From 764048d3d460fb0a8bedb6a6bbb44be05cd7b035 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Mon, 15 Jun 2026 12:12:23 +0700 Subject: [PATCH 03/21] IGNITE-28743 Add jdbc tests --- .../jdbc2/JdbcConnectionSelfTest.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index b8056bfaca7a8..96f1bdc727402 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -296,4 +296,67 @@ public void testSqlHints() throws Exception { assertTrue(((JdbcConnection)conn).skipReducerOnUpdate()); } } + + /** + * Test that JDBC cfg:// URL with remote HTTP location is blocked by default to prevent RCE. + */ + @Test + public void testRemoteHttpCfgUrlIsBlocked() { + final String url = CFG_URL_PREFIX + "http://attacker.example.com/evil.xml"; + + GridTestUtils.assertThrows( + log, + new Callable() { + @Override public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; + } + } + }, + SQLException.class, + "Remote Spring configuration URLs" + ); + } + + /** + * Test that JDBC cfg:// URL with remote HTTPS location is blocked by default to prevent RCE. + */ + @Test + public void testRemoteHttpsCfgUrlIsBlocked() { + final String url = CFG_URL_PREFIX + "https://attacker.example.com/evil.xml"; + + GridTestUtils.assertThrows( + log, + new Callable() { + @Override public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; + } + } + }, + SQLException.class, + "Remote Spring configuration URLs" + ); + } + + /** + * Test that JDBC cfg:// URL with FTP location is always blocked. + */ + @Test + public void testFtpCfgUrlIsAlwaysBlocked() { + final String url = CFG_URL_PREFIX + "ftp://attacker.example.com/evil.xml"; + + GridTestUtils.assertThrows( + log, + new Callable() { + @Override public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; + } + } + }, + SQLException.class, + "always blocked" + ); + } } From 669854005b621bf1c1ec5128a3164e7e8d7bc84f Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Mon, 22 Jun 2026 14:29:23 +0700 Subject: [PATCH 04/21] IGNITE-28743 Combine three tests into one --- .../jdbc2/JdbcConnectionSelfTest.java | 73 +++++-------------- 1 file changed, 17 insertions(+), 56 deletions(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index 96f1bdc727402..0e69395df736e 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -20,6 +20,7 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.Arrays; import java.util.UUID; import java.util.concurrent.Callable; import org.apache.ignite.configuration.CacheConfiguration; @@ -298,65 +299,25 @@ public void testSqlHints() throws Exception { } /** - * Test that JDBC cfg:// URL with remote HTTP location is blocked by default to prevent RCE. + * Test that JDBC cfg:// URL with remote HTTP, HTTPS, and FTP location is blocked. */ @Test - public void testRemoteHttpCfgUrlIsBlocked() { - final String url = CFG_URL_PREFIX + "http://attacker.example.com/evil.xml"; + public void testRemoteCfgUrlsAreBlocked() { + for (String scheme : Arrays.asList("http", "https", "ftp", "ftps")) { + final String url = CFG_URL_PREFIX + scheme + "://attacker.example.com/evil.xml"; - GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; - } - } - }, - SQLException.class, - "Remote Spring configuration URLs" - ); - } - - /** - * Test that JDBC cfg:// URL with remote HTTPS location is blocked by default to prevent RCE. - */ - @Test - public void testRemoteHttpsCfgUrlIsBlocked() { - final String url = CFG_URL_PREFIX + "https://attacker.example.com/evil.xml"; - - GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; - } - } - }, - SQLException.class, - "Remote Spring configuration URLs" - ); - } - - /** - * Test that JDBC cfg:// URL with FTP location is always blocked. - */ - @Test - public void testFtpCfgUrlIsAlwaysBlocked() { - final String url = CFG_URL_PREFIX + "ftp://attacker.example.com/evil.xml"; - - GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; + GridTestUtils.assertThrows( + log, + new Callable() { + @Override public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; + } } - } - }, - SQLException.class, - "always blocked" - ); + }, + SQLException.class, + null + ); + } } } From 4806277fef6263f405713c4a9324c63f285d3a73 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Mon, 22 Jun 2026 14:39:01 +0700 Subject: [PATCH 05/21] IGNITE-28743 Add a test that verifies that no security-exception is thrown when the flag is enabled --- .../jdbc2/JdbcConnectionSelfTest.java | 155 +++++++++++------- 1 file changed, 97 insertions(+), 58 deletions(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index 0e69395df736e..6ac3cca69ece6 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -27,6 +27,7 @@ import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.WithSystemProperty; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.jetbrains.annotations.NotNull; import org.junit.Test; @@ -37,10 +38,14 @@ * Connection test. */ public class JdbcConnectionSelfTest extends GridCommonAbstractTest { - /** Custom cache name. */ + /** + * Custom cache name. + */ private static final String CUSTOM_CACHE_NAME = "custom-cache"; - /** Grid count. */ + /** + * Grid count. + */ private static final int GRID_CNT = 2; /** @@ -50,8 +55,11 @@ protected String configURL() { return "modules/clients/src/test/config/jdbc-config.xml"; } - /** {@inheritDoc} */ - @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + /** + * {@inheritDoc} + */ + @Override + protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); cfg.setCacheConfiguration(cacheConfiguration(DEFAULT_CACHE_NAME), cacheConfiguration(CUSTOM_CACHE_NAME)); @@ -72,8 +80,11 @@ private CacheConfiguration cacheConfiguration(@NotNull String name) throws Excep return cfg; } - /** {@inheritDoc} */ - @Override protected void beforeTestsStarted() throws Exception { + /** + * {@inheritDoc} + */ + @Override + protected void beforeTestsStarted() throws Exception { startGridsMultiThreaded(GRID_CNT); } @@ -86,12 +97,12 @@ public void testDefaults() throws Exception { try (Connection conn = DriverManager.getConnection(url)) { assertNotNull(conn); - assertTrue(((JdbcConnection)conn).ignite().configuration().isClientMode()); + assertTrue(((JdbcConnection) conn).ignite().configuration().isClientMode()); } try (Connection conn = DriverManager.getConnection(url + '/')) { assertNotNull(conn); - assertTrue(((JdbcConnection)conn).ignite().configuration().isClientMode()); + assertTrue(((JdbcConnection) conn).ignite().configuration().isClientMode()); } } @@ -125,7 +136,8 @@ public void testWrongNodeId() throws Exception { GridTestUtils.assertThrows( log, new Callable() { - @Override public Object call() throws Exception { + @Override + public Object call() throws Exception { try (Connection conn = DriverManager.getConnection(url)) { return conn; } @@ -150,7 +162,8 @@ public void testClientNodeId() throws Exception { GridTestUtils.assertThrows( log, new Callable() { - @Override public Object call() throws Exception { + @Override + public Object call() throws Exception { try (Connection conn = DriverManager.getConnection(url)) { return conn; } @@ -181,16 +194,17 @@ public void testWrongCache() throws Exception { final String url = CFG_URL_PREFIX + "cache=wrongCacheName@" + configURL(); GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; + log, + new Callable() { + @Override + public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; + } } - } - }, - SQLException.class, - "Client is invalid. Probably cache name is wrong." + }, + SQLException.class, + "Client is invalid. Probably cache name is wrong." ); } @@ -210,16 +224,17 @@ public void testClose() throws Exception { assertTrue(conn.isClosed()); GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - conn.isValid(2); + log, + new Callable() { + @Override + public Object call() throws Exception { + conn.isValid(2); - return null; - } - }, - SQLException.class, - "Connection is closed." + return null; + } + }, + SQLException.class, + "Connection is closed." ); } } @@ -266,35 +281,35 @@ public void testTxAllowedRollback() throws Exception { @Test public void testSqlHints() throws Exception { try (final Connection conn = DriverManager.getConnection(CFG_URL_PREFIX + "enforceJoinOrder=true@" - + configURL())) { - assertTrue(((JdbcConnection)conn).isEnforceJoinOrder()); - assertFalse(((JdbcConnection)conn).isDistributedJoins()); - assertFalse(((JdbcConnection)conn).isCollocatedQuery()); - assertFalse(((JdbcConnection)conn).skipReducerOnUpdate()); + + configURL())) { + assertTrue(((JdbcConnection) conn).isEnforceJoinOrder()); + assertFalse(((JdbcConnection) conn).isDistributedJoins()); + assertFalse(((JdbcConnection) conn).isCollocatedQuery()); + assertFalse(((JdbcConnection) conn).skipReducerOnUpdate()); } try (final Connection conn = DriverManager.getConnection(CFG_URL_PREFIX + "distributedJoins=true@" - + configURL())) { - assertFalse(((JdbcConnection)conn).isEnforceJoinOrder()); - assertTrue(((JdbcConnection)conn).isDistributedJoins()); - assertFalse(((JdbcConnection)conn).isCollocatedQuery()); - assertFalse(((JdbcConnection)conn).skipReducerOnUpdate()); + + configURL())) { + assertFalse(((JdbcConnection) conn).isEnforceJoinOrder()); + assertTrue(((JdbcConnection) conn).isDistributedJoins()); + assertFalse(((JdbcConnection) conn).isCollocatedQuery()); + assertFalse(((JdbcConnection) conn).skipReducerOnUpdate()); } try (final Connection conn = DriverManager.getConnection(CFG_URL_PREFIX + "collocated=true@" - + configURL())) { - assertFalse(((JdbcConnection)conn).isEnforceJoinOrder()); - assertFalse(((JdbcConnection)conn).isDistributedJoins()); - assertTrue(((JdbcConnection)conn).isCollocatedQuery()); - assertFalse(((JdbcConnection)conn).skipReducerOnUpdate()); + + configURL())) { + assertFalse(((JdbcConnection) conn).isEnforceJoinOrder()); + assertFalse(((JdbcConnection) conn).isDistributedJoins()); + assertTrue(((JdbcConnection) conn).isCollocatedQuery()); + assertFalse(((JdbcConnection) conn).skipReducerOnUpdate()); } try (final Connection conn = DriverManager.getConnection(CFG_URL_PREFIX + "skipReducerOnUpdate=true@" - + configURL())) { - assertFalse(((JdbcConnection)conn).isEnforceJoinOrder()); - assertFalse(((JdbcConnection)conn).isDistributedJoins()); - assertFalse(((JdbcConnection)conn).isCollocatedQuery()); - assertTrue(((JdbcConnection)conn).skipReducerOnUpdate()); + + configURL())) { + assertFalse(((JdbcConnection) conn).isEnforceJoinOrder()); + assertFalse(((JdbcConnection) conn).isDistributedJoins()); + assertFalse(((JdbcConnection) conn).isCollocatedQuery()); + assertTrue(((JdbcConnection) conn).skipReducerOnUpdate()); } } @@ -307,17 +322,41 @@ public void testRemoteCfgUrlsAreBlocked() { final String url = CFG_URL_PREFIX + scheme + "://attacker.example.com/evil.xml"; GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; + log, + new Callable() { + @Override + public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; + } } - } - }, - SQLException.class, - null + }, + SQLException.class, + null ); } } -} + + /** + * Test that JDBC cfg:// URL with remote HTTP location is allowed when system property is set. + */ + @Test + @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") + public void testRemoteHttpCfgUrlAllowedWhenFlagSet() { + final String url = CFG_URL_PREFIX + "http://127.0.0.1:1/nonexistent.xml"; + + GridTestUtils.assertThrows( + log, + new Callable() { + @Override + public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; + } + } + }, + SQLException.class, + null + ); + } +} \ No newline at end of file From b58630129ed3948132beaa86dc393ac60f2aad1e Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Fri, 26 Jun 2026 15:03:40 +0700 Subject: [PATCH 06/21] IGNITE-28743 Add JDBC end-to-end tests for remote cfg:// URL blocking --- .../jdbc2/JdbcConnectionSelfTest.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index 6ac3cca69ece6..c6a1c9a595e12 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -345,18 +345,14 @@ public Object call() throws Exception { public void testRemoteHttpCfgUrlAllowedWhenFlagSet() { final String url = CFG_URL_PREFIX + "http://127.0.0.1:1/nonexistent.xml"; - GridTestUtils.assertThrows( - log, - new Callable() { - @Override - public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; - } - } - }, - SQLException.class, - null - ); + try { + DriverManager.getConnection(url); + } + catch (SQLException e) { + assertFalse( + "Security exception should not be thrown when flag is enabled", + e.getMessage().contains("Remote Spring configuration URLs") + ); + } } } \ No newline at end of file From 2af3a6e5b87dfaca0806ec64518dbb8124da27c6 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Fri, 26 Jun 2026 19:49:30 +0700 Subject: [PATCH 07/21] IGNITE-28743 Fix code style --- .../jdbc2/JdbcConnectionSelfTest.java | 45 ++++++------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index c6a1c9a595e12..cae2a1ee645c1 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -38,14 +38,10 @@ * Connection test. */ public class JdbcConnectionSelfTest extends GridCommonAbstractTest { - /** - * Custom cache name. - */ + /** Custom cache name. */ private static final String CUSTOM_CACHE_NAME = "custom-cache"; - /** - * Grid count. - */ + /** Grid count. */ private static final int GRID_CNT = 2; /** @@ -55,11 +51,8 @@ protected String configURL() { return "modules/clients/src/test/config/jdbc-config.xml"; } - /** - * {@inheritDoc} - */ - @Override - protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); cfg.setCacheConfiguration(cacheConfiguration(DEFAULT_CACHE_NAME), cacheConfiguration(CUSTOM_CACHE_NAME)); @@ -80,11 +73,8 @@ private CacheConfiguration cacheConfiguration(@NotNull String name) throws Excep return cfg; } - /** - * {@inheritDoc} - */ - @Override - protected void beforeTestsStarted() throws Exception { + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { startGridsMultiThreaded(GRID_CNT); } @@ -136,8 +126,7 @@ public void testWrongNodeId() throws Exception { GridTestUtils.assertThrows( log, new Callable() { - @Override - public Object call() throws Exception { + @Override public Object call() throws Exception { try (Connection conn = DriverManager.getConnection(url)) { return conn; } @@ -162,8 +151,7 @@ public void testClientNodeId() throws Exception { GridTestUtils.assertThrows( log, new Callable() { - @Override - public Object call() throws Exception { + @Override public Object call() throws Exception { try (Connection conn = DriverManager.getConnection(url)) { return conn; } @@ -196,8 +184,7 @@ public void testWrongCache() throws Exception { GridTestUtils.assertThrows( log, new Callable() { - @Override - public Object call() throws Exception { + @Override public Object call() throws Exception { try (Connection conn = DriverManager.getConnection(url)) { return conn; } @@ -226,8 +213,7 @@ public void testClose() throws Exception { GridTestUtils.assertThrows( log, new Callable() { - @Override - public Object call() throws Exception { + @Override public Object call() throws Exception { conn.isValid(2); return null; @@ -313,9 +299,7 @@ public void testSqlHints() throws Exception { } } - /** - * Test that JDBC cfg:// URL with remote HTTP, HTTPS, and FTP location is blocked. - */ + /** Test that JDBC cfg:// URL with remote HTTP, HTTPS, and FTP location is blocked. */ @Test public void testRemoteCfgUrlsAreBlocked() { for (String scheme : Arrays.asList("http", "https", "ftp", "ftps")) { @@ -324,8 +308,7 @@ public void testRemoteCfgUrlsAreBlocked() { GridTestUtils.assertThrows( log, new Callable() { - @Override - public Object call() throws Exception { + @Override public Object call() throws Exception { try (Connection conn = DriverManager.getConnection(url)) { return conn; } @@ -337,9 +320,7 @@ public Object call() throws Exception { } } - /** - * Test that JDBC cfg:// URL with remote HTTP location is allowed when system property is set. - */ + /** Test that JDBC cfg:// URL with remote HTTP location is allowed when system property is set. */ @Test @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") public void testRemoteHttpCfgUrlAllowedWhenFlagSet() { From ac366c039243c38726bfcab61f311e3666cf96e7 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Fri, 26 Jun 2026 19:57:06 +0700 Subject: [PATCH 08/21] IGNITE-28743 Fix code style --- .../jdbc2/JdbcConnectionSelfTest.java | 158 ++++++++++-------- 1 file changed, 85 insertions(+), 73 deletions(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index cae2a1ee645c1..4a5bfc5055626 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -38,10 +38,14 @@ * Connection test. */ public class JdbcConnectionSelfTest extends GridCommonAbstractTest { - /** Custom cache name. */ + /** + * Custom cache name. + */ private static final String CUSTOM_CACHE_NAME = "custom-cache"; - /** Grid count. */ + /** + * Grid count. + */ private static final int GRID_CNT = 2; /** @@ -51,7 +55,9 @@ protected String configURL() { return "modules/clients/src/test/config/jdbc-config.xml"; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); @@ -73,7 +79,9 @@ private CacheConfiguration cacheConfiguration(@NotNull String name) throws Excep return cfg; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override protected void beforeTestsStarted() throws Exception { startGridsMultiThreaded(GRID_CNT); } @@ -87,12 +95,12 @@ public void testDefaults() throws Exception { try (Connection conn = DriverManager.getConnection(url)) { assertNotNull(conn); - assertTrue(((JdbcConnection) conn).ignite().configuration().isClientMode()); + assertTrue(((JdbcConnection)conn).ignite().configuration().isClientMode()); } try (Connection conn = DriverManager.getConnection(url + '/')) { assertNotNull(conn); - assertTrue(((JdbcConnection) conn).ignite().configuration().isClientMode()); + assertTrue(((JdbcConnection)conn).ignite().configuration().isClientMode()); } } @@ -124,16 +132,16 @@ public void testWrongNodeId() throws Exception { final String url = CFG_URL_PREFIX + "nodeId=" + wrongId + '@' + configURL(); GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; - } + log, + new Callable() { + @Override public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; } - }, - SQLException.class, - "Failed to establish connection with node (is it a server node?): " + wrongId + } + }, + SQLException.class, + "Failed to establish connection with node (is it a server node?): " + wrongId ); } @@ -149,16 +157,16 @@ public void testClientNodeId() throws Exception { final String url = CFG_URL_PREFIX + "nodeId=" + clientId + '@' + configURL(); GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; - } + log, + new Callable() { + @Override public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; } - }, - SQLException.class, - "Failed to establish connection with node (is it a server node?): " + clientId + } + }, + SQLException.class, + "Failed to establish connection with node (is it a server node?): " + clientId ); } @@ -182,16 +190,16 @@ public void testWrongCache() throws Exception { final String url = CFG_URL_PREFIX + "cache=wrongCacheName@" + configURL(); GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; - } + log, + new Callable() { + @Override public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; } - }, - SQLException.class, - "Client is invalid. Probably cache name is wrong." + } + }, + SQLException.class, + "Client is invalid. Probably cache name is wrong." ); } @@ -211,16 +219,16 @@ public void testClose() throws Exception { assertTrue(conn.isClosed()); GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - conn.isValid(2); + log, + new Callable() { + @Override public Object call() throws Exception { + conn.isValid(2); - return null; - } - }, - SQLException.class, - "Connection is closed." + return null; + } + }, + SQLException.class, + "Connection is closed." ); } } @@ -267,60 +275,64 @@ public void testTxAllowedRollback() throws Exception { @Test public void testSqlHints() throws Exception { try (final Connection conn = DriverManager.getConnection(CFG_URL_PREFIX + "enforceJoinOrder=true@" - + configURL())) { - assertTrue(((JdbcConnection) conn).isEnforceJoinOrder()); - assertFalse(((JdbcConnection) conn).isDistributedJoins()); - assertFalse(((JdbcConnection) conn).isCollocatedQuery()); - assertFalse(((JdbcConnection) conn).skipReducerOnUpdate()); + + configURL())) { + assertTrue(((JdbcConnection)conn).isEnforceJoinOrder()); + assertFalse(((JdbcConnection)conn).isDistributedJoins()); + assertFalse(((JdbcConnection)conn).isCollocatedQuery()); + assertFalse(((JdbcConnection)conn).skipReducerOnUpdate()); } try (final Connection conn = DriverManager.getConnection(CFG_URL_PREFIX + "distributedJoins=true@" - + configURL())) { - assertFalse(((JdbcConnection) conn).isEnforceJoinOrder()); - assertTrue(((JdbcConnection) conn).isDistributedJoins()); - assertFalse(((JdbcConnection) conn).isCollocatedQuery()); - assertFalse(((JdbcConnection) conn).skipReducerOnUpdate()); + + configURL())) { + assertFalse(((JdbcConnection)conn).isEnforceJoinOrder()); + assertTrue(((JdbcConnection)conn).isDistributedJoins()); + assertFalse(((JdbcConnection)conn).isCollocatedQuery()); + assertFalse(((JdbcConnection)conn).skipReducerOnUpdate()); } try (final Connection conn = DriverManager.getConnection(CFG_URL_PREFIX + "collocated=true@" - + configURL())) { - assertFalse(((JdbcConnection) conn).isEnforceJoinOrder()); - assertFalse(((JdbcConnection) conn).isDistributedJoins()); - assertTrue(((JdbcConnection) conn).isCollocatedQuery()); - assertFalse(((JdbcConnection) conn).skipReducerOnUpdate()); + + configURL())) { + assertFalse(((JdbcConnection)conn).isEnforceJoinOrder()); + assertFalse(((JdbcConnection)conn).isDistributedJoins()); + assertTrue(((JdbcConnection)conn).isCollocatedQuery()); + assertFalse(((JdbcConnection)conn).skipReducerOnUpdate()); } try (final Connection conn = DriverManager.getConnection(CFG_URL_PREFIX + "skipReducerOnUpdate=true@" - + configURL())) { - assertFalse(((JdbcConnection) conn).isEnforceJoinOrder()); - assertFalse(((JdbcConnection) conn).isDistributedJoins()); - assertFalse(((JdbcConnection) conn).isCollocatedQuery()); - assertTrue(((JdbcConnection) conn).skipReducerOnUpdate()); + + configURL())) { + assertFalse(((JdbcConnection)conn).isEnforceJoinOrder()); + assertFalse(((JdbcConnection)conn).isDistributedJoins()); + assertFalse(((JdbcConnection)conn).isCollocatedQuery()); + assertTrue(((JdbcConnection)conn).skipReducerOnUpdate()); } } - /** Test that JDBC cfg:// URL with remote HTTP, HTTPS, and FTP location is blocked. */ + /** + * Test that JDBC cfg:// URL with remote HTTP, HTTPS, and FTP location is blocked. + */ @Test public void testRemoteCfgUrlsAreBlocked() { for (String scheme : Arrays.asList("http", "https", "ftp", "ftps")) { final String url = CFG_URL_PREFIX + scheme + "://attacker.example.com/evil.xml"; GridTestUtils.assertThrows( - log, - new Callable() { - @Override public Object call() throws Exception { - try (Connection conn = DriverManager.getConnection(url)) { - return conn; - } + log, + new Callable() { + @Override public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; } - }, - SQLException.class, - null + } + }, + SQLException.class, + null ); } } - /** Test that JDBC cfg:// URL with remote HTTP location is allowed when system property is set. */ + /** + * Test that JDBC cfg:// URL with remote HTTP location is allowed when system property is set. + */ @Test @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") public void testRemoteHttpCfgUrlAllowedWhenFlagSet() { From a479bf0a762b90c3527b1d826961add6d07ed737 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Tue, 30 Jun 2026 11:02:30 +0700 Subject: [PATCH 09/21] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../ignite/internal/util/IgniteUtils.java | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index e83d9a411d75b..621e1d24c3cbc 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2571,26 +2571,42 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc try { url = new URL(springCfgPath); - String scheme = url.getProtocol().toLowerCase(); + URL cfgUrl = url; + + String scheme = cfgUrl.getProtocol().toLowerCase(Locale.ROOT); + + // Handle jar:!/path to avoid bypassing remote-scheme checks. + if ("jar".equals(scheme)) { + String file = cfgUrl.getFile(); + + int sep = file.indexOf("!/"); + + if (sep > 0) { + try { + cfgUrl = new URL(file.substring(0, sep)); + scheme = cfgUrl.getProtocol().toLowerCase(Locale.ROOT); + } + catch (MalformedURLException ignored) { + // No-op. + } + } + } if (REMOTE_CFG_SCHEMES.contains(scheme)) { - // FTP is always blocked — unencrypted, susceptible to MITM if (ALWAYS_BLOCKED_CFG_SCHEMES.contains(scheme)) throw new IgniteCheckedException( "Spring configuration URLs with scheme '" + scheme + "' are always blocked " + - "due to security risk (unencrypted transfer, MITM vulnerability). " + - "Use HTTPS or a local file/classpath reference instead. " + - "Provided host: " + url.getHost() + "due to security risk. Use HTTPS or a local file/classpath reference instead. " + + "Provided host: " + cfgUrl.getHost() ); - // HTTP/HTTPS blocked by default, allowed via system property boolean allowRemote = Boolean.getBoolean(IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL); if (!allowRemote) throw new IgniteCheckedException( "Remote Spring configuration URLs (http/https) are not allowed by default " + "to prevent remote code execution via attacker-controlled Spring XML. " + - "Provided host: " + url.getHost() + ". " + + "Provided host: " + cfgUrl.getHost() + ". " + "To allow remote URLs set system property: -D" + IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL + "=true" ); From 50e3d5e02ee15f8a05191a479b58f49508b26c69 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Tue, 30 Jun 2026 11:20:10 +0700 Subject: [PATCH 10/21] Copilot review suggestions --- .../internal/jdbc2/JdbcConnectionSelfTest.java | 6 ++---- .../apache/ignite/IgniteSystemProperties.java | 2 +- .../internal/util/IgniteUtilsSelfTest.java | 18 ++++++------------ 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index 4a5bfc5055626..9fa03bac2f7dc 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -307,13 +307,11 @@ public void testSqlHints() throws Exception { } } - /** - * Test that JDBC cfg:// URL with remote HTTP, HTTPS, and FTP location is blocked. - */ @Test public void testRemoteCfgUrlsAreBlocked() { for (String scheme : Arrays.asList("http", "https", "ftp", "ftps")) { final String url = CFG_URL_PREFIX + scheme + "://attacker.example.com/evil.xml"; + final String expMsg = scheme.startsWith("ftp") ? "always blocked" : "Remote Spring configuration URLs"; GridTestUtils.assertThrows( log, @@ -325,7 +323,7 @@ public void testRemoteCfgUrlsAreBlocked() { } }, SQLException.class, - null + expMsg ); } } diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java index 2e402a946abb1..8207204508372 100644 --- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java +++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java @@ -1912,7 +1912,7 @@ public final class IgniteSystemProperties extends IgniteCommonsSystemProperties /** * System property to allow remote HTTP/HTTPS URLs when loading Spring XML configuration. * Remote URLs are blocked by default to prevent RCE via attacker-controlled Spring XML. - * FTP is always blocked regardless of this property due to MITM risk. + * FTP/FTPS are always blocked regardless of this property due to security risk. */ @SystemProperty(value = "Allow remote HTTP/HTTPS URLs when loading Spring XML configuration") public static final String IGNITE_ALLOW_REMOTE_SPRING_CFG_URL = "ignite.spring.cfg.allowRemoteUrl"; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java index e0149ef69bd41..7ba60c212db9f 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java @@ -38,6 +38,7 @@ import java.math.BigInteger; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -1668,18 +1669,11 @@ public void testResolveSpringUrlBlocksHttpByDefault() { */ @Test @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") - public void testResolveSpringUrlAllowsHttpWhenPropertySet() { - // Should not throw — validation passes when flag is true. - // Will throw MalformedURLException or connection error, not our security check. - try { - IgniteUtils.resolveSpringUrl("http://127.0.0.1:1/nonexistent.xml"); - } - catch (IgniteCheckedException e) { - assertFalse( - "Should not throw security exception when flag is enabled", - e.getMessage().contains("Remote Spring configuration URLs") - ); - } + public void testResolveSpringUrlAllowsHttpWhenPropertySet() throws IgniteCheckedException { + URL url = IgniteUtils.resolveSpringUrl("http://127.0.0.1:1/nonexistent.xml"); + + assertNotNull(url); + assertEquals("http", url.getProtocol()); } /** From 3ab4cfe4f1302707ee266c88fe8c6e0073694f30 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Wed, 8 Jul 2026 09:44:38 +0700 Subject: [PATCH 11/21] IGNITE-28743 checkstyle --- .../apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index 9fa03bac2f7dc..2cb76ea0d1650 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -307,6 +307,9 @@ public void testSqlHints() throws Exception { } } + /** + * Test that JDBC cfg:// URL with remote HTTP, HTTPS, and FTP location is blocked. + */ @Test public void testRemoteCfgUrlsAreBlocked() { for (String scheme : Arrays.asList("http", "https", "ftp", "ftps")) { From b4e0d72772a5e657cfb66b7f6356ae3de5630a14 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Wed, 8 Jul 2026 10:49:41 +0700 Subject: [PATCH 12/21] IGNITE-28743 checkstyle --- .../apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index 2cb76ea0d1650..43b04a4c87b47 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -349,4 +349,4 @@ public void testRemoteHttpCfgUrlAllowedWhenFlagSet() { ); } } -} \ No newline at end of file +} From 600c6c928bbba7e6dc2dac9fddeafe845dbda0df Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Tue, 14 Jul 2026 13:56:16 +0700 Subject: [PATCH 13/21] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../ignite/internal/util/IgniteUtils.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 621e1d24c3cbc..01cdc72ce1e69 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2575,20 +2575,21 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc String scheme = cfgUrl.getProtocol().toLowerCase(Locale.ROOT); - // Handle jar:!/path to avoid bypassing remote-scheme checks. - if ("jar".equals(scheme)) { + // Unwrap jar:!/path (potentially nested) to avoid bypassing remote-scheme checks. + while ("jar".equals(scheme)) { String file = cfgUrl.getFile(); int sep = file.indexOf("!/"); - if (sep > 0) { - try { - cfgUrl = new URL(file.substring(0, sep)); - scheme = cfgUrl.getProtocol().toLowerCase(Locale.ROOT); - } - catch (MalformedURLException ignored) { - // No-op. - } + if (sep <= 0) + break; + + try { + cfgUrl = new URL(file.substring(0, sep)); + scheme = cfgUrl.getProtocol().toLowerCase(Locale.ROOT); + } + catch (MalformedURLException ignored) { + break; } } From b2b3f26796609a699425d8668c662570e7c5f15b Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Tue, 14 Jul 2026 14:23:04 +0700 Subject: [PATCH 14/21] IGNITE-28869 Potential fix for pull request finding --- .../jdbc2/JdbcConnectionSelfTest.java | 35 ++++++++++++------- .../ignite/internal/util/IgniteUtils.java | 10 ++++-- .../internal/util/IgniteUtilsSelfTest.java | 4 +-- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index 43b04a4c87b47..d4e750ce40d70 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.UUID; import java.util.concurrent.Callable; +import org.apache.ignite.IgniteSystemProperties; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; @@ -38,9 +39,7 @@ * Connection test. */ public class JdbcConnectionSelfTest extends GridCommonAbstractTest { - /** - * Custom cache name. - */ + /** Custom cache name. */ private static final String CUSTOM_CACHE_NAME = "custom-cache"; /** @@ -335,18 +334,28 @@ public void testRemoteCfgUrlsAreBlocked() { * Test that JDBC cfg:// URL with remote HTTP location is allowed when system property is set. */ @Test - @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") + @WithSystemProperty(key = IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL, value = "true") public void testRemoteHttpCfgUrlAllowedWhenFlagSet() { final String url = CFG_URL_PREFIX + "http://127.0.0.1:1/nonexistent.xml"; - try { - DriverManager.getConnection(url); - } - catch (SQLException e) { - assertFalse( - "Security exception should not be thrown when flag is enabled", - e.getMessage().contains("Remote Spring configuration URLs") - ); - } + Throwable err = GridTestUtils.assertThrows( + log, + new Callable() { + @Override public Object call() throws Exception { + try (Connection conn = DriverManager.getConnection(url)) { + return conn; + } + } + }, + SQLException.class, + null + ); + + String msg = err.getMessage(); + + assertFalse( + "Security exception should not be thrown when flag is enabled", + msg != null && msg.contains("Remote Spring configuration URLs") + ); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 01cdc72ce1e69..b25b78726f3c0 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2593,15 +2593,19 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc } } + String prop = IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL; + if (REMOTE_CFG_SCHEMES.contains(scheme)) { if (ALWAYS_BLOCKED_CFG_SCHEMES.contains(scheme)) throw new IgniteCheckedException( "Spring configuration URLs with scheme '" + scheme + "' are always blocked " + - "due to security risk. Use HTTPS or a local file/classpath reference instead. " + + "due to security risk. Use a local file/classpath reference instead. " + + "For remote HTTP/HTTPS set system property: -D" + + prop + "=true. " + "Provided host: " + cfgUrl.getHost() ); - boolean allowRemote = Boolean.getBoolean(IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL); + boolean allowRemote = IgniteSystemProperties.getBoolean(prop); if (!allowRemote) throw new IgniteCheckedException( @@ -2609,7 +2613,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc "to prevent remote code execution via attacker-controlled Spring XML. " + "Provided host: " + cfgUrl.getHost() + ". " + "To allow remote URLs set system property: -D" + - IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL + "=true" + prop + "=true" ); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java index 7ba60c212db9f..bee3d75b04f3c 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java @@ -1668,7 +1668,7 @@ public void testResolveSpringUrlBlocksHttpByDefault() { * Test that remote HTTP URL is allowed when system property is set. */ @Test - @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") + @WithSystemProperty(key = IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL, value = "true") public void testResolveSpringUrlAllowsHttpWhenPropertySet() throws IgniteCheckedException { URL url = IgniteUtils.resolveSpringUrl("http://127.0.0.1:1/nonexistent.xml"); @@ -1680,7 +1680,7 @@ public void testResolveSpringUrlAllowsHttpWhenPropertySet() throws IgniteChecked * Test that FTP is always blocked even when remote URL property is set. */ @Test - @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") + @WithSystemProperty(key = IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL, value = "true") public void testResolveSpringUrlFtpAlwaysBlocked() { assertThrows(log, () -> { IgniteUtils.resolveSpringUrl("ftp://attacker.example.com/evil.xml"); From 8eedf695b2cbbc29c6493f0e331b4be3f9614cf3 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Thu, 16 Jul 2026 14:10:28 +0700 Subject: [PATCH 15/21] IGNITE-28827 Move ftp validation before try --- .../ignite/internal/util/IgniteUtils.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index b25b78726f3c0..3dd68dffe1c2a 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2566,6 +2566,23 @@ public static boolean mkdirs(File dir) { public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedException { A.notNull(springCfgPath, "springCfgPath"); + String prop = IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL; + + // Check always-blocked schemes against the raw string first, since java.net.URL + // does not support ftp/ftps natively and throws MalformedURLException before + // the scheme can be inspected, which would otherwise bypass this check. + String lowerPath = springCfgPath.toLowerCase(Locale.ROOT); + + for (String blockedScheme : ALWAYS_BLOCKED_CFG_SCHEMES) { + if (lowerPath.startsWith(blockedScheme + "://")) + throw new IgniteCheckedException( + "Spring configuration URLs with scheme '" + blockedScheme + "' are always blocked " + + "due to security risk. Use a local file/classpath reference instead. " + + "For remote HTTP/HTTPS set system property: -D" + + prop + "=true." + ); + } + URL url; try { @@ -2593,8 +2610,6 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc } } - String prop = IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL; - if (REMOTE_CFG_SCHEMES.contains(scheme)) { if (ALWAYS_BLOCKED_CFG_SCHEMES.contains(scheme)) throw new IgniteCheckedException( From e9c4729c033c8764392d09621edac83843922b1b Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Fri, 24 Jul 2026 22:41:15 +0700 Subject: [PATCH 16/21] Update modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java Co-authored-by: Evgeniy Stanilovskiy --- .../main/java/org/apache/ignite/internal/util/IgniteUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 3dd68dffe1c2a..d5b1219328a43 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2627,7 +2627,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc "Remote Spring configuration URLs (http/https) are not allowed by default " + "to prevent remote code execution via attacker-controlled Spring XML. " + "Provided host: " + cfgUrl.getHost() + ". " + - "To allow remote URLs set system property: -D" + + "To allow remote URL`s set system property: -D" + prop + "=true" ); } From c42bd916c4a05e62f0e9c55c333e6a03f536a39e Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Fri, 24 Jul 2026 22:41:28 +0700 Subject: [PATCH 17/21] Update modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java Co-authored-by: Evgeniy Stanilovskiy --- .../main/java/org/apache/ignite/internal/util/IgniteUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index d5b1219328a43..ce3c097622597 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2614,7 +2614,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc if (ALWAYS_BLOCKED_CFG_SCHEMES.contains(scheme)) throw new IgniteCheckedException( "Spring configuration URLs with scheme '" + scheme + "' are always blocked " + - "due to security risk. Use a local file/classpath reference instead. " + + "due to security risk. Use a local file or classpath reference instead. " + "For remote HTTP/HTTPS set system property: -D" + prop + "=true. " + "Provided host: " + cfgUrl.getHost() From 1aa536c23ca549d84c6a13fae0b1f295387fde13 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Fri, 24 Jul 2026 22:41:40 +0700 Subject: [PATCH 18/21] Update modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java Co-authored-by: Evgeniy Stanilovskiy --- .../main/java/org/apache/ignite/internal/util/IgniteUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index ce3c097622597..da0b0e9f9187b 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2613,7 +2613,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc if (REMOTE_CFG_SCHEMES.contains(scheme)) { if (ALWAYS_BLOCKED_CFG_SCHEMES.contains(scheme)) throw new IgniteCheckedException( - "Spring configuration URLs with scheme '" + scheme + "' are always blocked " + + "Spring configuration URL`s with scheme '" + scheme + "' are always blocked " + "due to security risk. Use a local file or classpath reference instead. " + "For remote HTTP/HTTPS set system property: -D" + prop + "=true. " + From cb9eb9a7022368e560f203704815337dd48620bd Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Fri, 24 Jul 2026 22:41:54 +0700 Subject: [PATCH 19/21] Update modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java Co-authored-by: Evgeniy Stanilovskiy --- .../main/java/org/apache/ignite/internal/util/IgniteUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index da0b0e9f9187b..295f2ec3a826b 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2624,7 +2624,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc if (!allowRemote) throw new IgniteCheckedException( - "Remote Spring configuration URLs (http/https) are not allowed by default " + + "Remote Spring configuration URL`s (http/https) are not allowed by default " + "to prevent remote code execution via attacker-controlled Spring XML. " + "Provided host: " + cfgUrl.getHost() + ". " + "To allow remote URL`s set system property: -D" + From d1f781db36e2c191471783d742f57ac6c2630860 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Fri, 24 Jul 2026 23:02:22 +0700 Subject: [PATCH 20/21] Review changes --- .../internal/jdbc2/JdbcConnectionSelfTest.java | 12 +++--------- .../org/apache/ignite/IgniteSystemProperties.java | 6 +++--- .../org/apache/ignite/internal/util/IgniteUtils.java | 8 ++++---- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java index d4e750ce40d70..aecfffbb3e19c 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcConnectionSelfTest.java @@ -42,9 +42,7 @@ public class JdbcConnectionSelfTest extends GridCommonAbstractTest { /** Custom cache name. */ private static final String CUSTOM_CACHE_NAME = "custom-cache"; - /** - * Grid count. - */ + /** Grid count. */ private static final int GRID_CNT = 2; /** @@ -54,9 +52,7 @@ protected String configURL() { return "modules/clients/src/test/config/jdbc-config.xml"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); @@ -78,9 +74,7 @@ private CacheConfiguration cacheConfiguration(@NotNull String name) throws Excep return cfg; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override protected void beforeTestsStarted() throws Exception { startGridsMultiThreaded(GRID_CNT); } diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java index 8207204508372..432548c8bc35d 100644 --- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java +++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java @@ -1910,11 +1910,11 @@ public final class IgniteSystemProperties extends IgniteCommonsSystemProperties /** - * System property to allow remote HTTP/HTTPS URLs when loading Spring XML configuration. + * System property to allow remote HTTP|HTTPS URLs when loading Spring XML configuration. * Remote URLs are blocked by default to prevent RCE via attacker-controlled Spring XML. - * FTP/FTPS are always blocked regardless of this property due to security risk. + * FTP|FTPS are always blocked regardless of this property due to security risk. */ - @SystemProperty(value = "Allow remote HTTP/HTTPS URLs when loading Spring XML configuration") + @SystemProperty(value = "Allow remote HTTP|HTTPS URLs when loading Spring XML configuration") public static final String IGNITE_ALLOW_REMOTE_SPRING_CFG_URL = "ignite.spring.cfg.allowRemoteUrl"; /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 295f2ec3a826b..78ad2eb70c309 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2569,7 +2569,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc String prop = IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL; // Check always-blocked schemes against the raw string first, since java.net.URL - // does not support ftp/ftps natively and throws MalformedURLException before + // does not support ftp|ftps natively and throws MalformedURLException before // the scheme can be inspected, which would otherwise bypass this check. String lowerPath = springCfgPath.toLowerCase(Locale.ROOT); @@ -2578,7 +2578,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc throw new IgniteCheckedException( "Spring configuration URLs with scheme '" + blockedScheme + "' are always blocked " + "due to security risk. Use a local file/classpath reference instead. " + - "For remote HTTP/HTTPS set system property: -D" + + "For remote HTTP|HTTPS set system property: -D" + prop + "=true." ); } @@ -2615,7 +2615,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc throw new IgniteCheckedException( "Spring configuration URL`s with scheme '" + scheme + "' are always blocked " + "due to security risk. Use a local file or classpath reference instead. " + - "For remote HTTP/HTTPS set system property: -D" + + "For remote HTTP|HTTPS set system property: -D" + prop + "=true. " + "Provided host: " + cfgUrl.getHost() ); @@ -2624,7 +2624,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc if (!allowRemote) throw new IgniteCheckedException( - "Remote Spring configuration URL`s (http/https) are not allowed by default " + + "Remote Spring configuration URL`s (http|https) are not allowed by default " + "to prevent remote code execution via attacker-controlled Spring XML. " + "Provided host: " + cfgUrl.getHost() + ". " + "To allow remote URL`s set system property: -D" + From fa234141485e6b4d05bd9cb4c00ce1b83c77163c Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Fri, 24 Jul 2026 23:38:40 +0700 Subject: [PATCH 21/21] Move the raw-string check into the catch block --- .../ignite/internal/util/IgniteUtils.java | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 78ad2eb70c309..60eca21048755 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -2568,21 +2568,6 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc String prop = IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL; - // Check always-blocked schemes against the raw string first, since java.net.URL - // does not support ftp|ftps natively and throws MalformedURLException before - // the scheme can be inspected, which would otherwise bypass this check. - String lowerPath = springCfgPath.toLowerCase(Locale.ROOT); - - for (String blockedScheme : ALWAYS_BLOCKED_CFG_SCHEMES) { - if (lowerPath.startsWith(blockedScheme + "://")) - throw new IgniteCheckedException( - "Spring configuration URLs with scheme '" + blockedScheme + "' are always blocked " + - "due to security risk. Use a local file/classpath reference instead. " + - "For remote HTTP|HTTPS set system property: -D" + - prop + "=true." - ); - } - URL url; try { @@ -2633,6 +2618,20 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc } } catch (MalformedURLException e) { + // "ftps" is not a recognized scheme for java.net.URL, so it lands here rather + // than being caught by the scheme check above. Block it explicitly with the + // same security message for a consistent user-facing error. + String lowerPath = springCfgPath.toLowerCase(Locale.ROOT); + + for (String blockedScheme : ALWAYS_BLOCKED_CFG_SCHEMES) { + if (lowerPath.startsWith(blockedScheme + "://")) + throw new IgniteCheckedException( + "Spring configuration URL`s with scheme '" + blockedScheme + "' are always blocked " + + "due to security risk. Use a local file or classpath reference instead. " + + "For remote HTTP|HTTPS set system property: -D" + + prop + "=true.", e + ); + } url = resolveIgniteUrl(springCfgPath); if (url == null)