diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractS3CompatibleProperties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractS3CompatibleProperties.java
index 2a7e58d787fa15..c4cc493abe9232 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractS3CompatibleProperties.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractS3CompatibleProperties.java
@@ -21,6 +21,7 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
+import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.logging.log4j.LogManager;
@@ -149,10 +150,6 @@ public void initNormalizeAndCheckProps() {
}
}
- boolean isEndpointCheckRequired() {
- return true;
- }
-
/**
* Checks and validates the configured endpoint.
*
@@ -229,6 +226,8 @@ public static Optional extractRegion(Set endpointPatterns, Stri
protected abstract Set endpointPatterns();
+ protected abstract Set schemas();
+
// This method should be overridden by subclasses to provide a default endpoint based on the region.
// Because for aws s3, only region is needed, the endpoint can be constructed from the region.
// But for other s3 compatible storage, the endpoint may need to be specified explicitly.
@@ -250,10 +249,16 @@ public String validateAndGetUri(Map loadProps) throws UserExcept
@Override
public void initializeHadoopStorageConfig() {
hadoopStorageConfig = new Configuration();
+ origProps.forEach((key, value) -> {
+ if (key.startsWith("fs.")) {
+ hadoopStorageConfig.set(key, value);
+ }
+ });
// Compatibility note: Due to historical reasons, even when the underlying
// storage is OSS, OBS, etc., users may still configure the schema as "s3://".
// To ensure backward compatibility, we append S3-related properties by default.
appendS3HdfsProperties(hadoopStorageConfig);
+ ensureDisableCache(hadoopStorageConfig, origProps);
}
private void appendS3HdfsProperties(Configuration hadoopStorageConfig) {
@@ -279,6 +284,38 @@ private void appendS3HdfsProperties(Configuration hadoopStorageConfig) {
hadoopStorageConfig.set("fs.s3a.path.style.access", getUsePathStyle());
}
+ /**
+ * By default, Hadoop caches FileSystem instances per scheme and authority (e.g. s3a://bucket/), meaning that all
+ * subsequent calls using the same URI will reuse the same FileSystem object.
+ * In multi-tenant or dynamic credential environments — where different users may access the same bucket using
+ * different access keys or tokens — this cache reuse can lead to cross-credential contamination.
+ *
+ * Specifically, if the cache is not disabled, a FileSystem instance initialized with one set of credentials may
+ * be reused by another session targeting the same bucket but with a different AK/SK. This results in:
+ *
+ * Incorrect authentication (using stale credentials)
+ *
+ * Unexpected permission errors or access denial
+ *
+ * Potential data leakage between users
+ *
+ * To avoid such risks, the configuration property
+ * fs..impl.disable.cache
+ * must be set to true for all object storage backends (e.g., S3A, OSS, COS, OBS), ensuring that each new access
+ * creates an isolated FileSystem instance with its own credentials and configuration context.
+ */
+ private void ensureDisableCache(Configuration conf, Map origProps) {
+ for (String schema : schemas()) {
+ String key = "fs." + schema + ".impl.disable.cache";
+ String userValue = origProps.get(key);
+ if (StringUtils.isNotBlank(userValue)) {
+ conf.setBoolean(key, BooleanUtils.toBoolean(userValue));
+ } else {
+ conf.setBoolean(key, true);
+ }
+ }
+ }
+
@Override
public String getStorageName() {
return "S3";
diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/COSProperties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/COSProperties.java
index af4c332442481d..8f1d4b88f3a1fd 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/COSProperties.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/COSProperties.java
@@ -167,4 +167,9 @@ public void initializeHadoopStorageConfig() {
hadoopStorageConfig.set("fs.cosn.userinfo.secretId", accessKey);
hadoopStorageConfig.set("fs.cosn.userinfo.secretKey", secretKey);
}
+
+ @Override
+ protected Set schemas() {
+ return ImmutableSet.of("cos", "cosn");
+ }
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/GCSProperties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/GCSProperties.java
index dffd18d0d58ec2..20de10f97cd46e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/GCSProperties.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/GCSProperties.java
@@ -183,6 +183,11 @@ public Map getBackendConfigProperties() {
return backendProperties;
}
+ @Override
+ protected Set schemas() {
+ return ImmutableSet.of("gs");
+ }
+
@Override
public AwsCredentialsProvider getAwsCredentialsProvider() {
AwsCredentialsProvider credentialsProvider = super.getAwsCredentialsProvider();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/MinioProperties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/MinioProperties.java
index 1f52a84d392c52..34450383d83dde 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/MinioProperties.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/MinioProperties.java
@@ -136,4 +136,9 @@ protected void setEndpointIfPossible() {
throw new IllegalArgumentException("Property minio.endpoint is required.");
}
}
+
+ @Override
+ protected Set schemas() {
+ return ImmutableSet.of("s3");
+ }
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OBSProperties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OBSProperties.java
index e2adb19895c105..92abf2997b4460 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OBSProperties.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OBSProperties.java
@@ -180,4 +180,9 @@ protected void setEndpointIfPossible() {
throw new IllegalArgumentException("Property obs.endpoint is required.");
}
}
+
+ @Override
+ protected Set schemas() {
+ return ImmutableSet.of("obs");
+ }
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OSSProperties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OSSProperties.java
index 23e38f4676d89f..3cb52b6c6189d6 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OSSProperties.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OSSProperties.java
@@ -281,6 +281,11 @@ public AwsCredentialsProvider getAwsCredentialsProvider() {
return null;
}
+ @Override
+ protected Set schemas() {
+ return ImmutableSet.of("oss");
+ }
+
@Override
public void initializeHadoopStorageConfig() {
super.initializeHadoopStorageConfig();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/S3Properties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/S3Properties.java
index 1b162e87bf014f..47d0301ad9bed4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/S3Properties.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/S3Properties.java
@@ -215,11 +215,6 @@ public void initNormalizeAndCheckProps() {
convertGlueToS3EndpointIfNeeded();
}
- @Override
- boolean isEndpointCheckRequired() {
- return false;
- }
-
/**
* Guess if the storage properties is for this storage type.
* Subclass should override this method to provide the correct implementation.
@@ -268,6 +263,11 @@ protected Set endpointPatterns() {
return ENDPOINT_PATTERN;
}
+ @Override
+ protected Set schemas() {
+ return ImmutableSet.of("s3", "s3a", "s3n");
+ }
+
@Override
public Map getBackendConfigProperties() {
Map backendProperties = generateBackendS3Configuration();
diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/COSPropertiesTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/COSPropertiesTest.java
index aadecf63c3f81d..df20ea5d333547 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/COSPropertiesTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/COSPropertiesTest.java
@@ -20,6 +20,7 @@
import org.apache.doris.common.UserException;
import org.apache.doris.datasource.property.storage.exception.StoragePropertiesException;
+import com.google.common.collect.Maps;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -176,4 +177,19 @@ public void testAwsCredentialsProvider() throws Exception {
obsStorageProperties = (COSProperties) StorageProperties.createPrimary(props);
Assertions.assertEquals(StaticCredentialsProvider.class, obsStorageProperties.getAwsCredentialsProvider().getClass());
}
+
+ @Test
+ public void testS3DisableHadoopCache() throws UserException {
+ Map props = Maps.newHashMap();
+ props.put("cos.endpoint", "cos.ap-beijing.myqcloud.com");
+ COSProperties s3Properties = (COSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertEquals("true", s3Properties.hadoopStorageConfig.get("fs.cos.impl.disable.cache"));
+ Assertions.assertEquals("true", s3Properties.hadoopStorageConfig.get("fs.s3.impl.disable.cache"));
+ Assertions.assertEquals("true", s3Properties.hadoopStorageConfig.get("fs.cosn.impl.disable.cache"));
+ props.put("fs.cos.impl.disable.cache", "true");
+ props.put("fs.cosn.impl.disable.cache", "false");
+ s3Properties = (COSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertEquals("true", s3Properties.hadoopStorageConfig.get("fs.cos.impl.disable.cache"));
+ Assertions.assertEquals("false", s3Properties.hadoopStorageConfig.get("fs.cosn.impl.disable.cache"));
+ }
}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/GCSPropertiesTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/GCSPropertiesTest.java
index c7167a8eba7bf4..179655582b79cb 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/GCSPropertiesTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/GCSPropertiesTest.java
@@ -17,6 +17,7 @@
package org.apache.doris.datasource.property.storage;
+import com.google.common.collect.Maps;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -110,4 +111,21 @@ public void testGCSAwsCredentialsProvider() throws Exception {
gcsStorageProperties = (GCSProperties) StorageProperties.createPrimary(gcsProps);
Assertions.assertEquals(StaticCredentialsProvider.class, gcsStorageProperties.getAwsCredentialsProvider().getClass());
}
+
+ @Test
+ public void testS3DisableHadoopCache() {
+ Map props = Maps.newHashMap();
+ props.put("fs.gcs.support", "true");
+ GCSProperties s3Properties = (GCSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertTrue(s3Properties.hadoopStorageConfig.getBoolean("fs.gs.impl.disable.cache", false));
+ props.put("fs.gs.impl.disable.cache", "true");
+ s3Properties = (GCSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertTrue(s3Properties.hadoopStorageConfig.getBoolean("fs.gs.impl.disable.cache", false));
+ props.put("fs.gs.impl.disable.cache", "false");
+ s3Properties = (GCSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertFalse(s3Properties.hadoopStorageConfig.getBoolean("fs.gs.impl.disable.cache", false));
+ props.put("fs.gs.impl.disable.cache", "null");
+ s3Properties = (GCSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertFalse(s3Properties.hadoopStorageConfig.getBoolean("fs.gs.impl.disable.cache", false));
+ }
}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OBSPropertyTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OBSPropertyTest.java
index 1d9fee06e19e03..4329368254a45d 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OBSPropertyTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OBSPropertyTest.java
@@ -20,6 +20,7 @@
import org.apache.doris.common.ExceptionChecker;
import org.apache.doris.common.UserException;
+import com.google.common.collect.Maps;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
@@ -152,6 +153,23 @@ public void testAwsCredentialsProvider() throws Exception {
Assertions.assertEquals(StaticCredentialsProvider.class, obsStorageProperties.getAwsCredentialsProvider().getClass());
}
+ @Test
+ public void testS3DisableHadoopCache() {
+ Map props = Maps.newHashMap();
+ props.put("obs.endpoint", "obs.cn-north-4.myhuaweicloud.com");
+ OBSProperties s3Properties = (OBSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertTrue(s3Properties.hadoopStorageConfig.getBoolean("fs.obs.impl.disable.cache", false));
+ props.put("fs.obs.impl.disable.cache", "true");
+ s3Properties = (OBSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertTrue(s3Properties.hadoopStorageConfig.getBoolean("fs.obs.impl.disable.cache", false));
+ props.put("fs.obs.impl.disable.cache", "false");
+ s3Properties = (OBSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertFalse(s3Properties.hadoopStorageConfig.getBoolean("fs.obs.impl.disable.cache", false));
+ props.put("fs.obs.impl.disable.cache", "null");
+ s3Properties = (OBSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertFalse(s3Properties.hadoopStorageConfig.getBoolean("fs.obs.impl.disable.cache", false));
+ }
+
@Test
public void testMissingSecretKey() {
origProps.put("obs.endpoint", "obs.cn-north-4.myhuaweicloud.com");
diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OSSPropertiesTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OSSPropertiesTest.java
index 7a0460f3153e6c..874aea6e5108ea 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OSSPropertiesTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OSSPropertiesTest.java
@@ -20,6 +20,7 @@
import org.apache.doris.common.ExceptionChecker;
import org.apache.doris.common.UserException;
+import com.google.common.collect.Maps;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
@@ -250,4 +251,21 @@ public void testAwsCredentialsProvider() throws Exception {
Assertions.assertEquals(StaticCredentialsProvider.class, ossStorageProperties.getAwsCredentialsProvider().getClass());
}
+ @Test
+ public void testS3DisableHadoopCache() throws UserException {
+ Map props = Maps.newHashMap();
+ props.put("oss.endpoint", "oss-cn-hangzhou.aliyuncs.com");
+ OSSProperties s3Properties = (OSSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertTrue(s3Properties.hadoopStorageConfig.getBoolean("fs.oss.impl.disable.cache", false));
+ props.put("fs.oss.impl.disable.cache", "true");
+ s3Properties = (OSSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertTrue(s3Properties.hadoopStorageConfig.getBoolean("fs.oss.impl.disable.cache", false));
+ props.put("fs.oss.impl.disable.cache", "false");
+ s3Properties = (OSSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertFalse(s3Properties.hadoopStorageConfig.getBoolean("fs.oss.impl.disable.cache", false));
+ props.put("fs.oss.impl.disable.cache", "null");
+ s3Properties = (OSSProperties) StorageProperties.createPrimary(props);
+ Assertions.assertFalse(s3Properties.hadoopStorageConfig.getBoolean("fs.oss.impl.disable.cache", false));
+ }
+
}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/S3PropertiesTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/S3PropertiesTest.java
index a0e94042c59d15..f4ea2fcb381a0c 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/S3PropertiesTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/S3PropertiesTest.java
@@ -423,4 +423,21 @@ public void testS3PropertiesAwsAnonymousCredentialsProvider() {
Assertions.assertEquals(AwsCredentialsProviderChain.class, provider.getClass());
Config.aws_credentials_provider_version = "v2";
}
+
+ @Test
+ public void testS3DisableHadoopCache() throws UserException {
+ Map props = Maps.newHashMap();
+ props.put("s3.endpoint", "s3.us-west-2.amazonaws.com");
+ S3Properties s3Properties = (S3Properties) StorageProperties.createPrimary(props);
+ Assertions.assertEquals("true", s3Properties.hadoopStorageConfig.get("fs.s3a.impl.disable.cache"));
+ Assertions.assertEquals("true", s3Properties.hadoopStorageConfig.get("fs.s3.impl.disable.cache"));
+ Assertions.assertEquals("true", s3Properties.hadoopStorageConfig.get("fs.s3n.impl.disable.cache"));
+ props.put("fs.s3a.impl.disable.cache", "true");
+ props.put("fs.s3.impl.disable.cache", "false");
+ props.put("fs.s3n.impl.disable.cache", "null");
+ s3Properties = (S3Properties) StorageProperties.createPrimary(props);
+ Assertions.assertEquals("true", s3Properties.hadoopStorageConfig.get("fs.s3a.impl.disable.cache"));
+ Assertions.assertEquals("false", s3Properties.hadoopStorageConfig.get("fs.s3.impl.disable.cache"));
+ Assertions.assertEquals("false", s3Properties.hadoopStorageConfig.get("fs.s3n.impl.disable.cache"));
+ }
}