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 3790c927ea3e CAMEL-25229: camel-hazelcast - warn and document when a 
user-supplied config has no serialization filter (#27255)
3790c927ea3e is described below

commit 3790c927ea3e6a53c5eb70c41ce99bf82895fd5d
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Oct 2 09:53:00 2026 +0200

    CAMEL-25229: camel-hazelcast - warn and document when a user-supplied 
config has no serialization filter (#27255)
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../camel/catalog/docs/hazelcast-summary.adoc      |  59 ++++++++
 .../src/main/docs/hazelcast-summary.adoc           |  59 ++++++++
 .../hazelcast/HazelcastDefaultComponent.java       |  27 ++++
 ...stUserConfigSerializationFilterWarningTest.java | 168 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |   6 +
 5 files changed, 319 insertions(+)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/hazelcast-summary.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/hazelcast-summary.adoc
index 2ae28709a40e..8db9e561f2e6 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/hazelcast-summary.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/hazelcast-summary.adoc
@@ -136,4 +136,63 @@ In the example above we set up this for the hazelcast map 
component and setup ha
 </bean>
 ------------------------------------------------------------------------------
 
+== Java serialization filter
+
+Hazelcast stores objects that implement `Serializable` or `Externalizable` 
using Java serialization, and deserializes
+them when Camel reads them back from a map, queue, topic or any other data 
structure. A Hazelcast
+`JavaSerializationFilterConfig` limits which classes Hazelcast deserializes.
+
+When Camel creates the Hazelcast instance itself, it applies a default filter. 
The default allows class names that
+start with `java.`, `javax.` or `org.apache.camel.`, and rejects those that 
start with `java.net.`. Camel creates the
+instance itself when:
+
+* an endpoint sets none of `hazelcastInstance`, `hazelcastInstanceName`, 
`hazelcastConfig` or `hazelcastConfigUri`,
+and no instance is configured on the component;
+* a Hazelcast aggregation repository, idempotent repository, key-value 
repository or route policy starts its own
+instance because none was given to it.
+
+Camel uses a configuration you supply as it is: a `Config` or `ClientConfig` 
bean set with `hazelcastConfig`, a file
+set with `hazelcastConfigUri`, or an existing instance set with 
`hazelcastInstance` or found with
+`hazelcastInstanceName`. Hazelcast's own default configuration declares no 
serialization filter, so declare one in
+your configuration, and make it cover the classes your application stores in 
the cluster:
+
+[source,xml]
+----
+<hazelcast xmlns="http://www.hazelcast.com/schema/config";>
+    <serialization>
+        <java-serialization-filter>
+            <whitelist>
+                <prefix>java.</prefix>
+                <prefix>javax.</prefix>
+                <prefix>org.apache.camel.</prefix>
+                <prefix>com.example.model.</prefix>
+            </whitelist>
+            <blacklist>
+                <prefix>java.net.</prefix>
+            </blacklist>
+        </java-serialization-filter>
+    </serialization>
+</hazelcast>
+----
+
+A `hazelcast-client` configuration takes the same `serialization` element. In 
Java, set the filter on the
+`SerializationConfig` of a `Config` or a `ClientConfig`:
+
+[source,java]
+----
+JavaSerializationFilterConfig filter = new JavaSerializationFilterConfig();
+filter.setWhitelist(new ClassFilter().addPrefixes("java.", "javax.", 
"org.apache.camel.", "com.example.model."));
+filter.setBlacklist(new ClassFilter().addPrefixes("java.net."));
+
+Config config = new Config();
+config.getSerializationConfig().setJavaSerializationFilterConfig(filter);
+----
+
+Alternatively, set a JVM-wide filter with the `jdk.serialFilter` system 
property, for example
+`+-Djdk.serialFilter='!java.net.**;java.**;javax.**;org.apache.camel.**;com.example.model.**;!*'+`.
 It applies to all
+Java deserialization in the JVM, not only to Hazelcast.
+
+When Camel starts a Hazelcast member or client from a `Config` or 
`ClientConfig` you supplied that declares no
+filter, and no JVM-wide filter is set, it logs a WARN.
+
 
diff --git a/components/camel-hazelcast/src/main/docs/hazelcast-summary.adoc 
b/components/camel-hazelcast/src/main/docs/hazelcast-summary.adoc
index 2ae28709a40e..8db9e561f2e6 100644
--- a/components/camel-hazelcast/src/main/docs/hazelcast-summary.adoc
+++ b/components/camel-hazelcast/src/main/docs/hazelcast-summary.adoc
@@ -136,4 +136,63 @@ In the example above we set up this for the hazelcast map 
component and setup ha
 </bean>
 ------------------------------------------------------------------------------
 
+== Java serialization filter
+
+Hazelcast stores objects that implement `Serializable` or `Externalizable` 
using Java serialization, and deserializes
+them when Camel reads them back from a map, queue, topic or any other data 
structure. A Hazelcast
+`JavaSerializationFilterConfig` limits which classes Hazelcast deserializes.
+
+When Camel creates the Hazelcast instance itself, it applies a default filter. 
The default allows class names that
+start with `java.`, `javax.` or `org.apache.camel.`, and rejects those that 
start with `java.net.`. Camel creates the
+instance itself when:
+
+* an endpoint sets none of `hazelcastInstance`, `hazelcastInstanceName`, 
`hazelcastConfig` or `hazelcastConfigUri`,
+and no instance is configured on the component;
+* a Hazelcast aggregation repository, idempotent repository, key-value 
repository or route policy starts its own
+instance because none was given to it.
+
+Camel uses a configuration you supply as it is: a `Config` or `ClientConfig` 
bean set with `hazelcastConfig`, a file
+set with `hazelcastConfigUri`, or an existing instance set with 
`hazelcastInstance` or found with
+`hazelcastInstanceName`. Hazelcast's own default configuration declares no 
serialization filter, so declare one in
+your configuration, and make it cover the classes your application stores in 
the cluster:
+
+[source,xml]
+----
+<hazelcast xmlns="http://www.hazelcast.com/schema/config";>
+    <serialization>
+        <java-serialization-filter>
+            <whitelist>
+                <prefix>java.</prefix>
+                <prefix>javax.</prefix>
+                <prefix>org.apache.camel.</prefix>
+                <prefix>com.example.model.</prefix>
+            </whitelist>
+            <blacklist>
+                <prefix>java.net.</prefix>
+            </blacklist>
+        </java-serialization-filter>
+    </serialization>
+</hazelcast>
+----
+
+A `hazelcast-client` configuration takes the same `serialization` element. In 
Java, set the filter on the
+`SerializationConfig` of a `Config` or a `ClientConfig`:
+
+[source,java]
+----
+JavaSerializationFilterConfig filter = new JavaSerializationFilterConfig();
+filter.setWhitelist(new ClassFilter().addPrefixes("java.", "javax.", 
"org.apache.camel.", "com.example.model."));
+filter.setBlacklist(new ClassFilter().addPrefixes("java.net."));
+
+Config config = new Config();
+config.getSerializationConfig().setJavaSerializationFilterConfig(filter);
+----
+
+Alternatively, set a JVM-wide filter with the `jdk.serialFilter` system 
property, for example
+`+-Djdk.serialFilter='!java.net.**;java.**;javax.**;org.apache.camel.**;com.example.model.**;!*'+`.
 It applies to all
+Java deserialization in the JVM, not only to Hazelcast.
+
+When Camel starts a Hazelcast member or client from a `Config` or 
`ClientConfig` you supplied that declares no
+filter, and no JVM-wide filter is set, it logs a WARN.
+
 
diff --git 
a/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/HazelcastDefaultComponent.java
 
b/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/HazelcastDefaultComponent.java
index 636d4af66318..9df779876a63 100644
--- 
a/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/HazelcastDefaultComponent.java
+++ 
b/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/HazelcastDefaultComponent.java
@@ -17,6 +17,7 @@
 package org.apache.camel.component.hazelcast;
 
 import java.io.InputStream;
+import java.io.ObjectInputFilter;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
@@ -25,6 +26,7 @@ import com.hazelcast.client.HazelcastClient;
 import com.hazelcast.client.config.ClientConfig;
 import com.hazelcast.client.config.XmlClientConfigBuilder;
 import com.hazelcast.config.Config;
+import com.hazelcast.config.SerializationConfig;
 import com.hazelcast.config.XmlConfigBuilder;
 import com.hazelcast.core.Hazelcast;
 import com.hazelcast.core.HazelcastInstance;
@@ -134,6 +136,7 @@ public abstract class HazelcastDefaultComponent extends 
DefaultComponent {
     protected HazelcastInstance getOrCreateHzInstance(CamelContext context, 
Map<String, Object> parameters) throws Exception {
         HazelcastInstance hzInstance = null;
         Config config = null;
+        boolean userConfig = false;
 
         // Query param named 'hazelcastInstance' (if exists) overrides the 
instance that was set
         hzInstance = resolveAndRemoveReferenceParameter(parameters, 
HAZELCAST_INSTANCE_PARAM, HazelcastInstance.class);
@@ -167,6 +170,7 @@ public abstract class HazelcastDefaultComponent extends 
DefaultComponent {
 
                 hzInstance = Hazelcast.newHazelcastInstance(config);
             } else if (config != null) {
+                userConfig = true;
                 if (ObjectHelper.isNotEmpty(config.getInstanceName())) {
                     hzInstance = 
Hazelcast.getOrCreateHazelcastInstance(config);
                 } else {
@@ -177,6 +181,9 @@ public abstract class HazelcastDefaultComponent extends 
DefaultComponent {
             if (hzInstance != null) {
                 if (this.customHazelcastInstances.add(hzInstance)) {
                     LOGGER.debug("Add managed HZ instance {}", 
hzInstance.getName());
+                    if (userConfig) {
+                        
warnIfNoSerializationFilter(config.getSerializationConfig(), 
hzInstance.getName());
+                    }
                 }
             }
         }
@@ -188,6 +195,7 @@ public abstract class HazelcastDefaultComponent extends 
DefaultComponent {
             throws Exception {
         HazelcastInstance hzInstance = null;
         ClientConfig config = null;
+        boolean userConfig = false;
 
         // Query param named 'hazelcastInstance' (if exists) overrides the 
instance that was set
         hzInstance = resolveAndRemoveReferenceParameter(parameters, 
HAZELCAST_INSTANCE_PARAM, HazelcastInstance.class);
@@ -221,16 +229,35 @@ public abstract class HazelcastDefaultComponent extends 
DefaultComponent {
 
                 hzInstance = HazelcastClient.newHazelcastClient(config);
             } else if (config != null) {
+                userConfig = true;
                 hzInstance = HazelcastClient.newHazelcastClient(config);
             }
 
             if (hzInstance != null) {
                 if (this.customHazelcastInstances.add(hzInstance)) {
                     LOGGER.debug("Add managed HZ instance {}", 
hzInstance.getName());
+                    if (userConfig) {
+                        
warnIfNoSerializationFilter(config.getSerializationConfig(), 
hzInstance.getName());
+                    }
                 }
             }
         }
 
         return hzInstance == null ? hazelcastInstance : hzInstance;
     }
+
+    /**
+     * Camel applies its default serialization filter only to the 
configurations it builds itself, and uses a
+     * user-supplied configuration unchanged, so tell the user when that 
configuration restricts nothing.
+     */
+    private static void warnIfNoSerializationFilter(SerializationConfig 
serializationConfig, String instanceName) {
+        if (serializationConfig.getJavaSerializationFilterConfig() == null
+                && ObjectInputFilter.Config.getSerialFilter() == null) {
+            LOGGER.warn("The user-supplied configuration of Hazelcast instance 
{} declares no Java serialization filter"
+                        + " (JavaSerializationFilterConfig) and no JVM-wide 
jdk.serialFilter is set, so Java deserialization"
+                        + " in this instance is not restricted. Declare a 
java-serialization-filter in the configuration,"
+                        + " see the camel-hazelcast documentation.",
+                    instanceName);
+        }
+    }
 }
diff --git 
a/components/camel-hazelcast/src/test/java/org/apache/camel/component/hazelcast/HazelcastUserConfigSerializationFilterWarningTest.java
 
b/components/camel-hazelcast/src/test/java/org/apache/camel/component/hazelcast/HazelcastUserConfigSerializationFilterWarningTest.java
new file mode 100644
index 000000000000..38470b3b66e9
--- /dev/null
+++ 
b/components/camel-hazelcast/src/test/java/org/apache/camel/component/hazelcast/HazelcastUserConfigSerializationFilterWarningTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.hazelcast;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.hazelcast.client.HazelcastClient;
+import com.hazelcast.client.config.ClientConfig;
+import com.hazelcast.cluster.Address;
+import com.hazelcast.config.ClassFilter;
+import com.hazelcast.config.Config;
+import com.hazelcast.config.JavaSerializationFilterConfig;
+import com.hazelcast.config.JoinConfig;
+import com.hazelcast.core.Hazelcast;
+import org.apache.camel.component.hazelcast.map.HazelcastMapComponent;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.SimpleRegistry;
+import org.apache.camel.test.AvailablePortFinder;
+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.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class HazelcastUserConfigSerializationFilterWarningTest {
+
+    private final List<String> warnings = new CopyOnWriteArrayList<>();
+    private final AbstractAppender appender
+            = new AbstractAppender("SerializationFilterWarning", null, null, 
true, Property.EMPTY_ARRAY) {
+                @Override
+                public void append(LogEvent event) {
+                    if (event.getLevel() == Level.WARN) {
+                        warnings.add(event.getMessage().getFormattedMessage());
+                    }
+                }
+            };
+    private final SimpleRegistry registry = new SimpleRegistry();
+    private DefaultCamelContext context;
+
+    @BeforeEach
+    void captureWarnings() {
+        appender.start();
+        componentLogger().addAppender(appender);
+        // addAppender creates a non-additive logger config, which would hide 
this logger from the root appenders
+        componentLogger().setAdditive(true);
+    }
+
+    @AfterEach
+    void tearDown() {
+        componentLogger().removeAppender(appender);
+        appender.stop();
+        if (context != null) {
+            context.stop();
+        }
+        HazelcastClient.shutdownAll();
+        Hazelcast.shutdownAll();
+    }
+
+    @Test
+    void warnsOnceForUserConfigWithoutFilter() {
+        try (AvailablePortFinder.Port port = AvailablePortFinder.find()) {
+            Config config = memberConfig(port.getPort());
+            registry.bind("userConfig", config);
+            startContext(new HazelcastMapComponent());
+
+            
context.getEndpoint("hazelcast-map:cache-1?hazelcastConfig=#userConfig");
+            
context.getEndpoint("hazelcast-map:cache-2?hazelcastConfig=#userConfig");
+
+            assertEquals(1, warningsFor(config.getInstanceName()));
+        }
+    }
+
+    @Test
+    void doesNotWarnForUserConfigWithFilter() {
+        try (AvailablePortFinder.Port port = AvailablePortFinder.find()) {
+            Config config = memberConfig(port.getPort());
+            JavaSerializationFilterConfig filter = new 
JavaSerializationFilterConfig();
+            filter.setWhitelist(new ClassFilter().addPrefixes("java.", 
"org.apache.camel."));
+            
config.getSerializationConfig().setJavaSerializationFilterConfig(filter);
+            registry.bind("userConfig", config);
+            startContext(new HazelcastMapComponent());
+
+            
context.getEndpoint("hazelcast-map:cache?hazelcastConfig=#userConfig");
+
+            assertEquals(0, warningsFor(config.getInstanceName()));
+        }
+    }
+
+    @Test
+    void doesNotWarnForCamelBuiltConfig() {
+        startContext(new HazelcastMapComponent());
+
+        HazelcastDefaultEndpoint endpoint = (HazelcastDefaultEndpoint) 
context.getEndpoint("hazelcast-map:cache");
+
+        assertEquals(0, 
warningsFor(endpoint.getHazelcastInstance().getName()));
+    }
+
+    @Test
+    void warnsForUserClientConfigWithoutFilter() {
+        try (AvailablePortFinder.Port port = AvailablePortFinder.find()) {
+            Config memberConfig = memberConfig(port.getPort());
+            Address member = 
Hazelcast.newHazelcastInstance(memberConfig).getCluster().getLocalMember().getAddress();
+
+            ClientConfig clientConfig = new ClientConfig();
+            clientConfig.setInstanceName("user-client-config-" + 
UUID.randomUUID());
+            clientConfig.setClusterName(memberConfig.getClusterName());
+            clientConfig.getNetworkConfig().addAddress(member.getHost() + ":" 
+ member.getPort());
+            
clientConfig.getConnectionStrategyConfig().getConnectionRetryConfig().setClusterConnectTimeoutMillis(30000);
+            registry.bind("userClientConfig", clientConfig);
+
+            HazelcastMapComponent component = new HazelcastMapComponent();
+            
component.setHazelcastMode(HazelcastConstants.HAZELCAST_CLIENT_MODE);
+            startContext(component);
+
+            
context.getEndpoint("hazelcast-map:cache?hazelcastConfig=#userClientConfig");
+
+            assertEquals(1, warningsFor(clientConfig.getInstanceName()));
+        }
+    }
+
+    private void startContext(HazelcastMapComponent component) {
+        context = new DefaultCamelContext(registry);
+        context.addComponent("hazelcast-map", component);
+        context.start();
+    }
+
+    private long warningsFor(String instanceName) {
+        return warnings.stream().filter(message -> 
message.contains(instanceName)).count();
+    }
+
+    private static Logger componentLogger() {
+        return (Logger) LogManager.getLogger(HazelcastDefaultComponent.class);
+    }
+
+    private static Config memberConfig(int port) {
+        Config config = new Config();
+        config.setInstanceName("user-config-" + UUID.randomUUID());
+        config.setClusterName("user-config-" + UUID.randomUUID());
+        config.getNetworkConfig().setPort(port);
+        JoinConfig join = config.getNetworkConfig().getJoin();
+        join.getMulticastConfig().setEnabled(false);
+        join.getTcpIpConfig().setEnabled(false);
+        join.getAutoDetectionConfig().setEnabled(false);
+        return config;
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index c15293a15c68..2e4dec9ba18a 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -1491,6 +1491,12 @@ The same default is now also applied to the 
`ClientConfig` that Camel builds for
 endpoints, when neither a referenced `ClientConfig` nor `hazelcastConfigUri` 
is supplied. Client mode
 previously behaved differently from node mode for an otherwise identical 
endpoint configuration.
 
+Camel still uses a user-supplied `Config` or `ClientConfig` (a 
`hazelcastConfig` bean or a
+`hazelcastConfigUri` file) unchanged. It now logs a WARN when it starts a 
Hazelcast member or client from
+such a configuration that declares no `JavaSerializationFilterConfig`, unless 
a JVM-wide `jdk.serialFilter`
+is set. To remove the warning, declare a `java-serialization-filter` in the 
configuration, as described in
+the xref:components::hazelcast-summary.adoc[Hazelcast component] documentation.
+
 === camel-netty - NettyConverter.toByteArray returns a copy of the buffer's 
readable bytes
 
 The `ByteBuf` to `byte[]` type converter (`NettyConverter.toByteArray`, also 
used when converting a Netty

Reply via email to