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

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kafka-connector.git


The following commit(s) were added to refs/heads/main by this push:
     new 8a310e9380 Fix #1797: sanitize property values in the startup 
properties log (#1814)
8a310e9380 is described below

commit 8a310e9380b2e166764e00ed746d62f75f7ba0d5
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Aug 24 14:56:45 2026 +0200

    Fix #1797: sanitize property values in the startup properties log (#1814)
    
    CamelKafkaConnectMain.Builder.build() logs every initial property at INFO, 
and
    filterSensitive() decided what to mask from the property KEY alone. That 
misses
    the values that actually carry credentials:
    
     - TaskHelper.buildUrl folds every camel.sink.endpoint.* / 
camel.source.endpoint.*
       option into one composed URI, stored under camel.kamelet.ckcSink.toUrl or
       camel.kamelet.ckcSource.fromUrl. Neither key contains a token 
SensitiveUtils
       recognises, so an option that is correctly declared 
ConfigDef.Type.PASSWORD
       and dutifully masked under its own key was reproduced in clear text 
inside
       the composed URI on the same log line.
     - camel.sink.url / camel.source.url may embed credentials as userinfo or as
       query parameters.
    
    Run String values through URISupport.sanitizeUri, which masks userinfo and 
the
    query parameters SensitiveUtils matches while leaving the endpoint itself
    readable, so the log stays useful for diagnosis.
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../utils/CamelKafkaConnectMain.java               |  16 ++-
 .../utils/StartupPropertiesLoggingTest.java        | 156 +++++++++++++++++++++
 2 files changed, 169 insertions(+), 3 deletions(-)

diff --git 
a/core/src/main/java/org/apache/camel/kafkaconnector/utils/CamelKafkaConnectMain.java
 
b/core/src/main/java/org/apache/camel/kafkaconnector/utils/CamelKafkaConnectMain.java
index c4782298bc..f1a4cb2bdc 100644
--- 
a/core/src/main/java/org/apache/camel/kafkaconnector/utils/CamelKafkaConnectMain.java
+++ 
b/core/src/main/java/org/apache/camel/kafkaconnector/utils/CamelKafkaConnectMain.java
@@ -37,6 +37,7 @@ import 
org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.SensitiveUtils;
+import org.apache.camel.util.URISupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -219,11 +220,20 @@ public class CamelKafkaConnectMain extends SimpleMain {
         }
 
         private String filterSensitive(Map.Entry<Object, Object> entry) {
+            final String key = (String) entry.getKey();
 
-            if (SensitiveUtils.containsSensitive((String) entry.getKey())) {
-                return entry.getKey() + "=xxxxxxx";
+            if (SensitiveUtils.containsSensitive(key)) {
+                return key + "=xxxxxxx";
             }
-            return entry.getKey() + "=" + entry.getValue();
+            final Object value = entry.getValue();
+            if (value instanceof String) {
+                // The key alone is not enough: TaskHelper.buildUrl folds 
every endpoint option into a single
+                // composed URI stored under a key that carries no sensitive 
token (ckcSink.toUrl /
+                // ckcSource.fromUrl), and camel.sink.url / camel.source.url 
may embed credentials directly.
+                // Sanitize the value as well so userinfo and sensitive query 
parameters never reach the log.
+                return key + "=" + URISupport.sanitizeUri((String) value);
+            }
+            return key + "=" + value;
         }
 
         public CamelKafkaConnectMain build(CamelContext camelContext) {
diff --git 
a/core/src/test/java/org/apache/camel/kafkaconnector/utils/StartupPropertiesLoggingTest.java
 
b/core/src/test/java/org/apache/camel/kafkaconnector/utils/StartupPropertiesLoggingTest.java
new file mode 100644
index 0000000000..b4de6f7d97
--- /dev/null
+++ 
b/core/src/test/java/org/apache/camel/kafkaconnector/utils/StartupPropertiesLoggingTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.camel.kafkaconnector.utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.kafkaconnector.CamelSinkTask;
+import org.apache.camel.kafkaconnector.CamelSourceTask;
+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.LoggerContext;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.config.Property;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The connector must not write configuration secrets to the worker log at 
default levels, whichever property key
+ * they reach the log under. Endpoint options are folded into a single 
composed URI stored under a key that carries
+ * no sensitive token, so masking by key name alone is not enough.
+ */
+public class StartupPropertiesLoggingTest {
+
+    private static final String SECRET_KEY_VALUE = "sUp3rS3cr3tAccessValue";
+    private static final String USERINFO_PASSWORD = "hunter2PlainPassword";
+
+    private CapturingAppender appender;
+    private LoggerConfig loggerConfig;
+
+    @BeforeEach
+    public void attachAppender() {
+        appender = new CapturingAppender();
+        appender.start();
+
+        LoggerContext context = (LoggerContext) LogManager.getContext(false);
+        Configuration configuration = context.getConfiguration();
+        configuration.addAppender(appender);
+        loggerConfig = 
configuration.getLoggerConfig(CamelKafkaConnectMain.class.getName());
+        loggerConfig.addAppender(appender, Level.INFO, null);
+        context.updateLoggers();
+    }
+
+    @AfterEach
+    public void detachAppender() {
+        loggerConfig.removeAppender(appender.getName());
+        appender.stop();
+        ((LoggerContext) LogManager.getContext(false)).updateLoggers();
+    }
+
+    private String startupLogLine(Map<String, String> props) {
+        CamelKafkaConnectMain.builder("direct://start", "log://test")
+            .withProperties(props)
+            .build(new DefaultCamelContext());
+
+        return appender.messages.stream()
+            .filter(m -> m.startsWith("Setting initial properties"))
+            .findFirst()
+            .orElseThrow(() -> new AssertionError("the startup properties line 
was not logged: " + appender.messages));
+    }
+
+    @Test
+    public void testComposedSinkEndpointUriIsNotLoggedInClearText() {
+        Map<String, String> props = new HashMap<>();
+        props.put(CamelSinkTask.KAMELET_SINK_TEMPLATE_PARAMETERS_PREFIX + 
"toUrl",
+                "aws2-kms://label?accessKey=AKIAEXAMPLEKEY&secretKey=" + 
SECRET_KEY_VALUE + "&region=eu-west-1");
+
+        String logged = startupLogLine(props);
+
+        assertFalse(logged.contains(SECRET_KEY_VALUE),
+                "the composed endpoint URI must not carry the secret into the 
log: " + logged);
+        assertTrue(logged.contains("aws2-kms"), "the endpoint itself should 
stay readable: " + logged);
+    }
+
+    @Test
+    public void testComposedSourceEndpointUriIsNotLoggedInClearText() {
+        Map<String, String> props = new HashMap<>();
+        props.put(CamelSourceTask.KAMELET_SOURCE_TEMPLATE_PARAMETERS_PREFIX + 
"fromUrl",
+                "aws2-kms://label?accessKey=AKIAEXAMPLEKEY&secretKey=" + 
SECRET_KEY_VALUE + "&region=eu-west-1");
+
+        String logged = startupLogLine(props);
+
+        assertFalse(logged.contains(SECRET_KEY_VALUE),
+                "the composed endpoint URI must not carry the secret into the 
log: " + logged);
+    }
+
+    @Test
+    public void 
testCredentialsEmbeddedInTheConfiguredUrlAreNotLoggedInClearText() {
+        Map<String, String> props = new HashMap<>();
+        props.put("camel.sink.url", "netty:tcp://someuser:" + 
USERINFO_PASSWORD + "@somehost:5555");
+
+        String logged = startupLogLine(props);
+
+        assertFalse(logged.contains(USERINFO_PASSWORD),
+                "userinfo credentials must not reach the log: " + logged);
+    }
+
+    @Test
+    public void testKeyBasedMaskingStillApplies() {
+        Map<String, String> props = new HashMap<>();
+        props.put("camel.sink.endpoint.secretKey", SECRET_KEY_VALUE);
+
+        String logged = startupLogLine(props);
+
+        assertFalse(logged.contains(SECRET_KEY_VALUE), "a sensitively named 
key must stay masked: " + logged);
+    }
+
+    @Test
+    public void testNonSensitiveValuesAreStillLogged() {
+        Map<String, String> props = new HashMap<>();
+        props.put("camel.sink.endpoint.region", "eu-west-1");
+
+        String logged = startupLogLine(props);
+
+        assertTrue(logged.contains("eu-west-1"), "ordinary configuration 
should remain visible: " + logged);
+    }
+
+    private static final class CapturingAppender extends AbstractAppender {
+
+        private final List<String> messages = Collections.synchronizedList(new 
ArrayList<>());
+
+        CapturingAppender() {
+            super("ckcCapture", null, null, true, Property.EMPTY_ARRAY);
+        }
+
+        @Override
+        public void append(LogEvent event) {
+            messages.add(event.getMessage().getFormattedMessage());
+        }
+    }
+}

Reply via email to