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 1d5d3e983c3a CAMEL-24576: dynamic-router - gate control message 
predicate
1d5d3e983c3a is described below

commit 1d5d3e983c3a3fb64b6bfe238d614726191ee977
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Sep 1 16:51:05 2026 +0200

    CAMEL-24576: dynamic-router - gate control message predicate
    
    Adds allowPredicateFromMessage on the dynamic-router-control endpoint,
    default false. Previously a control message could freely supply a
    predicate/expressionLanguage that got compiled into the channel's rule
    base -- untrusted input driving expression evaluation, unlike every
    other Camel opt-in-gated template/script source (allowTemplateFromHeader).
    When the gate is closed, control messages fall back to the endpoint's
    configured predicate instead of being rejected. Also fixes
    DynamicRouterRecipientListHelper to wire allowedSchemes into the
    RecipientList it builds, and makes subscribeFromHeaders fall back to
    endpoint configuration for subscription parameters the message omits.
    
    This changes a default: deployments relying on control messages
    supplying their own predicate must now set allowPredicateFromMessage=true
    or switch to predicateBean. Documented in the 4.23 upgrade guide.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    
    Closes #25992
---
 .../catalog/components/dynamic-router-control.json |   3 +-
 .../camel/catalog/components/dynamic-router.json   |   3 +-
 .../catalog/docs/dynamic-router-component.adoc     |  19 +++-
 .../docs/dynamic-router-control-component.adoc     |  31 +++++
 .../DynamicRouterControlEndpointConfigurer.java    |   6 +
 .../DynamicRouterControlEndpointUriFactory.java    |   3 +-
 .../routing/DynamicRouterEndpointConfigurer.java   |   6 +
 .../routing/DynamicRouterEndpointUriFactory.java   |   3 +-
 .../control/dynamic-router-control.json            |   3 +-
 .../dynamicrouter/routing/dynamic-router.json      |   3 +-
 .../src/main/docs/dynamic-router-component.adoc    |  19 +++-
 .../docs/dynamic-router-control-component.adoc     |  31 +++++
 ...ynamicRouterControlChannelSendDynamicAware.java |   9 ++
 .../control/DynamicRouterControlConfiguration.java |  33 ++++++
 .../control/DynamicRouterControlConstants.java     |  16 +++
 .../control/DynamicRouterControlProducer.java      |  70 +++++++++---
 .../routing/DynamicRouterConfiguration.java        |  28 +++++
 .../routing/DynamicRouterRecipientListHelper.java  |   1 +
 ...icRouterControlChannelSendDynamicAwareTest.java |  11 ++
 .../control/DynamicRouterControlProducerTest.java  | 126 +++++++++++++++++++++
 .../DynamicRouterBasicSynchronousIT.java           |   8 +-
 .../DynamicRouterDefaultAggregationIT.java         |   2 +-
 .../DynamicRouterSendDynamicAwareIT.java           |   3 +-
 .../DynamicRouterRecipientListHelperTest.java      |   2 +
 .../java/org/apache/camel/util/SecurityUtils.java  |   1 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  33 ++++++
 ...DynamicRouterControlEndpointBuilderFactory.java |  42 +++++++
 .../dsl/DynamicRouterEndpointBuilderFactory.java   |  17 +++
 28 files changed, 498 insertions(+), 34 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router-control.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router-control.json
index 00e34e99babf..a27bcb45d41a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router-control.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router-control.json
@@ -46,6 +46,7 @@
     "predicateBean": { "index": 5, "kind": "parameter", "displayName": 
"Predicate Bean", "group": "control", "label": "control", "required": false, 
"type": "object", "javaType": "org.apache.camel.Predicate", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "A Predicate instance in 
the registry." },
     "priority": { "index": 6, "kind": "parameter", "displayName": "Priority", 
"group": "control", "label": "control", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "The subscription 
priority." },
     "subscribeChannel": { "index": 7, "kind": "parameter", "displayName": 
"Subscribe Channel", "group": "control", "label": "control", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "The channel to 
subscribe to" },
-    "subscriptionId": { "index": 8, "kind": "parameter", "displayName": 
"Subscription Id", "group": "control", "label": "control", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "The subscription ID; if 
unspecified, one will be assigned and returned." }
+    "subscriptionId": { "index": 8, "kind": "parameter", "displayName": 
"Subscription Id", "group": "control", "label": "control", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "The subscription ID; if 
unspecified, one will be assigned and returned." },
+    "allowPredicateFromMessage": { "index": 9, "kind": "parameter", 
"displayName": "Allow Predicate From Message", "group": "security", "label": 
"control,security", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"security": "insecure:dev", "insecureValue": "true", "defaultValue": false, 
"configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "c [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router.json
index c360b9c180ed..2a056e382f8d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/dynamic-router.json
@@ -48,6 +48,7 @@
     "synchronous": { "index": 17, "kind": "parameter", "displayName": 
"Synchronous", "group": "common", "label": "common", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration", 
"configurationField": "configuration", "description": "Sets whether synchronous 
processing should be strictly used. When enabl [...]
     "timeout": { "index": 18, "kind": "parameter", "displayName": "Timeout", 
"group": "common", "label": "common", "required": false, "type": "integer", 
"javaType": "long", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": -1, "configurationClass": 
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration", 
"configurationField": "configuration", "description": "Sets a total timeout 
specified in millis, when using parallel processing. If the Dyna [...]
     "warnDroppedMessage": { "index": 19, "kind": "parameter", "displayName": 
"Warn Dropped Message", "group": "common", "label": "common", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, 
"configurationClass": 
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration", 
"configurationField": "configuration", "description": "Flag to log a warning if 
no predicates match for an exc [...]
-    "lazyStartProducer": { "index": 20, "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 produ [...]
+    "lazyStartProducer": { "index": 20, "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 produ [...]
+    "allowedSchemes": { "index": 21, "kind": "parameter", "displayName": 
"Allowed Schemes", "group": "security", "label": "advanced,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration", 
"configurationField": "configuration", "description": "Sets an optional 
comma-separated allow-list of component schemes t [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-component.adoc
index 826097176c52..1aa7f2b2d187 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-component.adoc
@@ -318,7 +318,18 @@ producerTemplate.sendBody("kafka://subscriptions", 
mapper.writeValueAsString(con
 ----
 
 In another module, additional routing will serve as a bridge to get the 
message from Kafka to the control channel of the
-Dynamic Router:
+Dynamic Router.  The subscriptions above carry their predicate in the control 
message, so the bridge has to opt in with
+`allowPredicateFromMessage=true`.
+
+[WARNING]
+====
+`allowPredicateFromMessage` lets whoever puts a message on the bridged 
transport choose both the expression language and
+the expression that the Dynamic Router compiles and evaluates, which means it 
decides what runs inside the Camel
+process.  Only enable it when the transport that feeds the control channel is 
trusted.  When it is not, have the
+participants subscribe with `predicateBean`, which selects a `Predicate` that 
the route author bound in the registry,
+and leave the flag at its default of `false`.  See
+xref:dynamic-router-control-component.adoc[the control component 
documentation] for the alternatives.
+====
 
 .Bridge from Kafka to the Dynamic Router control channel
 
@@ -330,7 +341,7 @@ Java::
 ----
 from("kafka:subscriptions")
     .unmarshal().json(DynamicRouterControlMessage.class)
-    .to("dynamic-router-control:subscribe");
+    .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
 ----
 
 XML::
@@ -342,7 +353,7 @@ XML::
   <unmarshal>
     <json library="Jackson" 
unmarshalType="org.apache.camel.component.dynamicrouter.control.DynamicRouterControlMessage"/>
   </unmarshal>
-  <to uri="dynamic-router-control:subscribe"/>
+  <to uri="dynamic-router-control:subscribe?allowPredicateFromMessage=true"/>
 </route>
 ----
 
@@ -360,6 +371,8 @@ YAML::
               unmarshalType: 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlMessage
         - to:
             uri: dynamic-router-control:subscribe
+            parameters:
+              allowPredicateFromMessage: true
 ----
 ====
 
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-control-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-control-component.adoc
index 6f7ee90aae83..ec0eeb535aa8 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-control-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dynamic-router-control-component.adoc
@@ -30,6 +30,37 @@ include::partial$component-endpoint-options.adoc[]
 include::partial$component-endpoint-headers.adoc[]
 // component options: END
 
+== Supplying the subscription predicate
+
+A subscription needs a predicate that decides which exchanges the participant 
receives. There are three ways to supply
+one, and they differ in who chooses it:
+
+* `predicateBean` -- the name of a `Predicate` bound in the registry. The 
route author decides which predicates exist,
+and the control message only selects one of them by name.
+* `predicate` and `expressionLanguage` as control endpoint URI parameters -- 
the route author writes the expression, and
+every subscription made through that endpoint uses it.
+* `predicate` and `expressionLanguage` carried in the control message, in its 
body or its headers -- the sender of the
+control message chooses both the expression language and the expression.
+
+The third form is disabled by default. The predicate is compiled and then 
evaluated against every exchange on the
+channel, so letting the control message choose both the language and the 
expression means the sender of that message
+decides what runs inside the Camel process. Set 
`allowPredicateFromMessage=true` on the control endpoint to enable it,
+and only do so when control messages can only come from a trusted source:
+
+[source,java]
+----
+from("kafka:subscriptions")
+    .unmarshal().json(DynamicRouterControlMessage.class)
+    .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
+----
+
+When `allowPredicateFromMessage` is `false`, which is the default, a control 
message that supplies a `predicate` or an
+`expressionLanguage` is rejected with an `IllegalArgumentException`. Prefer 
`predicateBean`, or configure the predicate
+on the endpoint, when control messages arrive from outside the application.
+
+Subscription parameters that the control message does not carry fall back to 
the values configured on the control
+endpoint, so a participant can send only the parameters that identify it and 
let the endpoint supply the rest.
+
 == Subscribing
 
 Subscribing can be achieved by using query parameters in the control endpoint 
URI, or by sending a
diff --git 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointConfigurer.java
 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointConfigurer.java
index 65978aadf7f5..6eaaebe11bff 100644
--- 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointConfigurer.java
+++ 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointConfigurer.java
@@ -23,6 +23,8 @@ public class DynamicRouterControlEndpointConfigurer extends 
PropertyConfigurerSu
     public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
         DynamicRouterControlEndpoint target = (DynamicRouterControlEndpoint) 
obj;
         switch (ignoreCase ? name.toLowerCase() : name) {
+        case "allowpredicatefrommessage":
+        case "allowPredicateFromMessage": 
target.getConfiguration().setAllowPredicateFromMessage(property(camelContext, 
boolean.class, value)); return true;
         case "destinationuri":
         case "destinationUri": 
target.getConfiguration().setDestinationUri(property(camelContext, 
java.lang.String.class, value)); return true;
         case "expressionlanguage":
@@ -44,6 +46,8 @@ public class DynamicRouterControlEndpointConfigurer extends 
PropertyConfigurerSu
     @Override
     public Class<?> getOptionType(String name, boolean ignoreCase) {
         switch (ignoreCase ? name.toLowerCase() : name) {
+        case "allowpredicatefrommessage":
+        case "allowPredicateFromMessage": return boolean.class;
         case "destinationuri":
         case "destinationUri": return java.lang.String.class;
         case "expressionlanguage":
@@ -66,6 +70,8 @@ public class DynamicRouterControlEndpointConfigurer extends 
PropertyConfigurerSu
     public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
         DynamicRouterControlEndpoint target = (DynamicRouterControlEndpoint) 
obj;
         switch (ignoreCase ? name.toLowerCase() : name) {
+        case "allowpredicatefrommessage":
+        case "allowPredicateFromMessage": return 
target.getConfiguration().isAllowPredicateFromMessage();
         case "destinationuri":
         case "destinationUri": return 
target.getConfiguration().getDestinationUri();
         case "expressionlanguage":
diff --git 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointUriFactory.java
 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointUriFactory.java
index 98313723392b..42772d430286 100644
--- 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointUriFactory.java
+++ 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlEndpointUriFactory.java
@@ -24,7 +24,8 @@ public class DynamicRouterControlEndpointUriFactory extends 
org.apache.camel.sup
     private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(9);
+        Set<String> props = new HashSet<>(10);
+        props.add("allowPredicateFromMessage");
         props.add("controlAction");
         props.add("destinationUri");
         props.add("expressionLanguage");
diff --git 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointConfigurer.java
 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointConfigurer.java
index d1a144748b73..968bbd8dd52d 100644
--- 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointConfigurer.java
+++ 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointConfigurer.java
@@ -31,6 +31,8 @@ public class DynamicRouterEndpointConfigurer extends 
PropertyConfigurerSupport i
         case "aggregationStrategyMethodAllowNull": 
target.getConfiguration().setAggregationStrategyMethodAllowNull(property(camelContext,
 boolean.class, value)); return true;
         case "aggregationstrategymethodname":
         case "aggregationStrategyMethodName": 
target.getConfiguration().setAggregationStrategyMethodName(property(camelContext,
 java.lang.String.class, value)); return true;
+        case "allowedschemes":
+        case "allowedSchemes": 
target.getConfiguration().setAllowedSchemes(property(camelContext, 
java.lang.String.class, value)); return true;
         case "cachesize":
         case "cacheSize": 
target.getConfiguration().setCacheSize(property(camelContext, int.class, 
value)); return true;
         case "executorservice":
@@ -75,6 +77,8 @@ public class DynamicRouterEndpointConfigurer extends 
PropertyConfigurerSupport i
         case "aggregationStrategyMethodAllowNull": return boolean.class;
         case "aggregationstrategymethodname":
         case "aggregationStrategyMethodName": return java.lang.String.class;
+        case "allowedschemes":
+        case "allowedSchemes": return java.lang.String.class;
         case "cachesize":
         case "cacheSize": return int.class;
         case "executorservice":
@@ -120,6 +124,8 @@ public class DynamicRouterEndpointConfigurer extends 
PropertyConfigurerSupport i
         case "aggregationStrategyMethodAllowNull": return 
target.getConfiguration().isAggregationStrategyMethodAllowNull();
         case "aggregationstrategymethodname":
         case "aggregationStrategyMethodName": return 
target.getConfiguration().getAggregationStrategyMethodName();
+        case "allowedschemes":
+        case "allowedSchemes": return 
target.getConfiguration().getAllowedSchemes();
         case "cachesize":
         case "cacheSize": return target.getConfiguration().getCacheSize();
         case "executorservice":
diff --git 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointUriFactory.java
 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointUriFactory.java
index a8542b69adce..908eea5e9efd 100644
--- 
a/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointUriFactory.java
+++ 
b/components/camel-dynamic-router/src/generated/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterEndpointUriFactory.java
@@ -24,11 +24,12 @@ public class DynamicRouterEndpointUriFactory extends 
org.apache.camel.support.co
     private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(21);
+        Set<String> props = new HashSet<>(22);
         props.add("aggregationStrategy");
         props.add("aggregationStrategyBean");
         props.add("aggregationStrategyMethodAllowNull");
         props.add("aggregationStrategyMethodName");
+        props.add("allowedSchemes");
         props.add("cacheSize");
         props.add("channel");
         props.add("executorService");
diff --git 
a/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/control/dynamic-router-control.json
 
b/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/control/dynamic-router-control.json
index 00e34e99babf..a27bcb45d41a 100644
--- 
a/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/control/dynamic-router-control.json
+++ 
b/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/control/dynamic-router-control.json
@@ -46,6 +46,7 @@
     "predicateBean": { "index": 5, "kind": "parameter", "displayName": 
"Predicate Bean", "group": "control", "label": "control", "required": false, 
"type": "object", "javaType": "org.apache.camel.Predicate", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "A Predicate instance in 
the registry." },
     "priority": { "index": 6, "kind": "parameter", "displayName": "Priority", 
"group": "control", "label": "control", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "The subscription 
priority." },
     "subscribeChannel": { "index": 7, "kind": "parameter", "displayName": 
"Subscribe Channel", "group": "control", "label": "control", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "The channel to 
subscribe to" },
-    "subscriptionId": { "index": 8, "kind": "parameter", "displayName": 
"Subscription Id", "group": "control", "label": "control", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "The subscription ID; if 
unspecified, one will be assigned and returned." }
+    "subscriptionId": { "index": 8, "kind": "parameter", "displayName": 
"Subscription Id", "group": "control", "label": "control", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "configuration", "description": "The subscription ID; if 
unspecified, one will be assigned and returned." },
+    "allowPredicateFromMessage": { "index": 9, "kind": "parameter", 
"displayName": "Allow Predicate From Message", "group": "security", "label": 
"control,security", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"security": "insecure:dev", "insecureValue": "true", "defaultValue": false, 
"configurationClass": 
"org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConfiguration",
 "configurationField": "c [...]
   }
 }
diff --git 
a/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/routing/dynamic-router.json
 
b/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/routing/dynamic-router.json
index c360b9c180ed..2a056e382f8d 100644
--- 
a/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/routing/dynamic-router.json
+++ 
b/components/camel-dynamic-router/src/generated/resources/META-INF/org/apache/camel/component/dynamicrouter/routing/dynamic-router.json
@@ -48,6 +48,7 @@
     "synchronous": { "index": 17, "kind": "parameter", "displayName": 
"Synchronous", "group": "common", "label": "common", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration", 
"configurationField": "configuration", "description": "Sets whether synchronous 
processing should be strictly used. When enabl [...]
     "timeout": { "index": 18, "kind": "parameter", "displayName": "Timeout", 
"group": "common", "label": "common", "required": false, "type": "integer", 
"javaType": "long", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": -1, "configurationClass": 
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration", 
"configurationField": "configuration", "description": "Sets a total timeout 
specified in millis, when using parallel processing. If the Dyna [...]
     "warnDroppedMessage": { "index": 19, "kind": "parameter", "displayName": 
"Warn Dropped Message", "group": "common", "label": "common", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, 
"configurationClass": 
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration", 
"configurationField": "configuration", "description": "Flag to log a warning if 
no predicates match for an exc [...]
-    "lazyStartProducer": { "index": 20, "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 produ [...]
+    "lazyStartProducer": { "index": 20, "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 produ [...]
+    "allowedSchemes": { "index": 21, "kind": "parameter", "displayName": 
"Allowed Schemes", "group": "security", "label": "advanced,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.dynamicrouter.routing.DynamicRouterConfiguration", 
"configurationField": "configuration", "description": "Sets an optional 
comma-separated allow-list of component schemes t [...]
   }
 }
diff --git 
a/components/camel-dynamic-router/src/main/docs/dynamic-router-component.adoc 
b/components/camel-dynamic-router/src/main/docs/dynamic-router-component.adoc
index 826097176c52..1aa7f2b2d187 100644
--- 
a/components/camel-dynamic-router/src/main/docs/dynamic-router-component.adoc
+++ 
b/components/camel-dynamic-router/src/main/docs/dynamic-router-component.adoc
@@ -318,7 +318,18 @@ producerTemplate.sendBody("kafka://subscriptions", 
mapper.writeValueAsString(con
 ----
 
 In another module, additional routing will serve as a bridge to get the 
message from Kafka to the control channel of the
-Dynamic Router:
+Dynamic Router.  The subscriptions above carry their predicate in the control 
message, so the bridge has to opt in with
+`allowPredicateFromMessage=true`.
+
+[WARNING]
+====
+`allowPredicateFromMessage` lets whoever puts a message on the bridged 
transport choose both the expression language and
+the expression that the Dynamic Router compiles and evaluates, which means it 
decides what runs inside the Camel
+process.  Only enable it when the transport that feeds the control channel is 
trusted.  When it is not, have the
+participants subscribe with `predicateBean`, which selects a `Predicate` that 
the route author bound in the registry,
+and leave the flag at its default of `false`.  See
+xref:dynamic-router-control-component.adoc[the control component 
documentation] for the alternatives.
+====
 
 .Bridge from Kafka to the Dynamic Router control channel
 
@@ -330,7 +341,7 @@ Java::
 ----
 from("kafka:subscriptions")
     .unmarshal().json(DynamicRouterControlMessage.class)
-    .to("dynamic-router-control:subscribe");
+    .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
 ----
 
 XML::
@@ -342,7 +353,7 @@ XML::
   <unmarshal>
     <json library="Jackson" 
unmarshalType="org.apache.camel.component.dynamicrouter.control.DynamicRouterControlMessage"/>
   </unmarshal>
-  <to uri="dynamic-router-control:subscribe"/>
+  <to uri="dynamic-router-control:subscribe?allowPredicateFromMessage=true"/>
 </route>
 ----
 
@@ -360,6 +371,8 @@ YAML::
               unmarshalType: 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlMessage
         - to:
             uri: dynamic-router-control:subscribe
+            parameters:
+              allowPredicateFromMessage: true
 ----
 ====
 
diff --git 
a/components/camel-dynamic-router/src/main/docs/dynamic-router-control-component.adoc
 
b/components/camel-dynamic-router/src/main/docs/dynamic-router-control-component.adoc
index 6f7ee90aae83..ec0eeb535aa8 100644
--- 
a/components/camel-dynamic-router/src/main/docs/dynamic-router-control-component.adoc
+++ 
b/components/camel-dynamic-router/src/main/docs/dynamic-router-control-component.adoc
@@ -30,6 +30,37 @@ include::partial$component-endpoint-options.adoc[]
 include::partial$component-endpoint-headers.adoc[]
 // component options: END
 
+== Supplying the subscription predicate
+
+A subscription needs a predicate that decides which exchanges the participant 
receives. There are three ways to supply
+one, and they differ in who chooses it:
+
+* `predicateBean` -- the name of a `Predicate` bound in the registry. The 
route author decides which predicates exist,
+and the control message only selects one of them by name.
+* `predicate` and `expressionLanguage` as control endpoint URI parameters -- 
the route author writes the expression, and
+every subscription made through that endpoint uses it.
+* `predicate` and `expressionLanguage` carried in the control message, in its 
body or its headers -- the sender of the
+control message chooses both the expression language and the expression.
+
+The third form is disabled by default. The predicate is compiled and then 
evaluated against every exchange on the
+channel, so letting the control message choose both the language and the 
expression means the sender of that message
+decides what runs inside the Camel process. Set 
`allowPredicateFromMessage=true` on the control endpoint to enable it,
+and only do so when control messages can only come from a trusted source:
+
+[source,java]
+----
+from("kafka:subscriptions")
+    .unmarshal().json(DynamicRouterControlMessage.class)
+    .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
+----
+
+When `allowPredicateFromMessage` is `false`, which is the default, a control 
message that supplies a `predicate` or an
+`expressionLanguage` is rejected with an `IllegalArgumentException`. Prefer 
`predicateBean`, or configure the predicate
+on the endpoint, when control messages arrive from outside the application.
+
+Subscription parameters that the control message does not carry fall back to 
the values configured on the control
+endpoint, so a participant can send only the parameters that identify it and 
let the endpoint supply the rest.
+
 == Subscribing
 
 Subscribing can be achieved by using query parameters in the control endpoint 
URI, or by sending a
diff --git 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAware.java
 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAware.java
index 13662241b271..3e5d0a79731d 100644
--- 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAware.java
+++ 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAware.java
@@ -28,6 +28,7 @@ import org.apache.camel.spi.annotations.SendDynamic;
 import org.apache.camel.support.component.SendDynamicAwareSupport;
 import org.apache.camel.util.URISupport;
 
+import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.ALLOW_PREDICATE_FROM_MESSAGE_PROPERTY;
 import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.COMPONENT_SCHEME_CONTROL;
 import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_ACTION_PROPERTY;
 
@@ -95,6 +96,14 @@ public class DynamicRouterControlChannelSendDynamicAware 
extends SendDynamicAwar
         String uri = entry.getUri();
         if (DynamicRouterControlConstants.SHOULD_OPTIMIZE.test(uri)) {
             optimizedUri = URISupport.stripQuery(uri);
+            // The subscription parameters are carried as headers, but 
"allowPredicateFromMessage" decides whether a
+            // control message may supply the predicate at all, so it has to 
stay on the endpoint, where only the
+            // route author can set it. Stripping it here would make the 
endpoint fall back to the default of false.
+            Object allowPredicateFromMessage = 
entry.getProperties().get(ALLOW_PREDICATE_FROM_MESSAGE_PROPERTY);
+            if (allowPredicateFromMessage != null) {
+                optimizedUri = optimizedUri + "?" + 
ALLOW_PREDICATE_FROM_MESSAGE_PROPERTY + "="
+                               + allowPredicateFromMessage;
+            }
         }
         return optimizedUri;
     }
diff --git 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConfiguration.java
 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConfiguration.java
index 27a02fbe1684..875820f8d7be 100644
--- 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConfiguration.java
+++ 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConfiguration.java
@@ -80,6 +80,19 @@ public class DynamicRouterControlConfiguration {
     @UriParam(label = "control", defaultValue = "simple", description = "The 
subscription predicate language.")
     private String expressionLanguage = "simple";
 
+    /**
+     * Whether the subscription predicate, and the language used to compile 
it, may be taken from the incoming control
+     * message.
+     */
+    @UriParam(label = "control,security", defaultValue = "false", security = 
"insecure:dev", insecureValue = "true",
+              description = "Whether the subscription predicate, and the 
language used to compile it, may be taken"
+                            + " from the incoming control message. When 
disabled, the predicate and expressionLanguage"
+                            + " configured on this endpoint are used instead. 
Enabling this lets the sender of a"
+                            + " control message choose both the expression 
language and the expression that the"
+                            + " Dynamic Router compiles into a live predicate, 
so only enable it when control messages"
+                            + " come from a trusted source.")
+    private boolean allowPredicateFromMessage;
+
     /**
      * The control action (subscribe or unsubscribe).
      *
@@ -237,6 +250,26 @@ public class DynamicRouterControlConfiguration {
         this.expressionLanguage = expressionLanguage;
     }
 
+    /**
+     * Whether the subscription predicate, and the language used to compile 
it, may be taken from the incoming control
+     * message.
+     *
+     * @return true if the control message may supply the predicate
+     */
+    public boolean isAllowPredicateFromMessage() {
+        return allowPredicateFromMessage;
+    }
+
+    /**
+     * Whether the subscription predicate, and the language used to compile 
it, may be taken from the incoming control
+     * message.
+     *
+     * @param allowPredicateFromMessage true if the control message may supply 
the predicate
+     */
+    public void setAllowPredicateFromMessage(final boolean 
allowPredicateFromMessage) {
+        this.allowPredicateFromMessage = allowPredicateFromMessage;
+    }
+
     public DynamicRouterControlConfiguration() {
         // Default constructor is empty because we need an instance, and
         // then things can be populated through mutator methods.
diff --git 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConstants.java
 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConstants.java
index c5516973453d..196cb80f8174 100644
--- 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConstants.java
+++ 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlConstants.java
@@ -124,6 +124,15 @@ public final class DynamicRouterControlConstants {
      */
     public static final String ERROR_NO_PREDICATE_BEAN_FOUND = "Predicate bean 
could not be found";
 
+    /**
+     * Error when a control message supplies a predicate expression, or the 
language to compile it with, but the control
+     * endpoint does not allow those to be taken from a control message.
+     */
+    public static final String ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED
+            = "The subscription predicate expression, and the language used to 
compile it, cannot be taken from a "
+              + "control message. Configure 'predicate' and 
'expressionLanguage' on the dynamic-router-control "
+              + "endpoint, or set 'allowPredicateFromMessage=true' to let the 
control message supply them.";
+
     /**
      * The configuration property for the control channel action.
      */
@@ -164,6 +173,13 @@ public final class DynamicRouterControlConstants {
      */
     public static final String EXPRESSION_LANGUAGE_PROPERTY = 
"expressionLanguage";
 
+    /**
+     * The configuration property that controls whether the predicate may be 
taken from a control message. Unlike the
+     * other properties, this one is deliberately absent from {@link 
#URI_PARAMS_TO_HEADER_NAMES}: it must stay on the
+     * endpoint, where only the route author can set it, and must never be 
settable from a message.
+     */
+    public static final String ALLOW_PREDICATE_FROM_MESSAGE_PROPERTY = 
"allowPredicateFromMessage";
+
     /**
      * Header name for the control action.
      */
diff --git 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducer.java
 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducer.java
index 178cd0903390..658969e6af3d 100644
--- 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducer.java
+++ 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducer.java
@@ -42,6 +42,7 @@ import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterCont
 import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_SUBSCRIBE_CHANNEL;
 import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_SUBSCRIPTION_ID;
 import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.ERROR_NO_PREDICATE_BEAN_FOUND;
+import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED;
 import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.SIMPLE_LANGUAGE;
 
 /**
@@ -74,29 +75,57 @@ public class DynamicRouterControlProducer extends 
HeaderSelectorProducer {
         this.configuration = configuration;
     }
 
+    /**
+     * Returns the value to use for a predicate parameter that a control 
message may try to supply. Unless the endpoint
+     * explicitly allows the predicate to come from the message, a 
message-supplied value is rejected, and the value
+     * configured on the endpoint is used instead. Without this, the sender of 
a control message would choose both the
+     * expression language and the expression that the Dynamic Router compiles 
into a live predicate.
+     *
+     * @param  allowFromMessage whether the endpoint allows the predicate to 
come from the control message
+     * @param  messageValue     the value found in the control message, if any
+     * @param  configValue      the value configured on the endpoint
+     * @return                  the value to use
+     */
+    static String predicateValueToUse(
+            final boolean allowFromMessage, final String messageValue, final 
String configValue) {
+        if (allowFromMessage) {
+            return messageValue;
+        }
+        if (ObjectHelper.isNotEmpty(messageValue)) {
+            throw new 
IllegalArgumentException(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED);
+        }
+        return configValue;
+    }
+
     /**
      * Create a filter from parameters in the message body.
      *
      * @param  dynamicRouterControlService the {@link 
DynamicRouterControlService}
+     * @param  configuration               the configuration for the Dynamic 
Router Control
      * @param  message                     the message, where the body 
contains a control message
      * @param  update                      whether to update an existing 
filter (true) or add a new one (false)
      * @return                             the ID of the added filter
      */
     static String subscribeFromMessage(
             final DynamicRouterControlService dynamicRouterControlService,
+            final DynamicRouterControlConfiguration configuration,
             final Message message, final boolean update) {
         DynamicRouterControlMessage messageBody = 
message.getBody(DynamicRouterControlMessage.class);
         String subscriptionId = messageBody.getSubscriptionId();
         String subscribeChannel = messageBody.getSubscribeChannel();
         String destinationUri = messageBody.getDestinationUri();
         String priority = String.valueOf(messageBody.getPriority());
-        String predicate = messageBody.getPredicate();
         String predicateBean = messageBody.getPredicateBean();
-        String expressionLanguage = messageBody.getExpressionLanguage();
         if (ObjectHelper.isNotEmpty(predicateBean)) {
             return 
dynamicRouterControlService.subscribeWithPredicateBean(subscribeChannel, 
subscriptionId,
                     destinationUri, Integer.parseInt(priority), predicateBean, 
update);
-        } else if (ObjectHelper.isNotEmpty(predicate) && 
ObjectHelper.isNotEmpty(expressionLanguage)) {
+        }
+        boolean allowFromMessage = configuration.isAllowPredicateFromMessage();
+        String predicate = predicateValueToUse(allowFromMessage, 
messageBody.getPredicate(),
+                configuration.getPredicate());
+        String expressionLanguage = predicateValueToUse(allowFromMessage, 
messageBody.getExpressionLanguage(),
+                configuration.getExpressionLanguage());
+        if (ObjectHelper.isNotEmpty(predicate) && 
ObjectHelper.isNotEmpty(expressionLanguage)) {
             return 
dynamicRouterControlService.subscribeWithPredicateExpression(subscribeChannel, 
subscriptionId,
                     destinationUri, Integer.parseInt(priority), predicate, 
expressionLanguage, update);
         } else {
@@ -105,29 +134,38 @@ public class DynamicRouterControlProducer extends 
HeaderSelectorProducer {
     }
 
     /**
-     * Create a filter from parameters in message headers.
+     * Create a filter from parameters in message headers, falling back to the 
values configured on the endpoint for any
+     * parameter that the message does not carry.
      *
      * @param  dynamicRouterControlService the {@link 
DynamicRouterControlService}
+     * @param  configuration               the configuration for the Dynamic 
Router Control
      * @param  message                     the message, where the headers 
contain subscription params
      * @param  update                      whether to update an existing 
filter (true) or add a new one (false)
      * @return                             the ID of the added filter
      */
     static String subscribeFromHeaders(
             final DynamicRouterControlService dynamicRouterControlService,
+            final DynamicRouterControlConfiguration configuration,
             final Message message, final boolean update) {
         Map<String, Object> headers = message.getHeaders();
-        String subscriptionId = (String) headers.get(CONTROL_SUBSCRIPTION_ID);
-        String subscribeChannel = (String) 
headers.get(CONTROL_SUBSCRIBE_CHANNEL);
-        String destinationUri = (String) headers.get(CONTROL_DESTINATION_URI);
-        String priority = String.valueOf(headers.get(CONTROL_PRIORITY));
-        String predicate = (String) headers.get(CONTROL_PREDICATE);
+        String subscriptionId = (String) 
headers.getOrDefault(CONTROL_SUBSCRIPTION_ID, 
configuration.getSubscriptionId());
+        String subscribeChannel
+                = (String) headers.getOrDefault(CONTROL_SUBSCRIBE_CHANNEL, 
configuration.getSubscribeChannel());
+        String destinationUri = (String) 
headers.getOrDefault(CONTROL_DESTINATION_URI, 
configuration.getDestinationUri());
+        String priority = 
String.valueOf(headers.getOrDefault(CONTROL_PRIORITY, 
configuration.getPriority()));
         String predicateBean = (String) headers.get(CONTROL_PREDICATE_BEAN);
-        String expressionLanguage = Optional.ofNullable((String) 
headers.get(CONTROL_EXPRESSION_LANGUAGE))
-                .orElse(SIMPLE_LANGUAGE);
         if (ObjectHelper.isNotEmpty(predicateBean)) {
             return 
dynamicRouterControlService.subscribeWithPredicateBean(subscribeChannel, 
subscriptionId,
                     destinationUri, Integer.parseInt(priority), predicateBean, 
update);
-        } else if (ObjectHelper.isNotEmpty(predicate) && 
ObjectHelper.isNotEmpty(expressionLanguage)) {
+        }
+        boolean allowFromMessage = configuration.isAllowPredicateFromMessage();
+        String predicate = predicateValueToUse(allowFromMessage, (String) 
headers.get(CONTROL_PREDICATE),
+                configuration.getPredicate());
+        String expressionLanguage = Optional
+                .ofNullable(predicateValueToUse(allowFromMessage, (String) 
headers.get(CONTROL_EXPRESSION_LANGUAGE),
+                        configuration.getExpressionLanguage()))
+                .orElse(SIMPLE_LANGUAGE);
+        if (ObjectHelper.isNotEmpty(predicate) && 
ObjectHelper.isNotEmpty(expressionLanguage)) {
             return 
dynamicRouterControlService.subscribeWithPredicateExpression(subscribeChannel, 
subscriptionId,
                     destinationUri, Integer.parseInt(priority), predicate, 
expressionLanguage, update);
         } else {
@@ -147,9 +185,9 @@ public class DynamicRouterControlProducer extends 
HeaderSelectorProducer {
     public void performSubscribe(final Message message, AsyncCallback 
callback) {
         String filterId;
         if (message.getBody() instanceof DynamicRouterControlMessage) {
-            filterId = subscribeFromMessage(dynamicRouterControlService, 
message, false);
+            filterId = subscribeFromMessage(dynamicRouterControlService, 
configuration, message, false);
         } else {
-            filterId = subscribeFromHeaders(dynamicRouterControlService, 
message, false);
+            filterId = subscribeFromHeaders(dynamicRouterControlService, 
configuration, message, false);
         }
         message.setBody(filterId);
         callback.done(false);
@@ -190,9 +228,9 @@ public class DynamicRouterControlProducer extends 
HeaderSelectorProducer {
     public void performUpdate(final Message message, AsyncCallback callback) {
         String filterId;
         if (message.getBody() instanceof DynamicRouterControlMessage) {
-            filterId = subscribeFromMessage(dynamicRouterControlService, 
message, true);
+            filterId = subscribeFromMessage(dynamicRouterControlService, 
configuration, message, true);
         } else {
-            filterId = subscribeFromHeaders(dynamicRouterControlService, 
message, true);
+            filterId = subscribeFromHeaders(dynamicRouterControlService, 
configuration, message, true);
         }
         message.setBody(filterId);
         callback.done(false);
diff --git 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterConfiguration.java
 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterConfiguration.java
index e8d67b232cb8..a2b0bd203d29 100644
--- 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterConfiguration.java
+++ 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterConfiguration.java
@@ -103,6 +103,16 @@ public class DynamicRouterConfiguration {
     @UriParam(label = "common", defaultValue = "false")
     private boolean ignoreInvalidEndpoints;
 
+    /**
+     * An optional comma-separated allow-list of component schemes that a 
subscription destination may resolve to.
+     */
+    @UriParam(label = "advanced,security",
+              description = "Sets an optional comma-separated allow-list of 
component schemes that a subscription"
+                            + " destination may resolve to (e.g. http,https). 
When set, a subscription whose"
+                            + " destination URI uses a scheme that is not in 
the list is rejected. By default (unset)"
+                            + " any scheme is allowed.")
+    private String allowedSchemes;
+
     /**
      * If enabled, then Camel will process replies out-of-order (e.g., in the 
order they come back). If disabled, Camel
      * will process replies in the same order as defined by the multicast.
@@ -315,6 +325,24 @@ public class DynamicRouterConfiguration {
         this.ignoreInvalidEndpoints = ignoreInvalidEndpoints;
     }
 
+    /**
+     * An optional comma-separated allow-list of component schemes that a 
subscription destination may resolve to.
+     *
+     * @return the allowed schemes, or null if any scheme is allowed
+     */
+    public String getAllowedSchemes() {
+        return allowedSchemes;
+    }
+
+    /**
+     * An optional comma-separated allow-list of component schemes that a 
subscription destination may resolve to.
+     *
+     * @param allowedSchemes the allowed schemes
+     */
+    public void setAllowedSchemes(String allowedSchemes) {
+        this.allowedSchemes = allowedSchemes;
+    }
+
     /**
      * Gets whether parallel processing is enabled.
      *
diff --git 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
index 414a16814e07..31bb6e2de5a1 100644
--- 
a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
+++ 
b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
@@ -124,6 +124,7 @@ public final class DynamicRouterRecipientListHelper {
         recipientList.setShareUnitOfWork(cfg.isShareUnitOfWork());
         recipientList.setStopOnException(cfg.isStopOnException());
         
recipientList.setIgnoreInvalidEndpoints(cfg.isIgnoreInvalidEndpoints());
+        recipientList.setAllowedSchemes(cfg.getAllowedSchemes());
         recipientList.setCacheSize(cfg.getCacheSize());
         if (cfg.getOnPrepare() != null) {
             recipientList.setOnPrepare(mandatoryLookup(camelContext, 
cfg.getOnPrepare(), Processor.class));
diff --git 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAwareTest.java
 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAwareTest.java
index 634d942297b5..deb7e48b812d 100644
--- 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAwareTest.java
+++ 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlChannelSendDynamicAwareTest.java
@@ -68,6 +68,17 @@ class DynamicRouterControlChannelSendDynamicAwareTest {
         }
     }
 
+    @Test
+    void resolveStaticUriRetainsAllowPredicateFromMessage() throws Exception {
+        String originalUri = 
"dynamic-router-control:subscribe?subscriptionId=testSub1&allowPredicateFromMessage=true";
+        String uri = 
"dynamic-router-control://subscribe?subscriptionId=testSub1&allowPredicateFromMessage=true";
+        try (DynamicRouterControlChannelSendDynamicAware testSubject = new 
DynamicRouterControlChannelSendDynamicAware()) {
+            SendDynamicAware.DynamicAwareEntry entry = 
testSubject.prepare(exchange, uri, originalUri);
+            String result = testSubject.resolveStaticUri(exchange, entry);
+            
assertEquals("dynamic-router-control://subscribe?allowPredicateFromMessage=true",
 result);
+        }
+    }
+
     @Test
     void resolveStaticUriShouldNotOptimize() throws Exception {
         String originalUri = 
"dynamic-router-ctrl:subscribe?subscriptionId=testSub1";
diff --git 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerTest.java
 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerTest.java
index f86a48d55575..fad05c24c670 100644
--- 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerTest.java
+++ 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/control/DynamicRouterControlProducerTest.java
@@ -48,6 +48,7 @@ import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterCont
 import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_PRIORITY;
 import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_SUBSCRIBE_CHANNEL;
 import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.CONTROL_SUBSCRIPTION_ID;
+import static 
org.apache.camel.component.dynamicrouter.control.DynamicRouterControlConstants.ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.when;
@@ -89,6 +90,7 @@ class DynamicRouterControlProducerTest {
 
     @Test
     void performSubscribeAction() {
+        when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
         String subscribeChannel = "testChannel";
         Map<String, Object> headers = Map.of(
                 CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
@@ -107,6 +109,7 @@ class DynamicRouterControlProducerTest {
 
     @Test
     void performSubscribeActionWithEmptyExpressionLanguage() {
+        when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
         String subscribeChannel = "testChannel";
         Map<String, Object> headers = Map.of(
                 CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
@@ -155,6 +158,7 @@ class DynamicRouterControlProducerTest {
 
     @Test
     void performUpdateAction() {
+        when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
         // First, perform initial subscription
         String subscribeChannel = "testChannel";
         Map<String, Object> headers = Map.of(
@@ -229,6 +233,7 @@ class DynamicRouterControlProducerTest {
 
     @Test
     void performSubscribeActionWithMessageInBody() {
+        when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
         String subscribeChannel = "testChannel";
         DynamicRouterControlMessage subMsg = 
DynamicRouterControlMessage.Builder.newBuilder()
                 .subscribeChannel(subscribeChannel)
@@ -249,6 +254,7 @@ class DynamicRouterControlProducerTest {
 
     @Test
     void performSubscribeActionWithMessageInBodyWithEmptyExpressionLanguage() {
+        when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
         String subscribeChannel = "testChannel";
         DynamicRouterControlMessage subMsg = 
DynamicRouterControlMessage.Builder.newBuilder()
                 .subscribeChannel(subscribeChannel)
@@ -266,6 +272,7 @@ class DynamicRouterControlProducerTest {
 
     @Test
     void performSubscribeActionWithMessageInBodyWithEmptyExpression() {
+        when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
         String subscribeChannel = "testChannel";
         DynamicRouterControlMessage subMsg = 
DynamicRouterControlMessage.Builder.newBuilder()
                 .subscribeChannel(subscribeChannel)
@@ -302,6 +309,7 @@ class DynamicRouterControlProducerTest {
 
     @Test
     void performUpdateActionWithMessageInBody() {
+        when(configuration.isAllowPredicateFromMessage()).thenReturn(true);
         String subscribeChannel = "testChannel";
         DynamicRouterControlMessage subMsg = 
DynamicRouterControlMessage.Builder.newBuilder()
                 .subscribeChannel(subscribeChannel)
@@ -320,6 +328,124 @@ class DynamicRouterControlProducerTest {
                         subscribeChannel, "testId", "mock://test", 10, "true", 
"simple", true);
     }
 
+    @Test
+    void performSubscribeActionRejectsPredicateFromHeadersByDefault() {
+        Map<String, Object> headers = Map.of(
+                CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
+                CONTROL_SUBSCRIBE_CHANNEL, "testChannel",
+                CONTROL_SUBSCRIPTION_ID, "testId",
+                CONTROL_DESTINATION_URI, "mock://test",
+                CONTROL_PREDICATE, "true",
+                CONTROL_EXPRESSION_LANGUAGE, "simple",
+                CONTROL_PRIORITY, 10);
+        when(message.getHeaders()).thenReturn(headers);
+        Exception ex = assertThrows(IllegalArgumentException.class, () -> 
producer.performSubscribe(message, callback));
+        assertEquals(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED, 
ex.getMessage());
+        Mockito.verifyNoInteractions(controlService);
+    }
+
+    @Test
+    void performSubscribeActionRejectsExpressionLanguageFromHeadersByDefault() 
{
+        Map<String, Object> headers = Map.of(
+                CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
+                CONTROL_SUBSCRIBE_CHANNEL, "testChannel",
+                CONTROL_SUBSCRIPTION_ID, "testId",
+                CONTROL_DESTINATION_URI, "mock://test",
+                CONTROL_EXPRESSION_LANGUAGE, "simple",
+                CONTROL_PRIORITY, 10);
+        when(message.getHeaders()).thenReturn(headers);
+        Exception ex = assertThrows(IllegalArgumentException.class, () -> 
producer.performSubscribe(message, callback));
+        assertEquals(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED, 
ex.getMessage());
+        Mockito.verifyNoInteractions(controlService);
+    }
+
+    @Test
+    void performSubscribeActionRejectsPredicateFromMessageBodyByDefault() {
+        DynamicRouterControlMessage subMsg = 
DynamicRouterControlMessage.Builder.newBuilder()
+                .subscribeChannel("testChannel")
+                .subscriptionId("testId")
+                .destinationUri("mock://test")
+                .priority(10)
+                .predicate("true")
+                .expressionLanguage("simple")
+                .build();
+        when(message.getBody()).thenReturn(subMsg);
+        
when(message.getBody(DynamicRouterControlMessage.class)).thenReturn(subMsg);
+        Exception ex = assertThrows(IllegalArgumentException.class, () -> 
producer.performSubscribe(message, callback));
+        assertEquals(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED, 
ex.getMessage());
+        Mockito.verifyNoInteractions(controlService);
+    }
+
+    @Test
+    void performUpdateActionRejectsPredicateFromMessageBodyByDefault() {
+        DynamicRouterControlMessage subMsg = 
DynamicRouterControlMessage.Builder.newBuilder()
+                .subscribeChannel("testChannel")
+                .subscriptionId("testId")
+                .destinationUri("mock://test")
+                .priority(10)
+                .predicate("true")
+                .expressionLanguage("simple")
+                .build();
+        when(message.getBody()).thenReturn(subMsg);
+        
when(message.getBody(DynamicRouterControlMessage.class)).thenReturn(subMsg);
+        Exception ex = assertThrows(IllegalArgumentException.class, () -> 
producer.performUpdate(message, callback));
+        assertEquals(ERROR_PREDICATE_FROM_MESSAGE_NOT_ALLOWED, 
ex.getMessage());
+        Mockito.verifyNoInteractions(controlService);
+    }
+
+    @Test
+    void performSubscribeActionUsesEndpointPredicateWhenMessageSuppliesNone() {
+        String subscribeChannel = "testChannel";
+        Map<String, Object> headers = Map.of(
+                CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
+                CONTROL_SUBSCRIBE_CHANNEL, subscribeChannel,
+                CONTROL_SUBSCRIPTION_ID, "testId",
+                CONTROL_DESTINATION_URI, "mock://test",
+                CONTROL_PRIORITY, 10);
+        when(message.getHeaders()).thenReturn(headers);
+        when(configuration.getPredicate()).thenReturn("true");
+        when(configuration.getExpressionLanguage()).thenReturn("simple");
+        Mockito.doNothing().when(callback).done(false);
+        producer.performSubscribe(message, callback);
+        Mockito.verify(controlService, 
Mockito.times(1)).subscribeWithPredicateExpression(
+                subscribeChannel, "testId", "mock://test", 10, "true", 
"simple", false);
+    }
+
+    @Test
+    void 
performSubscribeActionFallsBackToEndpointParametersWhenHeadersAreAbsent() {
+        Map<String, Object> headers = Map.of(CONTROL_ACTION_HEADER, 
CONTROL_ACTION_SUBSCRIBE);
+        when(message.getHeaders()).thenReturn(headers);
+        when(configuration.getSubscribeChannel()).thenReturn("testChannel");
+        when(configuration.getSubscriptionId()).thenReturn("testId");
+        when(configuration.getDestinationUri()).thenReturn("mock://test");
+        when(configuration.getPriority()).thenReturn(10);
+        when(configuration.getPredicate()).thenReturn("true");
+        when(configuration.getExpressionLanguage()).thenReturn("simple");
+        Mockito.doNothing().when(callback).done(false);
+        producer.performSubscribe(message, callback);
+        Mockito.verify(controlService, 
Mockito.times(1)).subscribeWithPredicateExpression(
+                "testChannel", "testId", "mock://test", 10, "true", "simple", 
false);
+    }
+
+    @Test
+    void performSubscribeActionWithPredicateBeanIsUnaffectedByTheGate() {
+        String subscribeChannel = "testChannel";
+        Map<String, Object> headers = Map.of(
+                CONTROL_ACTION_HEADER, CONTROL_ACTION_SUBSCRIBE,
+                CONTROL_SUBSCRIBE_CHANNEL, subscribeChannel,
+                CONTROL_SUBSCRIPTION_ID, "testId",
+                CONTROL_DESTINATION_URI, "mock://test",
+                CONTROL_PREDICATE, "true",
+                CONTROL_EXPRESSION_LANGUAGE, "simple",
+                CONTROL_PRIORITY, 10,
+                CONTROL_PREDICATE_BEAN, "testPredicate");
+        when(message.getHeaders()).thenReturn(headers);
+        Mockito.doNothing().when(callback).done(false);
+        producer.performSubscribe(message, callback);
+        Mockito.verify(controlService, 
Mockito.times(1)).subscribeWithPredicateBean(
+                subscribeChannel, "testId", "mock://test", 10, 
"testPredicate", false);
+    }
+
     @Test
     void testPerformListAction() {
         String subscribeChannel = "testChannel";
diff --git 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterBasicSynchronousIT.java
 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterBasicSynchronousIT.java
index da957ccdc46c..8b21c21b624d 100644
--- 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterBasicSynchronousIT.java
+++ 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterBasicSynchronousIT.java
@@ -176,7 +176,7 @@ public class DynamicRouterBasicSynchronousIT {
                 .expressionLanguage("spel")
                 .build();
 
-        template.sendBody("dynamic-router-control:subscribe", controlMessage);
+        
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
 controlMessage);
 
         // Trigger events to subscribers
         template.sendBodyAndHeader("direct:start", "testMessage", "test", 
"testValue");
@@ -239,7 +239,7 @@ public class DynamicRouterBasicSynchronousIT {
                 .predicate("#{headers.test == 'testValue1'}")
                 .expressionLanguage("spel")
                 .build();
-        template.sendBody("dynamic-router-control:subscribe", controlMessage1);
+        
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
 controlMessage1);
 
         DynamicRouterControlMessage controlMessage2 = 
DynamicRouterControlMessage.Builder.newBuilder()
                 .subscribeChannel("test")
@@ -249,7 +249,7 @@ public class DynamicRouterBasicSynchronousIT {
                 .predicate("#{headers.test == 'testValue2'}")
                 .expressionLanguage("spel")
                 .build();
-        template.sendBody("dynamic-router-control:subscribe", controlMessage2);
+        
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
 controlMessage2);
 
         DynamicRouterControlMessage controlMessage3 = 
DynamicRouterControlMessage.Builder.newBuilder()
                 .subscribeChannel("test")
@@ -259,7 +259,7 @@ public class DynamicRouterBasicSynchronousIT {
                 .predicate("#{headers.test == 'testValue3'}")
                 .expressionLanguage("spel")
                 .build();
-        template.sendBody("dynamic-router-control:subscribe", controlMessage3);
+        
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
 controlMessage3);
 
         String filtersJson = template.requestBodyAndHeader("direct:list", "", 
"subscribeChannel", "test", String.class);
         Assertions.assertEquals(
diff --git 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
index bdf67dfc9ff5..47e08e1fed60 100644
--- 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
+++ 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
@@ -116,6 +116,6 @@ public class DynamicRouterDefaultAggregationIT {
                 .predicate("${body} != null")
                 .expressionLanguage("simple")
                 .build();
-        template.sendBody("dynamic-router-control:subscribe", controlMessage);
+        
template.sendBody("dynamic-router-control:subscribe?allowPredicateFromMessage=true",
 controlMessage);
     }
 }
diff --git 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterSendDynamicAwareIT.java
 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterSendDynamicAwareIT.java
index dfcc58352080..aae5c2e1d694 100644
--- 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterSendDynamicAwareIT.java
+++ 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterSendDynamicAwareIT.java
@@ -62,7 +62,8 @@ public class DynamicRouterSendDynamicAwareIT {
                              "&subscriptionId=${header.subscriptionId}" +
                              "&destinationUri=${header.destinationUri}" +
                              "&priority=${header.priority}" +
-                             "&predicate=${header.predicate}");
+                             "&predicate=${header.predicate}" +
+                             "&allowPredicateFromMessage=true");
             }
         });
     }
diff --git 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
index 7f9171d5f797..ea765b79e3f9 100644
--- 
a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
+++ 
b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
@@ -160,9 +160,11 @@ class DynamicRouterRecipientListHelperTest {
         when(mockConfig.isStopOnException()).thenReturn(true);
         when(mockConfig.isIgnoreInvalidEndpoints()).thenReturn(true);
         when(mockConfig.getCacheSize()).thenReturn(10);
+        when(mockConfig.getAllowedSchemes()).thenReturn("direct,mock");
         // Invoke the method under test
         
DynamicRouterRecipientListHelper.setPropertiesForRecipientList(recipientList, 
camelContext, mockConfig);
         // Verify results
+        verify(recipientList, times(1)).setAllowedSchemes("direct,mock");
         verify(recipientList, times(1)).setParallelProcessing(true);
         verify(recipientList, times(1)).setParallelAggregate(true);
         verify(recipientList, times(1)).setSynchronous(true);
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
index 11b71a339f68..035b5e1892ac 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
@@ -61,6 +61,7 @@ public final class SecurityUtils {
         map.put("allowfilepathsource", new SecurityOption(INSECURE_DEV, 
"true"));
         map.put("allowjavaserializedobject", new 
SecurityOption(INSECURE_SERIALIZATION, "true"));
         map.put("allowlocalwebhookurls", new SecurityOption(INSECURE_DEV, 
"true"));
+        map.put("allowpredicatefrommessage", new SecurityOption(INSECURE_DEV, 
"true"));
         map.put("allowserializedheaders", new 
SecurityOption(INSECURE_SERIALIZATION, "true"));
         map.put("allowurlbody", new SecurityOption(INSECURE_DEV, "true"));
         map.put("allowurlsource", new SecurityOption(INSECURE_DEV, "true"));
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 1c3b57e5338a..d50c50fdbb48 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
@@ -39,6 +39,39 @@ Avro IPC handshake classes:
 
 Do not use `*`, as it disables Avro's class-loading protection.
 
+=== camel-dynamic-router
+
+The `dynamic-router-control` endpoint no longer takes the subscription 
`predicate`, or the
+`expressionLanguage` used to compile it, from the incoming control message. A 
control message that
+supplies either is now rejected with an `IllegalArgumentException`.
+
+The predicate is compiled and then evaluated against every exchange on the 
channel, so letting the
+control message choose both the language and the expression means the sender 
of that message decides
+what runs inside the Camel process. Only enable this when control messages can 
only come from a
+trusted source:
+
+[source,java]
+----
+from("kafka:subscriptions")
+    .unmarshal().json(DynamicRouterControlMessage.class)
+    .to("dynamic-router-control:subscribe?allowPredicateFromMessage=true");
+----
+
+The option is annotated `security = "insecure:dev"`, so with 
`camel.main.profile = prod` the default
+policy for that category is `fail`, and an endpoint that sets 
`allowPredicateFromMessage=true` will
+not start unless you relax `camel.security.insecureDevPolicy`.
+
+Two alternatives avoid the flag entirely. A control message may still name a 
`predicateBean`, which
+selects a `Predicate` that the route author bound in the registry; that path 
is unchanged. The
+control endpoint may also carry `predicate` and `expressionLanguage` as URI 
parameters, in which case
+every subscription made through that endpoint uses the route author's 
expression. Subscription
+parameters that the control message does not carry now fall back to the values 
configured on the
+endpoint.
+
+The `dynamic-router` endpoint gained an `allowedSchemes` option, an optional 
comma-separated
+allow-list of component schemes that a subscription destination may resolve 
to. It is unset by
+default, which allows any scheme, matching the previous behaviour.
+
 === camel-exec
 
 `allowControlHeaders` is now annotated `security = "insecure:dev"`.
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterControlEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterControlEndpointBuilderFactory.java
index 0e45af063a02..aab0f8b357fb 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterControlEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterControlEndpointBuilderFactory.java
@@ -176,6 +176,48 @@ public interface 
DynamicRouterControlEndpointBuilderFactory {
             doSetProperty("subscriptionId", subscriptionId);
             return this;
         }
+        /**
+         * Whether the subscription predicate, and the language used to compile
+         * it, may be taken from the incoming control message. When disabled,
+         * the predicate and expressionLanguage configured on this endpoint are
+         * used instead. Enabling this lets the sender of a control message
+         * choose both the expression language and the expression that the
+         * Dynamic Router compiles into a live predicate, so only enable it 
when
+         * control messages come from a trusted source.
+         * 
+         * The option is a: <code>boolean</code> type.
+         * 
+         * Default: false
+         * Group: security
+         * 
+         * @param allowPredicateFromMessage the value to set
+         * @return the dsl builder
+         */
+        default DynamicRouterControlEndpointBuilder 
allowPredicateFromMessage(boolean allowPredicateFromMessage) {
+            doSetProperty("allowPredicateFromMessage", 
allowPredicateFromMessage);
+            return this;
+        }
+        /**
+         * Whether the subscription predicate, and the language used to compile
+         * it, may be taken from the incoming control message. When disabled,
+         * the predicate and expressionLanguage configured on this endpoint are
+         * used instead. Enabling this lets the sender of a control message
+         * choose both the expression language and the expression that the
+         * Dynamic Router compiles into a live predicate, so only enable it 
when
+         * control messages come from a trusted source.
+         * 
+         * The option will be converted to a <code>boolean</code> type.
+         * 
+         * Default: false
+         * Group: security
+         * 
+         * @param allowPredicateFromMessage the value to set
+         * @return the dsl builder
+         */
+        default DynamicRouterControlEndpointBuilder 
allowPredicateFromMessage(String allowPredicateFromMessage) {
+            doSetProperty("allowPredicateFromMessage", 
allowPredicateFromMessage);
+            return this;
+        }
     }
 
     /**
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterEndpointBuilderFactory.java
index 090629ff004e..a1969f993a80 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DynamicRouterEndpointBuilderFactory.java
@@ -701,6 +701,23 @@ public interface DynamicRouterEndpointBuilderFactory {
             doSetProperty("lazyStartProducer", lazyStartProducer);
             return this;
         }
+        /**
+         * Sets an optional comma-separated allow-list of component schemes 
that
+         * a subscription destination may resolve to (e.g. http,https). When
+         * set, a subscription whose destination URI uses a scheme that is not
+         * in the list is rejected. By default (unset) any scheme is allowed.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: security
+         * 
+         * @param allowedSchemes the value to set
+         * @return the dsl builder
+         */
+        default AdvancedDynamicRouterEndpointBuilder allowedSchemes(String 
allowedSchemes) {
+            doSetProperty("allowedSchemes", allowedSchemes);
+            return this;
+        }
     }
 
     public interface DynamicRouterBuilders {

Reply via email to