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

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


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new a7524061925 branch-4.2: [fix](load) Unify sensitive header redaction 
in COPY and Stream Load logs #69621 (#69663)
a7524061925 is described below

commit a75240619257f08346b9801a449f54506b38d601
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Oct 27 18:56:04 2026 +0800

    branch-4.2: [fix](load) Unify sensitive header redaction in COPY and Stream 
Load logs #69621 (#69663)
    
    Cherry-picked from #69621
    
    Co-authored-by: hui lai <[email protected]>
---
 .../apache/doris/httpv2/rest/CopyIntoAction.java   |  3 +-
 .../org/apache/doris/httpv2/rest/LoadAction.java   | 13 +---
 .../apache/doris/httpv2/util/HttpHeaderUtils.java  | 41 +++++++++++++
 .../doris/httpv2/rest/CopyIntoActionTest.java      | 70 ++++++++++++++++++++++
 .../apache/doris/httpv2/rest/LoadActionTest.java   | 14 +++--
 .../doris/httpv2/util/HttpHeaderUtilsTest.java     | 59 ++++++++++++++++++
 6 files changed, 183 insertions(+), 17 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/CopyIntoAction.java 
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/CopyIntoAction.java
index ea2c54e1976..1d43a05844c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/CopyIntoAction.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/CopyIntoAction.java
@@ -33,6 +33,7 @@ import org.apache.doris.httpv2.entity.ResponseEntityBuilder;
 import org.apache.doris.httpv2.exception.UnauthorizedException;
 import org.apache.doris.httpv2.rest.manager.HttpUtils;
 import org.apache.doris.httpv2.util.ExecutionResultSet;
+import org.apache.doris.httpv2.util.HttpHeaderUtils;
 import org.apache.doris.httpv2.util.StatementSubmitter;
 import org.apache.doris.metric.MetricRepo;
 import org.apache.doris.nereids.parser.NereidsParser;
@@ -118,7 +119,7 @@ public class CopyIntoAction extends RestBaseController {
             }
             while (headerNames.hasMoreElements()) {
                 String key = headerNames.nextElement();
-                String value = request.getHeader(key);
+                String value = 
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, key);
                 map.put(key, value);
             }
             return map;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/LoadAction.java 
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/LoadAction.java
index f5d7139f5a1..08d36c159cd 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/LoadAction.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/LoadAction.java
@@ -32,6 +32,7 @@ import org.apache.doris.common.util.DebugPointUtil;
 import org.apache.doris.httpv2.entity.ResponseEntityBuilder;
 import org.apache.doris.httpv2.entity.RestBaseResult;
 import org.apache.doris.httpv2.exception.UnauthorizedException;
+import org.apache.doris.httpv2.util.HttpHeaderUtils;
 import org.apache.doris.httpv2.util.StreamLoadRedirectDrainUtil;
 import org.apache.doris.load.StreamLoadHandler;
 import org.apache.doris.mysql.privilege.PrivPredicate;
@@ -679,7 +680,7 @@ public class LoadAction extends RestBaseController {
         Enumeration<String> headerNames = request.getHeaderNames();
         while (headerNames.hasMoreElements()) {
             String headerName = headerNames.nextElement();
-            String headerValue = isSensitiveHeader(headerName) ? 
"***MASKED***" : request.getHeader(headerName);
+            String headerValue = 
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, headerName);
             
headers.append(headerName).append(":").append(headerValue).append(", ");
         }
         return headers.toString();
@@ -759,16 +760,6 @@ public class LoadAction extends RestBaseController {
         DRAIN
     }
 
-    private boolean isSensitiveHeader(String headerName) {
-        return "Authorization".equalsIgnoreCase(headerName)
-                || "Proxy-Authorization".equalsIgnoreCase(headerName)
-                || "Cookie".equalsIgnoreCase(headerName)
-                || "Set-Cookie".equalsIgnoreCase(headerName)
-                || "token".equalsIgnoreCase(headerName)
-                || "Auth-Token".equalsIgnoreCase(headerName)
-                || "auth_code".equalsIgnoreCase(headerName);
-    }
-
     private Backend selectBackendForGroupCommit(String clusterName, 
HttpServletRequest req, long tableId)
             throws LoadException {
         ConnectContext currentCtx = ConnectContext.get();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/util/HttpHeaderUtils.java 
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/util/HttpHeaderUtils.java
new file mode 100644
index 00000000000..b616cbb1473
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/util/HttpHeaderUtils.java
@@ -0,0 +1,41 @@
+// 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.httpv2.util;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/** Shared rendering of HTTP request headers for logs. */
+public final class HttpHeaderUtils {
+    private HttpHeaderUtils() {
+    }
+
+    /** Mask credentials without reading or changing the header used for 
authentication. */
+    public static String getRedactedHeaderValueForLogging(HttpServletRequest 
request, String headerName) {
+        return isSensitiveHeader(headerName) ? "***MASKED***" : 
request.getHeader(headerName);
+    }
+
+    private static boolean isSensitiveHeader(String headerName) {
+        return "Authorization".equalsIgnoreCase(headerName)
+                || "Proxy-Authorization".equalsIgnoreCase(headerName)
+                || "Cookie".equalsIgnoreCase(headerName)
+                || "Set-Cookie".equalsIgnoreCase(headerName)
+                || "token".equalsIgnoreCase(headerName)
+                || "Auth-Token".equalsIgnoreCase(headerName)
+                || "auth_code".equalsIgnoreCase(headerName);
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/CopyIntoActionTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/CopyIntoActionTest.java
new file mode 100644
index 00000000000..c7887790d69
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/CopyIntoActionTest.java
@@ -0,0 +1,70 @@
+// 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.httpv2.rest;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+public class CopyIntoActionTest {
+    @Test
+    public void testHeadersForUploadAndQueryLogsMaskCredentials() throws 
Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        String[] sensitiveHeaders = {"aUtHoRiZaTiOn", "PROXY-AUTHORIZATION", 
"cookie", "Set-Cookie",
+                "TOKEN", "aUtH-tOkEn", "AUTH_CODE"};
+        for (String value : new String[] {"Basic dXNlcjpzZWNyZXQ=", "Bearer 
synthetic-token", "Basic malformed"}) {
+            
Mockito.when(request.getHeaderNames()).thenReturn(Collections.enumeration(Arrays.asList(
+                    "aUtHoRiZaTiOn", "PROXY-AUTHORIZATION", "cookie", 
"Set-Cookie", "TOKEN", "aUtH-tOkEn",
+                    "AUTH_CODE", "fileName", "label")));
+            
Mockito.when(request.getHeader(Mockito.anyString())).thenReturn(value);
+            Mockito.when(request.getHeader("fileName")).thenReturn("data.csv");
+            Mockito.when(request.getHeader("label")).thenReturn("copy_label");
+
+            Map<String, String> headers = getHeadersInfo(request);
+
+            for (String name : sensitiveHeaders) {
+                Assertions.assertEquals("***MASKED***", headers.get(name));
+            }
+            Assertions.assertEquals("data.csv", headers.get("fileName"));
+            Assertions.assertEquals("copy_label", headers.get("label"));
+            Assertions.assertFalse(headers.toString().contains(value));
+            Assertions.assertEquals(value, request.getHeader("aUtHoRiZaTiOn"));
+        }
+    }
+
+    @Test
+    public void testHeadersUnavailable() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        Assertions.assertTrue(getHeadersInfo(request).isEmpty());
+        Mockito.when(request.getHeaderNames()).thenThrow(new 
IllegalStateException("headers unavailable"));
+        Assertions.assertTrue(getHeadersInfo(request).isEmpty());
+    }
+
+    @SuppressWarnings("unchecked")
+    private Map<String, String> getHeadersInfo(HttpServletRequest request) 
throws Exception {
+        Method method = 
CopyIntoAction.class.getDeclaredMethod("getHeadersInfo", 
HttpServletRequest.class);
+        method.setAccessible(true);
+        return (Map<String, String>) method.invoke(null, request);
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/LoadActionTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/LoadActionTest.java
index 93bd89fafd1..29f67e053fa 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/LoadActionTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/LoadActionTest.java
@@ -255,18 +255,22 @@ public class LoadActionTest {
         LoadAction loadAction = new LoadAction();
         HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
         
Mockito.when(request.getHeaderNames()).thenReturn(Collections.enumeration(Arrays.asList(
-                "Authorization", "Cookie", "Set-Cookie", "token", "label")));
+                "aUtHoRiZaTiOn", "PROXY-AUTHORIZATION", "Cookie", 
"Set-Cookie", "token", "Auth-Token", "auth_code",
+                "label")));
+        
Mockito.when(request.getHeader(Mockito.anyString())).thenReturn("synthetic-secret");
         Mockito.when(request.getHeader("label")).thenReturn("load_label");
 
         Method method = LoadAction.class.getDeclaredMethod("getAllHeaders", 
HttpServletRequest.class);
         method.setAccessible(true);
         String headers = (String) method.invoke(loadAction, request);
 
-        Assertions.assertTrue(headers.contains("Authorization:***MASKED***"));
-        Assertions.assertTrue(headers.contains("Cookie:***MASKED***"));
-        Assertions.assertTrue(headers.contains("Set-Cookie:***MASKED***"));
-        Assertions.assertTrue(headers.contains("token:***MASKED***"));
+        for (String name : new String[] {"aUtHoRiZaTiOn", 
"PROXY-AUTHORIZATION", "Cookie", "Set-Cookie",
+                "token", "Auth-Token", "auth_code"}) {
+            Assertions.assertTrue(headers.contains(name + ":***MASKED***"));
+        }
         Assertions.assertTrue(headers.contains("label:load_label"));
+        Assertions.assertFalse(headers.contains("synthetic-secret"));
+        Assertions.assertEquals("synthetic-secret", 
request.getHeader("aUtHoRiZaTiOn"));
     }
 
     @Test
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/httpv2/util/HttpHeaderUtilsTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/util/HttpHeaderUtilsTest.java
new file mode 100644
index 00000000000..680c5b8407a
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/util/HttpHeaderUtilsTest.java
@@ -0,0 +1,59 @@
+// 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.httpv2.util;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.Locale;
+
+public class HttpHeaderUtilsTest {
+    @Test
+    public void testSensitiveHeadersAreMaskedRegardlessOfCase() {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        for (String name : new String[] {"Authorization", 
"Proxy-Authorization", "Cookie", "Set-Cookie",
+                "token", "Auth-Token", "auth_code", "aUtHoRiZaTiOn", 
"pRoXy-AuThOrIzAtIoN"}) {
+            for (String spelling : new String[] {name, 
name.toLowerCase(Locale.ROOT), name.toUpperCase(Locale.ROOT)}) {
+                Assertions.assertEquals("***MASKED***", 
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, spelling));
+            }
+        }
+        Mockito.verifyNoInteractions(request);
+    }
+
+    @Test
+    public void testMaskingDoesNotChangeAuthenticationHeader() {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        for (String value : new String[] {"Basic dXNlcjpzZWNyZXQ=", "Bearer 
synthetic-token", "Basic malformed"}) {
+            Mockito.when(request.getHeader("Authorization")).thenReturn(value);
+            Assertions.assertEquals("***MASKED***", 
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, "Authorization"));
+            Assertions.assertEquals(value, request.getHeader("Authorization"));
+        }
+    }
+
+    @Test
+    public void testNonSensitiveHeadersKeepTheirValues() {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        Mockito.when(request.getHeader("label")).thenReturn("load_label");
+        Mockito.when(request.getHeader("fileName")).thenReturn("data.csv");
+        Assertions.assertEquals("load_label", 
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, "label"));
+        Assertions.assertEquals("data.csv", 
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, "fileName"));
+        
Assertions.assertNull(HttpHeaderUtils.getRedactedHeaderValueForLogging(request, 
"missing"));
+    }
+}


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

Reply via email to