You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/09/11 05:36:23 UTC

(camel) branch backport/26267-to-camel-4.18.x created (now 58609a034637)

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

davsclaus pushed a change to branch backport/26267-to-camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git


      at 58609a034637 CAMEL-24676: camel-jgroups - add a configurable deserialization filter and document serialization hardening

This branch includes the following new commits:

     new 58609a034637 CAMEL-24676: camel-jgroups - add a configurable deserialization filter and document serialization hardening

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(camel) 01/01: CAMEL-24676: camel-jgroups - add a configurable deserialization filter and document serialization hardening

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch backport/26267-to-camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 58609a034637b2b91d7958f131e8393e33b18609
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Fri Sep 11 07:19:14 2026 +0200

    CAMEL-24676: camel-jgroups - add a configurable deserialization filter and document serialization hardening
    
    Backport of #26267 (0ef94ee8feab) to camel-4.18.x.
    
    The jgroups consumer maps an incoming cluster message to the exchange body via
    org.jgroups.Message.getObject(), which Java-deserializes the payload. The class of
    that body is now checked against a JEP-290 ObjectInputFilter before the exchange is
    routed. The check is applied by default: when the new deserializationFilter
    consumer/component option is not set, the JVM-wide jdk.serialFilter is honoured if
    set, otherwise a conservative default allow-list applies.
    deserializationFilter=* opts out.
    
    A refused message is no longer dropped silently: createExchange was called outside
    the try/catch in CamelJGroupsReceiver, so the exception escaped into the JGroups
    receive thread and was swallowed. It is now reported to the consumer's
    ExceptionHandler, so it is logged at WARN and honours bridgeErrorHandler.
    
    Adaptations for camel-4.18.x (this is not a straight cherry-pick):
    
    - core/camel-support has no DeserializationFilterHelper on this branch; the shared
      helper landed in 4.22. The filter resolution, the default pattern constant and the
      class-only FilterInfo are therefore implemented locally in JGroupsEndpoint, the
      same component-local style JmsBinding, MinaConverter and NettyHttpHelper already
      use on this branch. The default pattern is byte-identical to the shared helper's
      DEFAULT_CLASS_DESERIALIZATION_FILTER
      (!java.net.**;java.**;javax.**;org.apache.camel.**;!*), that is, without the
      JEP-290 graph-shape limits, which cannot be enforced once a third-party library
      has already deserialized the payload.
    
    - The two new tests extend org.apache.camel.test.junit5.CamelTestSupport; the junit6
      base class used on main does not exist on this branch.
    
    - The upgrade guide entry targets camel-4x-upgrade-guide-4_18.adoc (section
      "Upgrading from 4.18.4 to 4.18.5") instead of the 4_23 guide, and names the
      default pattern directly rather than referring to DeserializationFilterHelper.
    
    - catalog/camel-catalog does not mirror component .adoc docs on this branch, so the
      catalog docs copy from the original commit is dropped; the component's own
      src/main/docs/jgroups-component.adoc carries the new Security section.
    
    Verified on this branch: components/camel-jgroups builds and the full module test
    suite passes (22 tests, 0 failures), including the 4 new filter tests.
    
    Co-Authored-By: Claude Opus 5 (1M context) <no...@anthropic.com>
    Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
 .../apache/camel/catalog/components/jgroups.json   |   6 +-
 .../jgroups/JGroupsComponentConfigurer.java        |   6 +
 .../jgroups/JGroupsEndpointConfigurer.java         |   6 +
 .../jgroups/JGroupsEndpointUriFactory.java         |   3 +-
 .../apache/camel/component/jgroups/jgroups.json    |   6 +-
 .../src/main/docs/jgroups-component.adoc           |  61 +++++++++++
 .../component/jgroups/CamelJGroupsReceiver.java    |  11 +-
 .../camel/component/jgroups/JGroupsComponent.java  |  29 +++++
 .../camel/component/jgroups/JGroupsEndpoint.java   | 116 +++++++++++++++++++-
 .../camel/component/jgroups/JGroupsException.java  |   4 +
 .../jgroups/CapturingExceptionHandler.java         |  50 +++++++++
 .../JGroupsDefaultDeserializationFilterTest.java   | 121 +++++++++++++++++++++
 .../jgroups/JGroupsDeserializationFilterTest.java  | 111 +++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc    |  27 +++++
 .../dsl/JgroupsComponentBuilderFactory.java        |  26 +++++
 .../dsl/JGroupsEndpointBuilderFactory.java         |  24 ++++
 16 files changed, 600 insertions(+), 7 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jgroups.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jgroups.json
index 91d662a171bf..7049729f0b3c 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jgroups.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jgroups.json
@@ -29,7 +29,8 @@
     "bridgeErrorHandler": { "index": 2, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the like [...]
     "enableViewMessages": { "index": 3, "kind": "property", "displayName": "Enable View Messages", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If set to true, the consumer endpoint will receive org.jgroups.View messages as well (not only org.jgroups.Message instances). By default only regular messages are consumed by the endpoint." },
     "lazyStartProducer": { "index": 4, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...]
-    "autowiredEnabled": { "index": 5, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...]
+    "autowiredEnabled": { "index": 5, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...]
+    "deserializationFilter": { "index": 6, "kind": "property", "displayName": "Deserialization Filter", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied as a defense-in-depth check on the class of the message body deserialized by org.jgroups.Message.getObject(). The pattern is e [...]
   },
   "headers": {
     "CamelJGroupsChannelAddress": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "org.jgroups.Address", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Address (org.jgroups.Address) of the channel associated with the endpoint.", "constantName": "org.apache.camel.component.jgroups.JGroupsConstants#HEADER_JGROUPS_CHANNEL_ADDRESS" },
@@ -44,6 +45,7 @@
     "bridgeErrorHandler": { "index": 3, "kind": "parameter", "displayName": "Bridge Error Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming  [...]
     "exceptionHandler": { "index": 4, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By def [...]
     "exchangePattern": { "index": 5, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." },
-    "lazyStartProducer": { "index": 6, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...]
+    "lazyStartProducer": { "index": 6, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...]
+    "deserializationFilter": { "index": 7, "kind": "parameter", "displayName": "Deserialization Filter", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied as a defense-in-depth check on the class of the message body deserialized by org.jgroups.Message.getObject(). The pattern is  [...]
   }
 }
diff --git a/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsComponentConfigurer.java b/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsComponentConfigurer.java
index caa39edc7b91..0e16fb679bb0 100644
--- a/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsComponentConfigurer.java
+++ b/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsComponentConfigurer.java
@@ -30,6 +30,8 @@ public class JGroupsComponentConfigurer extends PropertyConfigurerSupport implem
         case "channel": target.setChannel(property(camelContext, org.jgroups.JChannel.class, value)); return true;
         case "channelproperties":
         case "channelProperties": target.setChannelProperties(property(camelContext, java.lang.String.class, value)); return true;
+        case "deserializationfilter":
+        case "deserializationFilter": target.setDeserializationFilter(property(camelContext, java.lang.String.class, value)); return true;
         case "enableviewmessages":
         case "enableViewMessages": target.setEnableViewMessages(property(camelContext, boolean.class, value)); return true;
         case "lazystartproducer":
@@ -48,6 +50,8 @@ public class JGroupsComponentConfigurer extends PropertyConfigurerSupport implem
         case "channel": return org.jgroups.JChannel.class;
         case "channelproperties":
         case "channelProperties": return java.lang.String.class;
+        case "deserializationfilter":
+        case "deserializationFilter": return java.lang.String.class;
         case "enableviewmessages":
         case "enableViewMessages": return boolean.class;
         case "lazystartproducer":
@@ -67,6 +71,8 @@ public class JGroupsComponentConfigurer extends PropertyConfigurerSupport implem
         case "channel": return target.getChannel();
         case "channelproperties":
         case "channelProperties": return target.getChannelProperties();
+        case "deserializationfilter":
+        case "deserializationFilter": return target.getDeserializationFilter();
         case "enableviewmessages":
         case "enableViewMessages": return target.isEnableViewMessages();
         case "lazystartproducer":
diff --git a/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsEndpointConfigurer.java b/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsEndpointConfigurer.java
index 8b33ca708e4e..0cc61e508a1b 100644
--- a/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsEndpointConfigurer.java
+++ b/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsEndpointConfigurer.java
@@ -27,6 +27,8 @@ public class JGroupsEndpointConfigurer extends PropertyConfigurerSupport impleme
         case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true;
         case "channelproperties":
         case "channelProperties": target.setChannelProperties(property(camelContext, java.lang.String.class, value)); return true;
+        case "deserializationfilter":
+        case "deserializationFilter": target.setDeserializationFilter(property(camelContext, java.lang.String.class, value)); return true;
         case "enableviewmessages":
         case "enableViewMessages": target.setEnableViewMessages(property(camelContext, boolean.class, value)); return true;
         case "exceptionhandler":
@@ -46,6 +48,8 @@ public class JGroupsEndpointConfigurer extends PropertyConfigurerSupport impleme
         case "bridgeErrorHandler": return boolean.class;
         case "channelproperties":
         case "channelProperties": return java.lang.String.class;
+        case "deserializationfilter":
+        case "deserializationFilter": return java.lang.String.class;
         case "enableviewmessages":
         case "enableViewMessages": return boolean.class;
         case "exceptionhandler":
@@ -66,6 +70,8 @@ public class JGroupsEndpointConfigurer extends PropertyConfigurerSupport impleme
         case "bridgeErrorHandler": return target.isBridgeErrorHandler();
         case "channelproperties":
         case "channelProperties": return target.getChannelProperties();
+        case "deserializationfilter":
+        case "deserializationFilter": return target.getDeserializationFilter();
         case "enableviewmessages":
         case "enableViewMessages": return target.isEnableViewMessages();
         case "exceptionhandler":
diff --git a/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsEndpointUriFactory.java b/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsEndpointUriFactory.java
index a3da2c521d15..ac3bb98b315f 100644
--- a/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsEndpointUriFactory.java
+++ b/components/camel-jgroups/src/generated/java/org/apache/camel/component/jgroups/JGroupsEndpointUriFactory.java
@@ -23,10 +23,11 @@ public class JGroupsEndpointUriFactory extends org.apache.camel.support.componen
     private static final Set<String> SECRET_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(7);
+        Set<String> props = new HashSet<>(8);
         props.add("bridgeErrorHandler");
         props.add("channelProperties");
         props.add("clusterName");
+        props.add("deserializationFilter");
         props.add("enableViewMessages");
         props.add("exceptionHandler");
         props.add("exchangePattern");
diff --git a/components/camel-jgroups/src/generated/resources/META-INF/org/apache/camel/component/jgroups/jgroups.json b/components/camel-jgroups/src/generated/resources/META-INF/org/apache/camel/component/jgroups/jgroups.json
index 91d662a171bf..7049729f0b3c 100644
--- a/components/camel-jgroups/src/generated/resources/META-INF/org/apache/camel/component/jgroups/jgroups.json
+++ b/components/camel-jgroups/src/generated/resources/META-INF/org/apache/camel/component/jgroups/jgroups.json
@@ -29,7 +29,8 @@
     "bridgeErrorHandler": { "index": 2, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the like [...]
     "enableViewMessages": { "index": 3, "kind": "property", "displayName": "Enable View Messages", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If set to true, the consumer endpoint will receive org.jgroups.View messages as well (not only org.jgroups.Message instances). By default only regular messages are consumed by the endpoint." },
     "lazyStartProducer": { "index": 4, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...]
-    "autowiredEnabled": { "index": 5, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...]
+    "autowiredEnabled": { "index": 5, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...]
+    "deserializationFilter": { "index": 6, "kind": "property", "displayName": "Deserialization Filter", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied as a defense-in-depth check on the class of the message body deserialized by org.jgroups.Message.getObject(). The pattern is e [...]
   },
   "headers": {
     "CamelJGroupsChannelAddress": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "org.jgroups.Address", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Address (org.jgroups.Address) of the channel associated with the endpoint.", "constantName": "org.apache.camel.component.jgroups.JGroupsConstants#HEADER_JGROUPS_CHANNEL_ADDRESS" },
@@ -44,6 +45,7 @@
     "bridgeErrorHandler": { "index": 3, "kind": "parameter", "displayName": "Bridge Error Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming  [...]
     "exceptionHandler": { "index": 4, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By def [...]
     "exchangePattern": { "index": 5, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." },
-    "lazyStartProducer": { "index": 6, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...]
+    "lazyStartProducer": { "index": 6, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...]
+    "deserializationFilter": { "index": 7, "kind": "parameter", "displayName": "Deserialization Filter", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied as a defense-in-depth check on the class of the message body deserialized by org.jgroups.Message.getObject(). The pattern is  [...]
   }
 }
diff --git a/components/camel-jgroups/src/main/docs/jgroups-component.adoc b/components/camel-jgroups/src/main/docs/jgroups-component.adoc
index ee73dd57d8be..2da0651c3d14 100644
--- a/components/camel-jgroups/src/main/docs/jgroups-component.adoc
+++ b/components/camel-jgroups/src/main/docs/jgroups-component.adoc
@@ -47,6 +47,67 @@ include::partial$component-endpoint-options.adoc[]
 include::partial$component-endpoint-headers.adoc[]
 // component options: END
 
+== Security
+
+The `jgroups` consumer converts an incoming cluster message into the exchange
+body by calling `org.jgroups.Message.getObject()`, which Java-deserializes the
+payload carried by the message. Deserialization is performed by JGroups on its
+own receive path, and the bodies a consumer receives originate from any peer
+that has joined the cluster.
+
+The default JGroups protocol stack (`udp.xml`) multicasts over UDP and does
+*not* authenticate or encrypt peers: any host able to reach the cluster's
+multicast group or bind port can join and send messages. Treat cluster peers as
+part of your trust boundary and harden the deployment accordingly.
+
+* *Authenticate and encrypt the channel.* Supply a hardened JGroups
+configuration through the `channelProperties` option and add the `AUTH`
+protocol together with `SYM_ENCRYPT` or `ASYM_ENCRYPT`, so that only trusted
+peers can join and message contents are protected on the wire. See the
+https://www.jgroups.org[JGroups documentation] for the protocol-stack reference.
+* *Constrain deserialization at the JVM.* Set a JVM-wide JEP-290 serialization
+filter (`-Djdk.serialFilter=...` or `ObjectInputFilter.Config.setSerialFilter(...)`)
+to restrict which classes may be deserialized from the network. Because JGroups
+deserializes messages inside its own receive path, the JVM-wide filter is the
+mechanism that constrains that deserialization.
+* *Restrict the accepted body types in Camel.* The consumer applies a JEP-290
+`ObjectInputFilter` pattern to the type of the message body before the exchange
+is routed, and refuses bodies whose type is not allowed. This is a
+defense-in-depth allow-list applied _after_ JGroups has deserialized the
+message; it complements, but does not replace, the JVM-wide `jdk.serialFilter`
+and the channel authentication and encryption above.
+
+=== The consumer deserialization filter
+
+The class check is applied by default. When the `deserializationFilter` option
+is not configured, the JVM-wide `jdk.serialFilter` is honoured if it is set,
+otherwise the shared Camel default allow-list is applied: it permits standard
+Java and Apache Camel types, denies `java.net.**`, and rejects everything else.
+
+A route that exchanges its own classes over the cluster must widen the filter
+through the `deserializationFilter` option, listing the packages it accepts:
+
+[source,java]
+----
+from("jgroups:clusterName?deserializationFilter=com.example.model.**;java.**;!*")
+  .to("seda:queue");
+----
+
+The snippet below accepts only `String` bodies from the cluster, on top of a
+hardened, authenticated and encrypted channel supplied through `channelProperties`:
+
+[source,java]
+----
+from("jgroups:clusterName?channelProperties=secure-udp.xml&deserializationFilter=java.lang.String;!*")
+  .to("seda:queue");
+----
+
+Setting `deserializationFilter=*` accepts any type and so opts out of the check.
+
+A refused message is not routed. It is reported to the consumer's
+`ExceptionHandler`, which logs it at WARN level by default, and is passed to the
+route error handler when `bridgeErrorHandler=true` is set.
+
 == Usage
 
 Using `jgroups` component on the consumer side of the route will capture
diff --git a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/CamelJGroupsReceiver.java b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/CamelJGroupsReceiver.java
index 012952367979..e0711fc41a36 100644
--- a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/CamelJGroupsReceiver.java
+++ b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/CamelJGroupsReceiver.java
@@ -69,7 +69,16 @@ public class CamelJGroupsReceiver implements Receiver {
 
     @Override
     public void receive(Message message) {
-        Exchange exchange = endpoint.createExchange(message);
+        Exchange exchange;
+        try {
+            exchange = endpoint.createExchange(message);
+        } catch (Exception e) {
+            // the message could not be turned into an exchange, for example because its body type was
+            // refused by the configured deserializationFilter, so let the consumer deal with the failure
+            consumer.getExceptionHandler().handleException(
+                    "Error creating exchange for message " + message + ". This message is dropped.", e);
+            return;
+        }
         try {
             LOG.debug("Processing message: {}", message);
             processor.process(exchange, doneSync -> {
diff --git a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsComponent.java b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsComponent.java
index 96ac0e858231..579c3c3d945c 100644
--- a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsComponent.java
+++ b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsComponent.java
@@ -36,6 +36,16 @@ public class JGroupsComponent extends DefaultComponent {
     private String channelProperties;
     @Metadata(label = "consumer")
     private boolean enableViewMessages;
+    @Metadata(label = "consumer,security",
+              description = "Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied as a defense-in-depth"
+                            + " check on the class of the message body deserialized by org.jgroups.Message.getObject()."
+                            + " The pattern is evaluated after JGroups has deserialized the payload, so this option alone"
+                            + " does not prevent gadget-chain execution that happens inside the JGroups receive path;"
+                            + " to block such attacks, also configure the JVM-wide -Djdk.serialFilter and secure the"
+                            + " channel with AUTH and encryption. When this option is not set and no JVM-wide filter is"
+                            + " configured, a conservative default filter denying java.net.** and otherwise allowing"
+                            + " java.**, javax.** and org.apache.camel.** is applied. Use * to accept any type.")
+    private String deserializationFilter;
 
     public JGroupsComponent() {
     }
@@ -43,6 +53,7 @@ public class JGroupsComponent extends DefaultComponent {
     @Override
     protected Endpoint createEndpoint(String uri, String clusterName, Map<String, Object> parameters) throws Exception {
         JGroupsEndpoint endpoint = new JGroupsEndpoint(uri, this, channel, clusterName, channelProperties, enableViewMessages);
+        endpoint.setDeserializationFilter(deserializationFilter);
         setProperties(endpoint, parameters);
         return endpoint;
     }
@@ -81,4 +92,22 @@ public class JGroupsComponent extends DefaultComponent {
         this.enableViewMessages = enableViewMessages;
     }
 
+    public String getDeserializationFilter() {
+        return deserializationFilter;
+    }
+
+    /**
+     * Sets an {@code java.io.ObjectInputFilter} pattern (same syntax as {@code jdk.serialFilter}) applied as a
+     * defense-in-depth check on the class of the message body deserialized by {@code org.jgroups.Message#getObject()}.
+     * The pattern is evaluated after JGroups has deserialized the payload, so this option alone does not prevent
+     * gadget-chain execution that happens inside the JGroups receive path; to block such attacks, also configure the
+     * JVM-wide {@code -Djdk.serialFilter} and secure the channel with {@code AUTH} and encryption. When this option is
+     * not set and no JVM-wide filter is configured, a conservative default filter denying {@code java.net.**} and
+     * otherwise allowing {@code java.**}, {@code javax.**} and {@code org.apache.camel.**} is applied. Use {@code *} to
+     * accept any type.
+     */
+    public void setDeserializationFilter(String deserializationFilter) {
+        this.deserializationFilter = deserializationFilter;
+    }
+
 }
diff --git a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsEndpoint.java b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsEndpoint.java
index b7ae9672c155..efffb4ef0df2 100644
--- a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsEndpoint.java
+++ b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsEndpoint.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.component.jgroups;
 
+import java.io.ObjectInputFilter;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.camel.Category;
@@ -42,6 +43,19 @@ import org.slf4j.LoggerFactory;
              category = { Category.CLUSTERING, Category.MESSAGING }, headersClass = JGroupsConstants.class)
 public class JGroupsEndpoint extends DefaultEndpoint {
 
+    /**
+     * Default {@link ObjectInputFilter} pattern applied as a defense-in-depth check on the class of the body returned
+     * by {@link Message#getObject()}. Allows standard Java types and Apache Camel types and rejects everything else.
+     * Can be overridden per-endpoint via {@link #setDeserializationFilter(String)} or globally via the JVM system
+     * property {@code jdk.serialFilter}.
+     * <p>
+     * Deliberately without the JEP-290 graph-shape limits ({@code maxdepth} and friends): JGroups has already
+     * deserialized the payload by the time this filter runs, so stream metrics are not available and including the
+     * limits would only advertise protection that can never be enforced.
+     */
+    static final String DEFAULT_CLASS_DESERIALIZATION_FILTER
+            = "!java.net.**;java.**;javax.**;org.apache.camel.**;!*";
+
     private static final Logger LOG = LoggerFactory.getLogger(JGroupsEndpoint.class);
     private AtomicInteger connectCount = new AtomicInteger();
 
@@ -55,6 +69,18 @@ public class JGroupsEndpoint extends DefaultEndpoint {
     private String channelProperties;
     @UriParam(label = "consumer")
     private boolean enableViewMessages;
+    @UriParam(label = "consumer,security",
+              description = "Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied as a defense-in-depth"
+                            + " check on the class of the message body deserialized by org.jgroups.Message.getObject()."
+                            + " The pattern is evaluated after JGroups has deserialized the payload, so this option alone"
+                            + " does not prevent gadget-chain execution that happens inside the JGroups receive path;"
+                            + " to block such attacks, also configure the JVM-wide -Djdk.serialFilter and secure the"
+                            + " channel with AUTH and encryption. When this option is not set and no JVM-wide filter is"
+                            + " configured, a conservative default filter denying java.net.** and otherwise allowing"
+                            + " java.**, javax.** and org.apache.camel.** is applied. Use * to accept any type.")
+    private String deserializationFilter;
+
+    private volatile ObjectInputFilter resolvedDeserializationFilter;
 
     public JGroupsEndpoint(String endpointUri, Component component, JChannel channel, String clusterName,
                            String channelProperties, boolean enableViewMessages) {
@@ -82,10 +108,29 @@ public class JGroupsEndpoint extends DefaultEndpoint {
         exchange.getIn().setHeader(JGroupsConstants.HEADER_JGROUPS_ORIGINAL_MESSAGE, message);
         exchange.getIn().setHeader(JGroupsConstants.HEADER_JGROUPS_SRC, message.getSrc());
         exchange.getIn().setHeader(JGroupsConstants.HEADER_JGROUPS_DEST, message.getDest());
-        exchange.getIn().setBody(message.getObject());
+        Object body = message.getObject();
+        if (body != null) {
+            checkDeserializedType(body.getClass());
+        }
+        exchange.getIn().setBody(body);
         return exchange;
     }
 
+    private void checkDeserializedType(Class<?> type) {
+        ObjectInputFilter filter = resolvedDeserializationFilter;
+        if (filter == null) {
+            // the endpoint was not started, so resolve the filter on the fly rather than skipping the check
+            filter = resolveDeserializationFilter();
+            resolvedDeserializationFilter = filter;
+        }
+        if (filter.checkInput(new ClassOnlyFilterInfo(type)) == ObjectInputFilter.Status.REJECTED) {
+            throw new JGroupsException(
+                    "JGroups message deserialization blocked for class: " + type.getName()
+                                       + ". Configure the 'deserializationFilter' endpoint option or -Djdk.serialFilter"
+                                       + " to allow it.");
+        }
+    }
+
     public Exchange createExchange(View view) {
         Exchange exchange = createExchange();
         exchange.getIn().setBody(view);
@@ -103,6 +148,7 @@ public class JGroupsEndpoint extends DefaultEndpoint {
     protected void doStart() throws Exception {
         super.doStart();
         resolvedChannel = resolveChannel();
+        resolvedDeserializationFilter = resolveDeserializationFilter();
     }
 
     @Override
@@ -114,6 +160,56 @@ public class JGroupsEndpoint extends DefaultEndpoint {
         super.doStop();
     }
 
+    private ObjectInputFilter resolveDeserializationFilter() {
+        if (deserializationFilter != null && !deserializationFilter.isBlank()) {
+            return ObjectInputFilter.Config.createFilter(deserializationFilter);
+        }
+        ObjectInputFilter jvmFilter = ObjectInputFilter.Config.getSerialFilter();
+        if (jvmFilter != null) {
+            return jvmFilter;
+        }
+        LOG.debug("No JVM-wide deserialization filter set, applying default Camel filter: {}",
+                DEFAULT_CLASS_DESERIALIZATION_FILTER);
+        return ObjectInputFilter.Config.createFilter(DEFAULT_CLASS_DESERIALIZATION_FILTER);
+    }
+
+    /**
+     * Exposes the class of an already-deserialized object to an {@link ObjectInputFilter}. Only the class is known at
+     * this point, so the JEP-290 graph-shape metrics report neutral values.
+     */
+    private static final class ClassOnlyFilterInfo implements ObjectInputFilter.FilterInfo {
+        private final Class<?> clazz;
+
+        private ClassOnlyFilterInfo(Class<?> clazz) {
+            this.clazz = clazz;
+        }
+
+        @Override
+        public Class<?> serialClass() {
+            return clazz;
+        }
+
+        @Override
+        public long arrayLength() {
+            return -1;
+        }
+
+        @Override
+        public long depth() {
+            return 0;
+        }
+
+        @Override
+        public long references() {
+            return 0;
+        }
+
+        @Override
+        public long streamBytes() {
+            return 0;
+        }
+    }
+
     private JChannel resolveChannel() throws Exception {
         if (channel != null) {
             return channel;
@@ -192,4 +288,22 @@ public class JGroupsEndpoint extends DefaultEndpoint {
         this.enableViewMessages = enableViewMessages;
     }
 
+    public String getDeserializationFilter() {
+        return deserializationFilter;
+    }
+
+    /**
+     * Sets an {@link ObjectInputFilter} pattern (same syntax as {@code jdk.serialFilter}) applied as a defense-in-depth
+     * check on the class of the message body deserialized by {@code org.jgroups.Message#getObject()}. The pattern is
+     * evaluated after JGroups has deserialized the payload, so this option alone does not prevent gadget-chain
+     * execution that happens inside the JGroups receive path; to block such attacks, also configure the JVM-wide
+     * {@code -Djdk.serialFilter} and secure the channel with {@code AUTH} and encryption. When this option is not set
+     * and no JVM-wide filter is configured, a conservative default filter denying {@code java.net.**} and otherwise
+     * allowing {@code java.**}, {@code javax.**} and {@code org.apache.camel.**} is applied. Use {@code *} to accept
+     * any type.
+     */
+    public void setDeserializationFilter(String deserializationFilter) {
+        this.deserializationFilter = deserializationFilter;
+    }
+
 }
diff --git a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsException.java b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsException.java
index 0157ed621156..39571d2be064 100644
--- a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsException.java
+++ b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsException.java
@@ -20,6 +20,10 @@ public class JGroupsException extends RuntimeException {
 
     private static final long serialVersionUID = 1L;
 
+    public JGroupsException(String message) {
+        super(message);
+    }
+
     public JGroupsException(String message, Throwable cause) {
         super(message, cause);
     }
diff --git a/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/CapturingExceptionHandler.java b/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/CapturingExceptionHandler.java
new file mode 100644
index 000000000000..d6871f5053e6
--- /dev/null
+++ b/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/CapturingExceptionHandler.java
@@ -0,0 +1,50 @@
+/*
+ * 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.jgroups;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.spi.ExceptionHandler;
+
+/**
+ * {@link ExceptionHandler} capturing the exceptions handed to it, so that a test can assert what the consumer refused.
+ */
+public class CapturingExceptionHandler implements ExceptionHandler {
+
+    private final List<Throwable> exceptions = new CopyOnWriteArrayList<>();
+
+    public List<Throwable> getExceptions() {
+        return exceptions;
+    }
+
+    @Override
+    public void handleException(Throwable exception) {
+        exceptions.add(exception);
+    }
+
+    @Override
+    public void handleException(String message, Throwable exception) {
+        exceptions.add(exception);
+    }
+
+    @Override
+    public void handleException(String message, Exchange exchange, Throwable exception) {
+        exceptions.add(exception);
+    }
+}
diff --git a/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsDefaultDeserializationFilterTest.java b/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsDefaultDeserializationFilterTest.java
new file mode 100644
index 000000000000..eaac6bb8d1f1
--- /dev/null
+++ b/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsDefaultDeserializationFilterTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.jgroups;
+
+import java.net.URI;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.EndpointInject;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.jgroups.JChannel;
+import org.jgroups.ObjectMessage;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies the default deserialization filter applied when the {@code deserializationFilter} option is not configured:
+ * the shared Camel default allow-list denies {@code java.net.**}, and {@code deserializationFilter=*} opts out of the
+ * check.
+ */
+public class JGroupsDefaultDeserializationFilterTest extends CamelTestSupport {
+
+    String defaultFilterCluster = "defaultFilterCluster";
+
+    String noFilterCluster = "noFilterCluster";
+
+    JChannel defaultFilterChannel;
+
+    JChannel noFilterChannel;
+
+    @BindToRegistry("filterExceptionHandler")
+    CapturingExceptionHandler exceptionHandler = new CapturingExceptionHandler();
+
+    @EndpointInject("mock:default")
+    MockEndpoint defaultFilterMock;
+
+    @EndpointInject("mock:nofilter")
+    MockEndpoint noFilterMock;
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                // no deserializationFilter configured, so the shared Camel default allow-list applies
+                from("jgroups:" + defaultFilterCluster + "?exceptionHandler=#filterExceptionHandler")
+                        .to(defaultFilterMock);
+                // opt out of the class check
+                from("jgroups:" + noFilterCluster + "?deserializationFilter=*").to(noFilterMock);
+            }
+        };
+    }
+
+    @Override
+    protected void doPreSetup() throws Exception {
+        super.doPreSetup();
+        defaultFilterChannel = new JChannel();
+        defaultFilterChannel.connect(defaultFilterCluster);
+        noFilterChannel = new JChannel();
+        noFilterChannel.connect(noFilterCluster);
+    }
+
+    @Override
+    public void doPostTearDown() {
+        if (defaultFilterChannel != null) {
+            defaultFilterChannel.close();
+        }
+        if (noFilterChannel != null) {
+            noFilterChannel.close();
+        }
+    }
+
+    @Test
+    public void shouldRejectDeniedTypeWithDefaultFilter() throws Exception {
+        // java.net.** is denied by the default filter; the String sentinel sent afterwards is allowed and, thanks to
+        // per-sender FIFO ordering, proves the URI was refused before reaching the route
+        defaultFilterMock.setExpectedMessageCount(1);
+        defaultFilterMock.expectedBodiesReceived("sentinel");
+
+        defaultFilterChannel.send(new ObjectMessage(null, URI.create("http://localhost")));
+        defaultFilterChannel.send(new ObjectMessage(null, "sentinel"));
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        assertEquals(1, exceptionHandler.getExceptions().size(), "The URI should have been refused");
+        Throwable refused = exceptionHandler.getExceptions().get(0);
+        assertInstanceOf(JGroupsException.class, refused);
+        assertTrue(refused.getMessage().contains(URI.class.getName()),
+                "Should report the refused type, but was: " + refused.getMessage());
+    }
+
+    @Test
+    public void shouldAcceptAnyTypeWhenFilterIsDisabled() throws Exception {
+        noFilterMock.setExpectedMessageCount(1);
+        noFilterMock.expectedBodiesReceived(URI.create("http://localhost"));
+
+        noFilterChannel.send(new ObjectMessage(null, URI.create("http://localhost")));
+
+        MockEndpoint.assertIsSatisfied(context);
+        assertTrue(exceptionHandler.getExceptions().isEmpty(), "No message should have been refused");
+    }
+
+}
diff --git a/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsDeserializationFilterTest.java b/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsDeserializationFilterTest.java
new file mode 100644
index 000000000000..5171822707b9
--- /dev/null
+++ b/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsDeserializationFilterTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.jgroups;
+
+import java.util.Date;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.EndpointInject;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.jgroups.JChannel;
+import org.jgroups.ObjectMessage;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies that the consumer {@code deserializationFilter} option constrains the message body types accepted from the
+ * cluster: a type on the allow-list is routed, a type outside it is refused and reported to the consumer's exception
+ * handler.
+ */
+public class JGroupsDeserializationFilterTest extends CamelTestSupport {
+
+    String clusterName = "deserializationFilterCluster";
+
+    JChannel channel;
+
+    @BindToRegistry("filterExceptionHandler")
+    CapturingExceptionHandler exceptionHandler = new CapturingExceptionHandler();
+
+    @EndpointInject("mock:test")
+    MockEndpoint mockEndpoint;
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                // allow only String bodies, reject everything else
+                from("jgroups:" + clusterName
+                     + "?deserializationFilter=java.lang.String;!*&exceptionHandler=#filterExceptionHandler")
+                        .to(mockEndpoint);
+            }
+        };
+    }
+
+    @Override
+    protected void doPreSetup() throws Exception {
+        super.doPreSetup();
+        channel = new JChannel();
+        channel.connect(clusterName);
+    }
+
+    @Override
+    public void doPostTearDown() {
+        if (channel != null) {
+            channel.close();
+        }
+    }
+
+    @Test
+    public void shouldAcceptBodyAllowedByFilter() throws Exception {
+        mockEndpoint.setExpectedMessageCount(1);
+        mockEndpoint.expectedBodiesReceived("allowed");
+
+        channel.send(new ObjectMessage(null, "allowed"));
+
+        MockEndpoint.assertIsSatisfied(context);
+        assertTrue(exceptionHandler.getExceptions().isEmpty(), "No message should have been refused");
+    }
+
+    @Test
+    public void shouldRejectBodyDeniedByFilter() throws Exception {
+        // The Date sent first is not on the allow-list and must be dropped by the filter; the String sentinel sent
+        // afterwards is allowed. JGroups preserves per-sender FIFO ordering, so receiving only the sentinel proves the
+        // Date was refused before reaching the route.
+        mockEndpoint.setExpectedMessageCount(1);
+        mockEndpoint.expectedBodiesReceived("sentinel");
+
+        channel.send(new ObjectMessage(null, new Date()));
+        channel.send(new ObjectMessage(null, "sentinel"));
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        // the refused message is not silently dropped, but reported to the consumer's exception handler. The
+        // sentinel was sent after the Date and has already been routed, so the refusal has happened by now.
+        assertEquals(1, exceptionHandler.getExceptions().size(), "The Date should have been refused");
+        Throwable refused = exceptionHandler.getExceptions().get(0);
+        assertInstanceOf(JGroupsException.class, refused);
+        assertTrue(refused.getMessage().contains(Date.class.getName()),
+                "Should report the refused type, but was: " + refused.getMessage());
+    }
+
+}
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 855b6883e8ad..c8c83b5f4366 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
@@ -46,6 +46,33 @@ path segments before checking that the destination remains inside that directory
 symbolic link that resolves outside the configured directory are rejected. Valid object names using `/`
 as a pseudo-directory separator continue to work.
 
+
+=== camel-jgroups - the consumer applies a deserialization filter by default
+
+The consumer maps an incoming cluster message to the exchange body by calling
+`org.jgroups.Message.getObject()`, which Java-deserializes the payload. The class of that body is
+now checked against a JEP-290 `java.io.ObjectInputFilter`. Previously no filter was applied at all,
+and any type a cluster peer sent was routed.
+
+When no explicit pattern is configured, the JVM-wide `jdk.serialFilter` is honoured if set,
+otherwise a conservative default allow-list is applied
+(`!java.net.**;java.**;javax.**;org.apache.camel.**;!*`), matching the default the other components
+that can receive Java-serialized payloads have applied since 4.18.3. Routes that exchange their own
+classes over the cluster must widen it through the new `deserializationFilter` endpoint option, for
+example:
+
+[source,java]
+----
+from("jgroups:clusterName?deserializationFilter=com.example.model.**;java.**;!*")
+----
+
+Setting `deserializationFilter=*` accepts any type and so restores the previous behavior. A refused
+message is not routed; it is reported to the consumer's `ExceptionHandler`, which logs it at WARN
+level by default.
+
+Note that JGroups deserializes the payload inside its own receive path, so this check is a
+defense-in-depth allow-list on the resulting body type. The JVM-wide `jdk.serialFilter`, together
+with a channel secured with `AUTH` and encryption, remain the primary mitigations.
 == Upgrading from 4.18.3 to 4.18.4
 
 === camel-core - Multicast EIP honors UseOriginalAggregationStrategy
diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/JgroupsComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/JgroupsComponentBuilderFactory.java
index 5cca08852fe1..c4de4309db97 100644
--- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/JgroupsComponentBuilderFactory.java
+++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/JgroupsComponentBuilderFactory.java
@@ -174,6 +174,31 @@ public interface JgroupsComponentBuilderFactory {
             doSetProperty("autowiredEnabled", autowiredEnabled);
             return this;
         }
+    
+        /**
+         * Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied
+         * as a defense-in-depth check on the class of the message body
+         * deserialized by org.jgroups.Message.getObject(). The pattern is
+         * evaluated after JGroups has deserialized the payload, so this option
+         * alone does not prevent gadget-chain execution that happens inside the
+         * JGroups receive path; to block such attacks, also configure the
+         * JVM-wide -Djdk.serialFilter and secure the channel with AUTH and
+         * encryption. When this option is not set and no JVM-wide filter is
+         * configured, a conservative default filter denying java.net. and
+         * otherwise allowing java., javax. and org.apache.camel. is applied.
+         * Use to accept any type.
+         * 
+         * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
+         * 
+         * Group: security
+         * 
+         * @param deserializationFilter the value to set
+         * @return the dsl builder
+         */
+        default JgroupsComponentBuilder deserializationFilter(java.lang.String deserializationFilter) {
+            doSetProperty("deserializationFilter", deserializationFilter);
+            return this;
+        }
     }
 
     class JgroupsComponentBuilderImpl
@@ -195,6 +220,7 @@ public interface JgroupsComponentBuilderFactory {
             case "enableViewMessages": ((JGroupsComponent) component).setEnableViewMessages((boolean) value); return true;
             case "lazyStartProducer": ((JGroupsComponent) component).setLazyStartProducer((boolean) value); return true;
             case "autowiredEnabled": ((JGroupsComponent) component).setAutowiredEnabled((boolean) value); return true;
+            case "deserializationFilter": ((JGroupsComponent) component).setDeserializationFilter((java.lang.String) value); return true;
             default: return false;
             }
         }
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/JGroupsEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/JGroupsEndpointBuilderFactory.java
index aea6a7d318aa..d7e3576145ac 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/JGroupsEndpointBuilderFactory.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/JGroupsEndpointBuilderFactory.java
@@ -92,6 +92,30 @@ public interface JGroupsEndpointBuilderFactory {
             doSetProperty("enableViewMessages", enableViewMessages);
             return this;
         }
+        /**
+         * Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied
+         * as a defense-in-depth check on the class of the message body
+         * deserialized by org.jgroups.Message.getObject(). The pattern is
+         * evaluated after JGroups has deserialized the payload, so this option
+         * alone does not prevent gadget-chain execution that happens inside the
+         * JGroups receive path; to block such attacks, also configure the
+         * JVM-wide -Djdk.serialFilter and secure the channel with AUTH and
+         * encryption. When this option is not set and no JVM-wide filter is
+         * configured, a conservative default filter denying java.net. and
+         * otherwise allowing java., javax. and org.apache.camel. is applied.
+         * Use to accept any type.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: security
+         * 
+         * @param deserializationFilter the value to set
+         * @return the dsl builder
+         */
+        default JGroupsEndpointConsumerBuilder deserializationFilter(String deserializationFilter) {
+            doSetProperty("deserializationFilter", deserializationFilter);
+            return this;
+        }
     }
 
     /**