This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 644ff8dadb28 CAMEL-26276: camel-clickup - do not log the webhook
signatures, and mask the credentials in toString() (#29809)
644ff8dadb28 is described below
commit 644ff8dadb2825dfab9aeec5e76c53a5364e2b69
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Nov 13 16:39:38 2026 +0100
CAMEL-26276: camel-clickup - do not log the webhook signatures, and mask
the credentials in toString() (#29809)
The webhook consumer logs the body it validates and whether the check
passed at DEBUG, but no longer the signature
from the X-Signature header nor the one it calculates. The
InvalidMessageSignatureException of a failed check no
longer carries the calculated signature: getCalculatedSignature() and the
constructor that takes it are deprecated.
The toString() of the ClickUp configuration and of the webhook masks the
credentials.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../component/clickup/ClickUpConfiguration.java | 11 +-
.../clickup/InvalidMessageSignatureException.java | 15 +++
.../camel/component/clickup/model/Webhook.java | 9 +-
.../clickup/service/ClickUpWebhookService.java | 12 +-
.../component/clickup/ClickUpToStringTest.java | 60 ++++++++++
.../clickup/ClickUpWebhookSignatureLogTest.java | 127 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc | 5 +
.../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 5 +
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 5 +
9 files changed, 241 insertions(+), 8 deletions(-)
diff --git
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/ClickUpConfiguration.java
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/ClickUpConfiguration.java
index 01707df67851..fb1892d5e193 100644
---
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/ClickUpConfiguration.java
+++
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/ClickUpConfiguration.java
@@ -93,10 +93,17 @@ public class ClickUpConfiguration {
public String toString() {
return "ClickUpConfiguration{" +
"workspaceId=" + workspaceId +
- ", webhookSecret='" + webhookSecret + '\'' +
- ", authorizationToken='" + authorizationToken + '\'' +
+ ", webhookSecret='" + mask(webhookSecret) + '\'' +
+ ", authorizationToken='" + mask(authorizationToken) + '\'' +
", baseUrl='" + baseUrl + '\'' +
'}';
}
+ /**
+ * Masks a credential so that logging this configuration cannot leak it.
{@code xxxxxx} is the mask Camel uses when
+ * it sanitizes a URI, and whether the value was set at all is still
visible.
+ */
+ private static String mask(Object secret) {
+ return secret != null ? "xxxxxx" : "null";
+ }
}
diff --git
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/InvalidMessageSignatureException.java
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/InvalidMessageSignatureException.java
index 53e2ac4dcbd1..6130b063eaee 100644
---
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/InvalidMessageSignatureException.java
+++
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/InvalidMessageSignatureException.java
@@ -22,6 +22,17 @@ public class InvalidMessageSignatureException extends
RuntimeException {
private final String messageSignature;
private final String calculatedSignature;
+ public InvalidMessageSignatureException(String message, String
messageSignature) {
+ this.message = message;
+ this.messageSignature = messageSignature;
+ this.calculatedSignature = null;
+ }
+
+ /**
+ * @deprecated the calculated signature is the valid signature of the
message, so it should not travel with the
+ * exception; use {@link
#InvalidMessageSignatureException(String, String)}
+ */
+ @Deprecated(since = "4.24.0")
public InvalidMessageSignatureException(String message, String
messageSignature, String calculatedSignature) {
this.message = message;
this.messageSignature = messageSignature;
@@ -37,6 +48,10 @@ public class InvalidMessageSignatureException extends
RuntimeException {
return messageSignature;
}
+ /**
+ * @deprecated the webhook consumer no longer sets the calculated
signature, so this returns null for its exceptions
+ */
+ @Deprecated(since = "4.24.0")
public String getCalculatedSignature() {
return calculatedSignature;
}
diff --git
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/Webhook.java
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/Webhook.java
index 38303d085762..e22d15c06ae9 100644
---
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/Webhook.java
+++
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/Webhook.java
@@ -184,8 +184,15 @@ public class Webhook implements Serializable {
", folderId='" + folderId + '\'' +
", spaceId='" + spaceId + '\'' +
", health=" + health +
- ", secret='" + secret + '\'' +
+ ", secret='" + mask(secret) + '\'' +
'}';
}
+ /**
+ * Masks a credential so that logging a webhook cannot leak it. {@code
xxxxxx} is the mask Camel uses when it
+ * sanitizes a URI, and whether the value was set at all is still visible.
+ */
+ private static String mask(Object secret) {
+ return secret != null ? "xxxxxx" : "null";
+ }
}
diff --git
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/service/ClickUpWebhookService.java
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/service/ClickUpWebhookService.java
index 769abcccac76..4d7a12c174cb 100644
---
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/service/ClickUpWebhookService.java
+++
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/service/ClickUpWebhookService.java
@@ -88,8 +88,9 @@ public class ClickUpWebhookService {
// malformed sequence into U+FFFD and change what is hashed
messageBytes = body.readAllBytes();
- LOG.debug("Message body {} to be validated against the given
signature {}",
- new String(messageBytes, StandardCharsets.UTF_8),
signature);
+ // the signature authenticates the event, so it stays out of the
log
+ LOG.debug("Message body {} to be validated against the given
signature",
+ new String(messageBytes, StandardCharsets.UTF_8));
body.reset();
} catch (IOException e) {
@@ -107,12 +108,13 @@ public class ClickUpWebhookService {
calculatedSignature.getBytes(StandardCharsets.US_ASCII),
signature.getBytes(StandardCharsets.US_ASCII));
if (!signatureMatches) {
- LOG.debug("The message signature {} does not match {}.",
signature, calculatedSignature);
+ LOG.debug("The message signature does not match.");
- throw new InvalidMessageSignatureException(messageBody, signature,
calculatedSignature);
+ // the calculated signature is the valid one for this body, so it
stays out of the exception too
+ throw new InvalidMessageSignatureException(messageBody, signature);
}
- LOG.debug("The message signature {} matches {}.", signature,
calculatedSignature);
+ LOG.debug("The message signature matches.");
}
}
diff --git
a/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpToStringTest.java
b/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpToStringTest.java
new file mode 100644
index 000000000000..099528e69437
--- /dev/null
+++
b/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpToStringTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.component.clickup;
+
+import org.apache.camel.component.clickup.model.Webhook;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The configuration and the webhook model are printed when logged, so their
toString() masks the credentials.
+ */
+public class ClickUpToStringTest {
+
+ @Test
+ public void configurationMasksTheCredentials() {
+ ClickUpConfiguration configuration = new ClickUpConfiguration();
+ configuration.setWorkspaceId(42L);
+ configuration.setWebhookSecret("the-webhook-secret");
+ configuration.setAuthorizationToken("the-authorization-token");
+
+ assertThat(configuration.toString())
+ .contains("workspaceId=42")
+ .contains("webhookSecret='xxxxxx'")
+ .contains("authorizationToken='xxxxxx'")
+ .doesNotContain("the-webhook-secret")
+ .doesNotContain("the-authorization-token");
+ }
+
+ @Test
+ public void configurationShowsACredentialThatIsNotSet() {
+ assertThat(new ClickUpConfiguration().toString())
+ .contains("webhookSecret='null'")
+ .contains("authorizationToken='null'");
+ }
+
+ @Test
+ public void webhookMasksTheSecret() {
+ Webhook webhook = new Webhook();
+ webhook.setSecret("the-webhook-secret");
+
+ assertThat(webhook.toString())
+ .contains("secret='xxxxxx'")
+ .doesNotContain("the-webhook-secret");
+ }
+}
diff --git
a/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpWebhookSignatureLogTest.java
b/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpWebhookSignatureLogTest.java
new file mode 100644
index 000000000000..242fb04087bd
--- /dev/null
+++
b/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpWebhookSignatureLogTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.component.clickup;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Message;
+import org.apache.camel.component.clickup.service.ClickUpWebhookService;
+import org.apache.camel.component.clickup.util.Utils;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultMessage;
+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.Configurator;
+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.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * The webhook signature check logs at DEBUG what it validates, but never a
signature: the valid signature of an event
+ * stays out of the log and out of the exception of a failed check.
+ */
+public class ClickUpWebhookSignatureLogTest {
+
+ private static final String SECRET = "secret";
+ private static final String BODY =
"{\"event\":\"taskTimeTrackedUpdated\"}";
+
+ private final CamelContext context = new DefaultCamelContext();
+ private final ClickUpWebhookService service = new
ClickUpWebhookService(null);
+ private final LogEventCaptor captor = new LogEventCaptor();
+ private Logger logger;
+ private Level level;
+
+ @BeforeEach
+ public void captureDebugLog() {
+ logger = (Logger) LogManager.getLogger(ClickUpWebhookService.class);
+ level = logger.getLevel();
+ Configurator.setLevel(logger.getName(), Level.DEBUG);
+ logger.addAppender(captor);
+ }
+
+ @AfterEach
+ public void restoreLog() {
+ logger.removeAppender(captor);
+ Configurator.setLevel(logger.getName(), level);
+ }
+
+ @Test
+ public void validSignatureIsNotLogged() {
+ String signature = Utils.computeMessageHMAC(BODY, SECRET);
+
+ service.validateMessageSignature(message(signature), SECRET);
+
+ assertThat(debugMessages()).anyMatch(m -> m.contains("matches"));
+ assertThat(debugMessages()).anyMatch(m -> m.contains(BODY));
+ assertThat(debugMessages()).noneMatch(m ->
m.toLowerCase().contains(signature.toLowerCase()));
+ }
+
+ @Test
+ public void
calculatedSignatureIsNotLoggedNorInTheExceptionOfAFailedCheck() {
+ String validSignature = Utils.computeMessageHMAC(BODY, SECRET);
+ String wrongSignature = Utils.computeMessageHMAC(BODY,
"another-secret");
+
+ assertThatThrownBy(() ->
service.validateMessageSignature(message(wrongSignature), SECRET))
+
.isInstanceOfSatisfying(InvalidMessageSignatureException.class, e -> {
+
assertThat(e.getMessageSignature()).isEqualToIgnoringCase(wrongSignature);
+ assertThat(e.getCalculatedSignature()).isNull();
+ });
+
+ assertThat(debugMessages()).anyMatch(m -> m.contains("does not
match"));
+ assertThat(debugMessages()).noneMatch(m ->
m.toLowerCase().contains(validSignature.toLowerCase()));
+ assertThat(debugMessages()).noneMatch(m ->
m.toLowerCase().contains(wrongSignature.toLowerCase()));
+ }
+
+ private Message message(String signature) {
+ Message message = new DefaultMessage(context);
+ message.setBody(new
ByteArrayInputStream(BODY.getBytes(StandardCharsets.UTF_8)));
+ message.setHeader("x-signature", signature);
+ return message;
+ }
+
+ private List<String> debugMessages() {
+ return captor.events.stream()
+ .filter(event -> event.getLevel() == Level.DEBUG)
+ .map(event -> event.getMessage().getFormattedMessage())
+ .toList();
+ }
+
+ private static final class LogEventCaptor extends AbstractAppender {
+ private final List<LogEvent> events = new CopyOnWriteArrayList<>();
+
+ LogEventCaptor() {
+ super("ClickUpWebhookSignatureLogCaptor", null, null, false,
Property.EMPTY_ARRAY);
+ start();
+ }
+
+ @Override
+ public void append(LogEvent event) {
+ events.add(event.toImmutable());
+ }
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
index d7984f86549a..147ca4d39f69 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
@@ -72,6 +72,11 @@ Checkpoint failures from the timer are now logged at `WARN`
level with the full
at `DEBUG` level with the message only. Checkpoint failures triggered by batch
size are still logged at
`WARN`.
+=== camel-clickup - the webhook signature check no longer logs signatures
+
+The webhook consumer still logs the body it validates at `DEBUG`, but no
longer the signature from the `X-Signature` header, nor the signature it
calculates.
+The `InvalidMessageSignatureException` of a failed check no longer carries the
calculated signature: `getCalculatedSignature()` returns `null` and is
deprecated, as is the constructor that takes it.
+
=== camel-hazelcast - the hazelcast-seda consumer restores a transferred
exchange only with transferExchange=true
A `hazelcast-seda` consumer now restores the headers and exchange properties
of an exchange that a producer with
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 34e38bc3c821..8560622e24b4 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -93,6 +93,11 @@ segment. A function name of several segments, such as
`products/42` for a functi
accepted. The `x-functions-key` header is only added to an `https` URL on an
`azurewebsites.net` host. Valid
configurations are not affected.
+=== camel-clickup - the webhook signature check no longer logs signatures
+
+The webhook consumer still logs the body it validates at `DEBUG`, but no
longer the signature from the `X-Signature` header, nor the signature it
calculates.
+The `InvalidMessageSignatureException` of a failed check no longer carries the
calculated signature: `getCalculatedSignature()` returns `null` and is
deprecated, as is the constructor that takes it.
+
=== camel-hazelcast - the hazelcast-seda consumer restores a transferred
exchange only with transferExchange=true
A `hazelcast-seda` consumer now restores the headers and exchange properties
of an exchange that a producer with
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index ec03b6400f3f..9580c105f0ef 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -32,6 +32,11 @@ camel.component.controlbus.allowed-languages = simple
The `route` command (`controlbus:route?action=...`) is not affected.
+=== camel-clickup - the webhook signature check no longer logs signatures
+
+The webhook consumer still logs the body it validates at `DEBUG`, but no
longer the signature from the `X-Signature` header, nor the signature it
calculates.
+The `InvalidMessageSignatureException` of a failed check no longer carries the
calculated signature: `getCalculatedSignature()` returns `null` and is
deprecated, as is the constructor that takes it.
+
=== camel-observability-services - promoted to Stable
The `camel-observability-services` component has been promoted from _Preview_
to _Stable_ support level.