This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new f5905b5c20b [fix](lakesoul) Avoid logging credentials on branch-4.1 
(#69473)
f5905b5c20b is described below

commit f5905b5c20b15a252b58c736ce46e25dc9e60cd5
Author: Oliveira <[email protected]>
AuthorDate: Fri Oct 23 17:46:05 2026 +0800

    [fix](lakesoul) Avoid logging credentials on branch-4.1 (#69473)
    
    ### What problem does this PR solve?
    
    Issue Number: N/A (no public GitHub issue)
    
    Problem Summary:
    
    The retained LakeSoul scanner logs complete scanner parameters at INFO,
    including object storage credentials carried in `options`. The FE scan
    node also logs complete catalog properties at DEBUG.
    
    Log only the query ID and batch size when opening the scanner, and
    remove the catalog-property dump. Authentication options continue to
    reach the reader unchanged.
    
    Add focused regression tests and an explicit `lakesoul-scanner-tests`
    Maven profile so the deprecated scanner can be tested while remaining
    excluded from default builds.
    
    ### Release note
    
    Prevent sensitive LakeSoul scanner parameters and catalog properties
    from being written to logs.
    
    ### Check List (For Author)
    
    - Test
        - [ ] Regression test
        - [x] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    
      Validation on JDK 17:
    - `LakeSoulJniScannerTest`: 3 tests passed, covering INFO, DEBUG, and a
    missing query ID; credentials still reach the native reader.
    - `LakeSoulScanNodeTest`: 1 test passed, verifying credentials stay in
    scan options and out of DEBUG logs.
    - Restoring the original production logging makes all 4 tests fail on
    credential-leak assertions; restoring the fix makes all 4 pass.
      - Targeted Checkstyle: 0 violations. `git diff --check` passed.
    
    Native I/O is isolated with Mockito. No live LakeSoul SQL scan was run.
    
    - Behavior changed:
        - [ ] No.
    - [x] Yes. Scanner startup logs contain the query ID and batch size
    instead of the full parameters; FE no longer logs complete catalog
    properties.
    
    - Does this need documentation?
        - [x] No.
        - [ ] Yes.
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label
---
 fe/be-java-extensions/lakesoul-scanner/pom.xml     |   5 +
 .../apache/doris/lakesoul/LakeSoulJniScanner.java  |   2 +-
 .../doris/lakesoul/LakeSoulJniScannerTest.java     | 122 +++++++++++++++++++++
 .../src/test/resources/log4j2-test.xml             |  25 +++++
 fe/be-java-extensions/pom.xml                      |  10 ++
 .../lakesoul/source/LakeSoulScanNode.java          |   3 -
 .../lakesoul/source/LakeSoulScanNodeTest.java      | 110 +++++++++++++++++++
 7 files changed, 273 insertions(+), 4 deletions(-)

diff --git a/fe/be-java-extensions/lakesoul-scanner/pom.xml 
b/fe/be-java-extensions/lakesoul-scanner/pom.xml
index 32f853bc76c..3c195eaae56 100644
--- a/fe/be-java-extensions/lakesoul-scanner/pom.xml
+++ b/fe/be-java-extensions/lakesoul-scanner/pom.xml
@@ -61,6 +61,11 @@ under the License.
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-inline</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/fe/be-java-extensions/lakesoul-scanner/src/main/java/org/apache/doris/lakesoul/LakeSoulJniScanner.java
 
b/fe/be-java-extensions/lakesoul-scanner/src/main/java/org/apache/doris/lakesoul/LakeSoulJniScanner.java
index 0ff491b246a..914b508d2e9 100644
--- 
a/fe/be-java-extensions/lakesoul-scanner/src/main/java/org/apache/doris/lakesoul/LakeSoulJniScanner.java
+++ 
b/fe/be-java-extensions/lakesoul-scanner/src/main/java/org/apache/doris/lakesoul/LakeSoulJniScanner.java
@@ -69,7 +69,7 @@ public class LakeSoulJniScanner extends 
LakeSoulArrowJniScanner {
         withAllocator(nativeIOReader.getAllocator());
         nativeIOReader.setBatchSize(batchSize);
 
-        LOG.info("opening LakeSoulJniScanner with params={}", params);
+        LOG.info("opening LakeSoulJniScanner, queryId={}, batchSize={}", 
params.get("query_id"), batchSize);
 
         // add files
         for (String file : 
params.get(LakeSoulUtils.FILE_NAMES).split(LakeSoulUtils.LIST_DELIM)) {
diff --git 
a/fe/be-java-extensions/lakesoul-scanner/src/test/java/org/apache/doris/lakesoul/LakeSoulJniScannerTest.java
 
b/fe/be-java-extensions/lakesoul-scanner/src/test/java/org/apache/doris/lakesoul/LakeSoulJniScannerTest.java
new file mode 100644
index 00000000000..c75d8860e34
--- /dev/null
+++ 
b/fe/be-java-extensions/lakesoul-scanner/src/test/java/org/apache/doris/lakesoul/LakeSoulJniScannerTest.java
@@ -0,0 +1,122 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.lakesoul;
+
+import com.dmetasoul.lakesoul.lakesoul.io.NativeIOReader;
+import com.lakesoul.shaded.com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.MockedConstruction;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LakeSoulJniScannerTest {
+    @Test
+    public void testOpenDoesNotLogCredentialsAtInfo() throws Exception {
+        assertSafeOpen(Level.INFO, "test-query-id");
+    }
+
+    @Test
+    public void testOpenDoesNotLogCredentialsAtDebug() throws Exception {
+        assertSafeOpen(Level.DEBUG, "test-query-id");
+    }
+
+    @Test
+    public void testOpenWithoutQueryIdDoesNotLogCredentials() throws Exception 
{
+        assertSafeOpen(Level.DEBUG, null);
+    }
+
+    private void assertSafeOpen(Level level, String queryId) throws Exception {
+        Map<String, String> options = new HashMap<>();
+        options.put(LakeSoulUtils.FS_S3A_ACCESS_KEY, "FAKE_ACCESS_KEY");
+        options.put(LakeSoulUtils.FS_S3A_SECRET_KEY, "FAKE_SECRET_KEY");
+        options.put(LakeSoulUtils.FS_S3A_ENDPOINT, 
"https://object-store.invalid";);
+        options.put(LakeSoulUtils.FS_S3A_PATH_STYLE_ACCESS, "true");
+        Map<String, String> params = new HashMap<>();
+        if (queryId != null) {
+            params.put("query_id", queryId);
+        }
+        params.put(LakeSoulUtils.OPTIONS, new 
ObjectMapper().writeValueAsString(options));
+        params.put(LakeSoulUtils.FILE_NAMES, 
"s3://test/file?signature=FAKE_SIGNATURE");
+        params.put(LakeSoulUtils.SCHEMA_JSON, "{\"fields\":[]}");
+        params.put(LakeSoulUtils.REQUIRED_FIELDS, "");
+        params.put("future_auth_option", "FAKE_FUTURE_TOKEN");
+        Map<String, String> originalParams = new HashMap<>(params);
+        IOException stopBeforeNativeIo = new IOException("stop before native 
initialization");
+
+        Logger logger = (Logger) 
LogManager.getLogger(LakeSoulJniScanner.class);
+        Level originalLevel = logger.getLevel();
+        boolean originalAdditive = logger.isAdditive();
+        CapturingAppender appender = new CapturingAppender();
+        appender.start();
+        logger.addAppender(appender);
+        logger.setLevel(level);
+        logger.setAdditive(false);
+        try (MockedConstruction<NativeIOReader> readers = 
Mockito.mockConstruction(
+                NativeIOReader.class, (reader, context) ->
+                        
Mockito.doThrow(stopBeforeNativeIo).when(reader).initializeReader())) {
+            LakeSoulJniScanner scanner = new LakeSoulJniScanner(128, params);
+            Assert.assertSame(stopBeforeNativeIo, 
Assert.assertThrows(IOException.class, scanner::open));
+            Assert.assertEquals(1, readers.constructed().size());
+            NativeIOReader reader = readers.constructed().get(0);
+            Mockito.verify(reader).setBatchSize(128);
+            for (Map.Entry<String, String> option : options.entrySet()) {
+                Mockito.verify(reader).setObjectStoreOption(option.getKey(), 
option.getValue());
+            }
+            Assert.assertEquals(originalParams, params);
+
+            String logs = String.join("\n", appender.messages);
+            Assert.assertTrue(logs.contains("opening LakeSoulJniScanner"));
+            Assert.assertFalse("Credentials must not appear in scanner logs", 
logs.contains("FAKE_"));
+            Assert.assertTrue(logs.contains("queryId=" + queryId));
+            Assert.assertTrue(logs.contains("batchSize=128"));
+            Assert.assertFalse(logs.contains("fs.s3a."));
+            Assert.assertFalse(logs.contains("options="));
+            Assert.assertFalse(logs.contains("future_auth_option"));
+        } finally {
+            logger.removeAppender(appender);
+            appender.stop();
+            logger.setLevel(originalLevel);
+            logger.setAdditive(originalAdditive);
+        }
+    }
+
+    private static class CapturingAppender extends AbstractAppender {
+        private final List<String> messages = new ArrayList<>();
+
+        CapturingAppender() {
+            super("lakesoul-scanner-test-capture", null, null, false, 
Property.EMPTY_ARRAY);
+        }
+
+        @Override
+        public void append(LogEvent event) {
+            messages.add(event.getMessage().getFormattedMessage());
+        }
+    }
+}
diff --git 
a/fe/be-java-extensions/lakesoul-scanner/src/test/resources/log4j2-test.xml 
b/fe/be-java-extensions/lakesoul-scanner/src/test/resources/log4j2-test.xml
new file mode 100644
index 00000000000..cd0ccaeb80a
--- /dev/null
+++ b/fe/be-java-extensions/lakesoul-scanner/src/test/resources/log4j2-test.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<!-- Tests attach their own capturing appender and do not need the JNI file 
appender. -->
+<Configuration status="WARN">
+    <Loggers>
+        <Root level="info"/>
+    </Loggers>
+</Configuration>
diff --git a/fe/be-java-extensions/pom.xml b/fe/be-java-extensions/pom.xml
index c0b2aaebb16..5611b74e5c1 100644
--- a/fe/be-java-extensions/pom.xml
+++ b/fe/be-java-extensions/pom.xml
@@ -51,4 +51,14 @@ under the License.
         <fe_ut_parallel>1</fe_ut_parallel>
     </properties>
 
+    <!-- Explicitly opt in to test the retained scanner; regular builds keep 
LakeSoul disabled. -->
+    <profiles>
+        <profile>
+            <id>lakesoul-scanner-tests</id>
+            <modules>
+                <module>lakesoul-scanner</module>
+            </modules>
+        </profile>
+    </profiles>
+
 </project>
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/lakesoul/source/LakeSoulScanNode.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/lakesoul/source/LakeSoulScanNode.java
index a1e287432de..cfa917327f0 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/lakesoul/source/LakeSoulScanNode.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/lakesoul/source/LakeSoulScanNode.java
@@ -181,9 +181,6 @@ public class LakeSoulScanNode extends FileQueryScanNode {
             options.put(LakeSoulUtils.SUBSTRAIT_PREDICATE, 
SubstraitUtil.encodeBase64String(predicate));
         }
         Map<String, String> catalogProps = getCatalog().getProperties();
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("{}", catalogProps);
-        }
 
         if (catalogProps.get("AWS_ENDPOINT") != null) {
             options.put(LakeSoulUtils.FS_S3A_ENDPOINT, 
catalogProps.get("AWS_ENDPOINT"));
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/lakesoul/source/LakeSoulScanNodeTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/lakesoul/source/LakeSoulScanNodeTest.java
new file mode 100644
index 00000000000..07d99d96616
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/lakesoul/source/LakeSoulScanNodeTest.java
@@ -0,0 +1,110 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.datasource.lakesoul.source;
+
+import org.apache.doris.datasource.ExternalCatalog;
+import org.apache.doris.datasource.TableFormatType;
+import org.apache.doris.datasource.lakesoul.LakeSoulUtils;
+import org.apache.doris.thrift.TFileRangeDesc;
+
+import com.alibaba.fastjson.JSON;
+import com.dmetasoul.lakesoul.meta.LakeSoulOptions;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LakeSoulScanNodeTest {
+    @Test
+    public void testScanOptionsPreserveCredentialsWithoutLoggingThem() throws 
Exception {
+        Map<String, String> catalogProps = new HashMap<>();
+        catalogProps.put("AWS_ENDPOINT", "https://object-store.invalid";);
+        catalogProps.put("AWS_ACCESS_KEY", "FAKE_ACCESS_KEY");
+        catalogProps.put("AWS_SECRET_KEY", "FAKE_SECRET_KEY");
+        catalogProps.put("AWS_REGION", "test-region");
+        catalogProps.put("future_auth_option", "FAKE_FUTURE_TOKEN");
+        Map<String, String> originalProps = new HashMap<>(catalogProps);
+        ExternalCatalog catalog = Mockito.mock(ExternalCatalog.class);
+        Mockito.when(catalog.getProperties()).thenReturn(catalogProps);
+        LakeSoulScanNode scanNode = Mockito.mock(LakeSoulScanNode.class, 
Mockito.CALLS_REAL_METHODS);
+        Mockito.doReturn(catalog).when(scanNode).getCatalog();
+        scanNode.readType = LakeSoulOptions.ReadType$.MODULE$.FULL_READ();
+        LakeSoulSplit split = new 
LakeSoulSplit(Collections.singletonList("s3://test/file"),
+                Collections.emptyList(), Collections.emptyMap(), 
"{\"fields\":[]}",
+                0, 0, 0, new String[0], Collections.emptyList());
+        split.setTableFormatType(TableFormatType.LAKESOUL);
+        TFileRangeDesc range = new TFileRangeDesc();
+
+        Logger logger = (Logger) LogManager.getLogger(LakeSoulScanNode.class);
+        Level originalLevel = logger.getLevel();
+        boolean originalAdditive = logger.isAdditive();
+        CapturingAppender appender = new CapturingAppender();
+        appender.start();
+        logger.addAppender(appender);
+        logger.setLevel(Level.DEBUG);
+        logger.setAdditive(false);
+        // Predicate translation is independent of the catalog options being 
tested.
+        try (MockedStatic<LakeSoulUtils> ignored = 
Mockito.mockStatic(LakeSoulUtils.class)) {
+            scanNode.setScanParams(range, split);
+            Map<String, Object> options = JSON.parseObject(
+                    
range.getTableFormatParams().getLakesoulParams().getOptions());
+            Assert.assertEquals("FAKE_ACCESS_KEY", 
options.get(LakeSoulUtils.FS_S3A_ACCESS_KEY));
+            Assert.assertEquals("FAKE_SECRET_KEY", 
options.get(LakeSoulUtils.FS_S3A_SECRET_KEY));
+            Assert.assertEquals("https://object-store.invalid";, 
options.get(LakeSoulUtils.FS_S3A_ENDPOINT));
+            Assert.assertEquals("test-region", 
options.get(LakeSoulUtils.FS_S3A_REGION));
+            Assert.assertEquals(originalProps, catalogProps);
+
+            Assert.assertFalse(appender.messages.isEmpty());
+            String logs = String.join("\n", appender.messages);
+            Assert.assertFalse("Catalog credentials must not appear in logs", 
logs.contains("FAKE_"));
+            Assert.assertFalse(logs.contains("AWS_ACCESS_KEY"));
+            Assert.assertFalse(logs.contains("AWS_SECRET_KEY"));
+            Assert.assertFalse(logs.contains("future_auth_option"));
+        } finally {
+            logger.removeAppender(appender);
+            appender.stop();
+            logger.setLevel(originalLevel);
+            logger.setAdditive(originalAdditive);
+        }
+    }
+
+    private static class CapturingAppender extends AbstractAppender {
+        private final List<String> messages = new ArrayList<>();
+
+        CapturingAppender() {
+            super("lakesoul-scan-node-test-capture", null, null, false, 
Property.EMPTY_ARRAY);
+        }
+
+        @Override
+        public void append(LogEvent event) {
+            messages.add(event.getMessage().getFormattedMessage());
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to