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

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


The following commit(s) were added to refs/heads/main by this push:
     new 0fe928c51062 CAMEL-24298: add an optional allowedSchemes allow-list to 
the toD dynamic-URI EIP (#25315)
0fe928c51062 is described below

commit 0fe928c5106277357dedc947335631b21372401c
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Aug 5 08:29:51 2026 +0200

    CAMEL-24298: add an optional allowedSchemes allow-list to the toD 
dynamic-URI EIP (#25315)
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 .../org/apache/camel/catalog/docs/toD-eip.adoc     | 39 ++++++++++
 .../org/apache/camel/catalog/models/toD.json       |  3 +-
 .../org/apache/camel/catalog/models/wireTap.json   |  3 +-
 .../apache/camel/catalog/schemas/camel-spring.xsd  | 11 +++
 .../apache/camel/catalog/schemas/camel-xml-io.xsd  | 11 +++
 .../src/main/docs/modules/eips/pages/toD-eip.adoc  | 39 ++++++++++
 .../META-INF/org/apache/camel/model/toD.json       |  3 +-
 .../META-INF/org/apache/camel/model/wireTap.json   |  3 +-
 .../apache/camel/model/ToDynamicDefinition.java    | 28 +++++++
 .../camel/processor/SendDynamicProcessor.java      | 38 +++++++++
 .../org/apache/camel/reifier/ToDynamicReifier.java |  1 +
 .../processor/ToDynamicAllowedSchemesTest.java     | 91 ++++++++++++++++++++++
 .../apache/camel/java/out/JavaDslModelWriter.java  |  1 +
 .../java/org/apache/camel/xml/in/ModelParser.java  |  1 +
 .../java/org/apache/camel/xml/out/ModelWriter.java |  1 +
 .../org/apache/camel/yaml/out/YamlModelWriter.java |  1 +
 .../dsl/yaml/deserializers/ModelDeserializers.java | 12 +++
 .../resources/schema/camelYamlDsl-canonical.json   | 10 +++
 .../generated/resources/schema/camelYamlDsl.json   | 10 +++
 19 files changed, 302 insertions(+), 4 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc
index fd2cc2e040d6..1f9fb853484e 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc
@@ -417,3 +417,42 @@ during startup if `toD` was optimized, or if there was a 
failure loading the opt
 ----
 Detected SendDynamicAware component: http optimising toD: 
http:myloginserver:8080/login?userid=${header.userName}
 ----
+
+== Restricting the allowed component schemes
+
+Because `toD` computes its endpoint uri at runtime, a route that interpolates 
message content into the uri
+(for example `toD("${header.target}")`) can end up resolving to any component 
on the classpath. In low-code or
+Kamelet-style deployments you may want to constrain this to a fixed set of 
components. The optional `allowedSchemes`
+option takes a comma-separated allow-list of component schemes; a resolved 
recipient whose scheme is not in the list
+is rejected (independently of `ignoreInvalidEndpoint`). By default the option 
is unset and any scheme is allowed.
+
+[source,java]
+----
+from("direct:start")
+    // only http and https recipients are permitted
+    .toD().allowedSchemes("http,https").uri("${header.target}");
+----
+
+And in XML:
+
+[source,xml]
+----
+<route>
+    <from uri="direct:start"/>
+    <toD uri="${header.target}" allowedSchemes="http,https"/>
+</route>
+----
+
+And in YAML:
+
+[source,yaml]
+----
+- from:
+    uri: direct:start
+    steps:
+      - toD:
+          uri: "${header.target}"
+          allowedSchemes: "http,https"
+----
+
+NOTE: `wireTap` extends `toD` and therefore honours the same `allowedSchemes` 
option.
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
index dc02feb1013d..14c5e2df2047 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
@@ -24,7 +24,8 @@
     "cacheSize": { "index": 8, "kind": "attribute", "displayName": "Cache 
Size", "group": "advanced", "label": "advanced", "required": false, "type": 
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": 
false, "secret": false, "description": "Sets the maximum size used by the 
ProducerCache which is used to cache and reuse producers when uris are reused. 
Use 0 for default cache size, or -1 to turn cache off." },
     "ignoreInvalidEndpoint": { "index": 9, "kind": "attribute", "displayName": 
"Ignore Invalid Endpoint", "group": "advanced", "label": "advanced", 
"required": false, "type": "boolean", "javaType": "java.lang.Boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "description": "Whether to ignore invalid endpoint URIs and skip sending 
the message." },
     "allowOptimisedComponents": { "index": 10, "kind": "attribute", 
"displayName": "Allow Optimised Components", "group": "advanced", "label": 
"advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": true, "description": "Whether to allow components to optimise 
toD if they are SendDynamicAware." },
-    "autoStartComponents": { "index": 11, "kind": "attribute", "displayName": 
"Auto Start Components", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to auto startup components when toD is starting up." }
+    "autoStartComponents": { "index": 11, "kind": "attribute", "displayName": 
"Auto Start Components", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to auto startup components when toD is starting up." },
+    "allowedSchemes": { "index": 12, "kind": "attribute", "displayName": 
"Allowed Schemes", "group": "security", "label": "advanced,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
an optional comma-separated allow-list of component schemes that the dynamic 
recipient may resolve to (e.g. http,https). When set, a dynamic endpoint whose 
scheme is not in the list is rejected. This i [...]
   },
   "exchangeProperties": {
     "CamelToEndpoint": { "index": 0, "kind": "exchangeProperty", 
"displayName": "To Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where this Exchange is being sent to" }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/wireTap.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/wireTap.json
index ffbf852b5537..e4ec0901987c 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/wireTap.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/wireTap.json
@@ -27,7 +27,8 @@
     "cacheSize": { "index": 11, "kind": "attribute", "displayName": "Cache 
Size", "group": "advanced", "label": "advanced", "required": false, "type": 
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": 
false, "secret": false, "description": "Sets the maximum size used by the 
ProducerCache which is used to cache and reuse producers when uris are reused. 
Use 0 for default cache size, or -1 to turn cache off." },
     "ignoreInvalidEndpoint": { "index": 12, "kind": "attribute", 
"displayName": "Ignore Invalid Endpoint", "group": "advanced", "label": 
"advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to ignore invalid endpoint URIs 
and skip sending the message." },
     "allowOptimisedComponents": { "index": 13, "kind": "attribute", 
"displayName": "Allow Optimised Components", "group": "advanced", "label": 
"advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": true, "description": "Whether to allow components to optimise 
toD if they are SendDynamicAware." },
-    "autoStartComponents": { "index": 14, "kind": "attribute", "displayName": 
"Auto Start Components", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to auto startup components when toD is starting up." }
+    "autoStartComponents": { "index": 14, "kind": "attribute", "displayName": 
"Auto Start Components", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to auto startup components when toD is starting up." },
+    "allowedSchemes": { "index": 15, "kind": "attribute", "displayName": 
"Allowed Schemes", "group": "security", "label": "advanced,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
an optional comma-separated allow-list of component schemes that the dynamic 
recipient may resolve to (e.g. http,https). When set, a dynamic endpoint whose 
scheme is not in the list is rejected. This i [...]
   },
   "exchangeProperties": {
     "CamelToEndpoint": { "index": 0, "kind": "exchangeProperty", 
"displayName": "To Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where this Exchange is being sent to" }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index 4b463bb4cf2a..ee5fb9097f7d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -14006,6 +14006,17 @@ Whether to allow components to optimise toD if they 
are SendDynamicAware. Defaul
             <xs:documentation xml:lang="en">
 <![CDATA[
 Whether to auto startup components when toD is starting up. Default value: true
+]]>
+            </xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="allowedSchemes" type="xs:string">
+          <xs:annotation>
+            <xs:documentation xml:lang="en">
+<![CDATA[
+Sets an optional comma-separated allow-list of component schemes that the 
dynamic recipient may resolve to (e.g.
+http,https). When set, a dynamic endpoint whose scheme is not in the list is 
rejected. This is a defence-in-depth
+restriction, useful for low-code / Kamelet deployments; by default (unset) any 
scheme is allowed.
 ]]>
             </xs:documentation>
           </xs:annotation>
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
index 10b2c2dea867..f7eeccaf7b22 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
@@ -13149,6 +13149,17 @@ Whether to allow components to optimise toD if they 
are SendDynamicAware. Defaul
             <xs:documentation xml:lang="en">
 <![CDATA[
 Whether to auto startup components when toD is starting up. Default value: true
+]]>
+            </xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="allowedSchemes" type="xs:string">
+          <xs:annotation>
+            <xs:documentation xml:lang="en">
+<![CDATA[
+Sets an optional comma-separated allow-list of component schemes that the 
dynamic recipient may resolve to (e.g.
+http,https). When set, a dynamic endpoint whose scheme is not in the list is 
rejected. This is a defence-in-depth
+restriction, useful for low-code / Kamelet deployments; by default (unset) any 
scheme is allowed.
 ]]>
             </xs:documentation>
           </xs:annotation>
diff --git 
a/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc 
b/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc
index fd2cc2e040d6..1f9fb853484e 100644
--- a/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc
+++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc
@@ -417,3 +417,42 @@ during startup if `toD` was optimized, or if there was a 
failure loading the opt
 ----
 Detected SendDynamicAware component: http optimising toD: 
http:myloginserver:8080/login?userid=${header.userName}
 ----
+
+== Restricting the allowed component schemes
+
+Because `toD` computes its endpoint uri at runtime, a route that interpolates 
message content into the uri
+(for example `toD("${header.target}")`) can end up resolving to any component 
on the classpath. In low-code or
+Kamelet-style deployments you may want to constrain this to a fixed set of 
components. The optional `allowedSchemes`
+option takes a comma-separated allow-list of component schemes; a resolved 
recipient whose scheme is not in the list
+is rejected (independently of `ignoreInvalidEndpoint`). By default the option 
is unset and any scheme is allowed.
+
+[source,java]
+----
+from("direct:start")
+    // only http and https recipients are permitted
+    .toD().allowedSchemes("http,https").uri("${header.target}");
+----
+
+And in XML:
+
+[source,xml]
+----
+<route>
+    <from uri="direct:start"/>
+    <toD uri="${header.target}" allowedSchemes="http,https"/>
+</route>
+----
+
+And in YAML:
+
+[source,yaml]
+----
+- from:
+    uri: direct:start
+    steps:
+      - toD:
+          uri: "${header.target}"
+          allowedSchemes: "http,https"
+----
+
+NOTE: `wireTap` extends `toD` and therefore honours the same `allowedSchemes` 
option.
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
index dc02feb1013d..14c5e2df2047 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
@@ -24,7 +24,8 @@
     "cacheSize": { "index": 8, "kind": "attribute", "displayName": "Cache 
Size", "group": "advanced", "label": "advanced", "required": false, "type": 
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": 
false, "secret": false, "description": "Sets the maximum size used by the 
ProducerCache which is used to cache and reuse producers when uris are reused. 
Use 0 for default cache size, or -1 to turn cache off." },
     "ignoreInvalidEndpoint": { "index": 9, "kind": "attribute", "displayName": 
"Ignore Invalid Endpoint", "group": "advanced", "label": "advanced", 
"required": false, "type": "boolean", "javaType": "java.lang.Boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "description": "Whether to ignore invalid endpoint URIs and skip sending 
the message." },
     "allowOptimisedComponents": { "index": 10, "kind": "attribute", 
"displayName": "Allow Optimised Components", "group": "advanced", "label": 
"advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": true, "description": "Whether to allow components to optimise 
toD if they are SendDynamicAware." },
-    "autoStartComponents": { "index": 11, "kind": "attribute", "displayName": 
"Auto Start Components", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to auto startup components when toD is starting up." }
+    "autoStartComponents": { "index": 11, "kind": "attribute", "displayName": 
"Auto Start Components", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to auto startup components when toD is starting up." },
+    "allowedSchemes": { "index": 12, "kind": "attribute", "displayName": 
"Allowed Schemes", "group": "security", "label": "advanced,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
an optional comma-separated allow-list of component schemes that the dynamic 
recipient may resolve to (e.g. http,https). When set, a dynamic endpoint whose 
scheme is not in the list is rejected. This i [...]
   },
   "exchangeProperties": {
     "CamelToEndpoint": { "index": 0, "kind": "exchangeProperty", 
"displayName": "To Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where this Exchange is being sent to" }
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/wireTap.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/wireTap.json
index ffbf852b5537..e4ec0901987c 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/wireTap.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/wireTap.json
@@ -27,7 +27,8 @@
     "cacheSize": { "index": 11, "kind": "attribute", "displayName": "Cache 
Size", "group": "advanced", "label": "advanced", "required": false, "type": 
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": 
false, "secret": false, "description": "Sets the maximum size used by the 
ProducerCache which is used to cache and reuse producers when uris are reused. 
Use 0 for default cache size, or -1 to turn cache off." },
     "ignoreInvalidEndpoint": { "index": 12, "kind": "attribute", 
"displayName": "Ignore Invalid Endpoint", "group": "advanced", "label": 
"advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to ignore invalid endpoint URIs 
and skip sending the message." },
     "allowOptimisedComponents": { "index": 13, "kind": "attribute", 
"displayName": "Allow Optimised Components", "group": "advanced", "label": 
"advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": true, "description": "Whether to allow components to optimise 
toD if they are SendDynamicAware." },
-    "autoStartComponents": { "index": 14, "kind": "attribute", "displayName": 
"Auto Start Components", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to auto startup components when toD is starting up." }
+    "autoStartComponents": { "index": 14, "kind": "attribute", "displayName": 
"Auto Start Components", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to auto startup components when toD is starting up." },
+    "allowedSchemes": { "index": 15, "kind": "attribute", "displayName": 
"Allowed Schemes", "group": "security", "label": "advanced,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
an optional comma-separated allow-list of component schemes that the dynamic 
recipient may resolve to (e.g. http,https). When set, a dynamic endpoint whose 
scheme is not in the list is rejected. This i [...]
   },
   "exchangeProperties": {
     "CamelToEndpoint": { "index": 0, "kind": "exchangeProperty", 
"displayName": "To Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where this Exchange is being sent to" }
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
index 076280d6380c..1d0b0a9f3e56 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
@@ -74,6 +74,13 @@ public class ToDynamicDefinition extends 
NoOutputDefinition<ToDynamicDefinition>
     @Metadata(label = "advanced", defaultValue = "true", javaType = 
"java.lang.Boolean",
               description = "Whether to auto startup components when toD is 
starting up.")
     private String autoStartComponents;
+    @XmlAttribute
+    @Metadata(label = "advanced,security",
+              description = "Sets an optional comma-separated allow-list of 
component schemes that the dynamic recipient"
+                            + " may resolve to (e.g. http,https). When set, a 
dynamic endpoint whose scheme is not in the"
+                            + " list is rejected. This is a defence-in-depth 
restriction, useful for low-code / Kamelet"
+                            + " deployments; by default (unset) any scheme is 
allowed.")
+    private String allowedSchemes;
 
     public ToDynamicDefinition() {
     }
@@ -93,6 +100,7 @@ public class ToDynamicDefinition extends 
NoOutputDefinition<ToDynamicDefinition>
         this.ignoreInvalidEndpoint = source.ignoreInvalidEndpoint;
         this.allowOptimisedComponents = source.allowOptimisedComponents;
         this.autoStartComponents = source.autoStartComponents;
+        this.allowedSchemes = source.allowedSchemes;
     }
 
     @Override
@@ -264,6 +272,18 @@ public class ToDynamicDefinition extends 
NoOutputDefinition<ToDynamicDefinition>
         return this;
     }
 
+    /**
+     * Sets an optional comma-separated allow-list of component schemes that 
the dynamic recipient may resolve to (e.g.
+     * http,https). When set, a dynamic endpoint whose scheme is not in the 
list is rejected. By default (unset) any
+     * scheme is allowed.
+     *
+     * @return the builder
+     */
+    public ToDynamicDefinition allowedSchemes(String allowedSchemes) {
+        setAllowedSchemes(allowedSchemes);
+        return this;
+    }
+
     // Properties
     // 
-------------------------------------------------------------------------
 
@@ -339,6 +359,14 @@ public class ToDynamicDefinition extends 
NoOutputDefinition<ToDynamicDefinition>
         this.autoStartComponents = autoStartComponents;
     }
 
+    public String getAllowedSchemes() {
+        return allowedSchemes;
+    }
+
+    public void setAllowedSchemes(String allowedSchemes) {
+        this.allowedSchemes = allowedSchemes;
+    }
+
     public ToDynamicDefinition copyDefinition() {
         return new ToDynamicDefinition(this);
     }
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
index 2104806dff08..b1e48e7f78c3 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
@@ -16,7 +16,9 @@
  */
 package org.apache.camel.processor;
 
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.CamelContext;
@@ -77,6 +79,8 @@ public class SendDynamicProcessor extends BaseProcessorSupport
     protected int cacheSize;
     protected boolean allowOptimisedComponents = true;
     protected boolean autoStartupComponents = true;
+    protected String allowedSchemes;
+    private Set<String> allowedSchemesSet;
 
     public SendDynamicProcessor(String uri, Expression expression) {
         this.uri = uri;
@@ -172,6 +176,18 @@ public class SendDynamicProcessor extends 
BaseProcessorSupport
                 callback.done(true);
                 return true;
             }
+            // enforce the optional allowed-schemes allow-list on the resolved 
dynamic recipient (CAMEL-24298);
+            // a disallowed scheme is always rejected, independently of 
ignoreInvalidEndpoint
+            if (allowedSchemes != null) {
+                String targetUri = targetRecipient.toString();
+                String targetScheme = resolveScheme(exchange, targetUri);
+                if (targetScheme != null && !isSchemeAllowed(targetScheme)) {
+                    exchange.setException(new ResolveEndpointFailedException(
+                            targetUri, "Scheme " + targetScheme + " is not in 
the allowed schemes: " + allowedSchemes));
+                    callback.done(true);
+                    return true;
+                }
+            }
             Endpoint existing = getExistingEndpoint(exchange, targetRecipient);
             if (existing == null) {
                 endpoint = resolveEndpoint(exchange, targetRecipient, 
prototype);
@@ -494,4 +510,26 @@ public class SendDynamicProcessor extends 
BaseProcessorSupport
     public void setAutoStartupComponents(boolean autoStartupComponents) {
         this.autoStartupComponents = autoStartupComponents;
     }
+
+    public String getAllowedSchemes() {
+        return allowedSchemes;
+    }
+
+    public void setAllowedSchemes(String allowedSchemes) {
+        this.allowedSchemes = allowedSchemes;
+        if (allowedSchemes != null) {
+            // pre-parse once into a set, so the per-exchange check does not 
allocate/split on every message
+            Set<String> set = new HashSet<>();
+            for (String allowed : allowedSchemes.split(",")) {
+                set.add(allowed.trim());
+            }
+            this.allowedSchemesSet = set;
+        } else {
+            this.allowedSchemesSet = null;
+        }
+    }
+
+    private boolean isSchemeAllowed(String scheme) {
+        return allowedSchemesSet == null || allowedSchemesSet.contains(scheme);
+    }
 }
diff --git 
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
 
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
index 291c0936430d..47b50e8f5a32 100644
--- 
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
+++ 
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
@@ -77,6 +77,7 @@ public class ToDynamicReifier<T extends ToDynamicDefinition> 
extends ProcessorRe
         if (definition.getAutoStartComponents() != null) {
             
processor.setAutoStartupComponents(parseBoolean(definition.getAutoStartComponents(),
 true));
         }
+        
processor.setAllowedSchemes(parseString(definition.getAllowedSchemes()));
         return processor;
     }
 
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicAllowedSchemesTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicAllowedSchemesTest.java
new file mode 100644
index 000000000000..14d6be372249
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicAllowedSchemesTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.ResolveEndpointFailedException;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * The optional {@code allowedSchemes} allow-list on {@code toD} restricts 
which component schemes a dynamic recipient
+ * may resolve to. A recipient whose scheme is not in the list is rejected, 
independently of
+ * {@code ignoreInvalidEndpoint}. See CAMEL-24298.
+ */
+class ToDynamicAllowedSchemesTest extends ContextTestSupport {
+
+    @Test
+    void allowedSchemeIsSent() throws Exception {
+        getMockEndpoint("mock:allowed").expectedMessageCount(1);
+
+        template.sendBodyAndHeader("direct:start", "Hello", "target", 
"mock:allowed");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    void disallowedSchemeIsRejected() {
+        assertThatThrownBy(() -> template.sendBodyAndHeader("direct:start", 
"Hello", "target", "seda:blocked"))
+                .isInstanceOf(CamelExecutionException.class)
+                .cause()
+                .isInstanceOf(ResolveEndpointFailedException.class)
+                .hasMessageContaining("not in the allowed schemes");
+    }
+
+    @Test
+    void multipleAllowedSchemesHonored() throws Exception {
+        getMockEndpoint("mock:ok").expectedMessageCount(1);
+
+        // both mock and seda are in the allow-list
+        template.sendBodyAndHeader("direct:multi", "Hello", "target", 
"mock:ok");
+        template.sendBodyAndHeader("direct:multi", "Hello", "target", 
"seda:ok");
+
+        assertMockEndpointsSatisfied();
+
+        // a scheme outside the allow-list is still rejected
+        assertThatThrownBy(() -> template.sendBodyAndHeader("direct:multi", 
"Hello", "target", "log:blocked"))
+                .isInstanceOf(CamelExecutionException.class)
+                .cause()
+                .isInstanceOf(ResolveEndpointFailedException.class);
+    }
+
+    @Test
+    void disallowedSchemeRejectedEvenWithIgnoreInvalidEndpoint() {
+        // a disallowed scheme hard-fails even when 
ignoreInvalidEndpoint=true: the allow-list check sets the
+        // exception directly instead of throwing, so it bypasses the 
ignoreInvalidEndpoint handling
+        assertThatThrownBy(() -> template.sendBodyAndHeader("direct:ignore", 
"Hello", "target", "seda:blocked"))
+                .isInstanceOf(CamelExecutionException.class)
+                .cause()
+                .isInstanceOf(ResolveEndpointFailedException.class)
+                .hasMessageContaining("not in the allowed schemes");
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("direct:start").toD().allowedSchemes("mock").uri("${header.target}");
+                
from("direct:multi").toD().allowedSchemes("mock,seda").uri("${header.target}");
+                
from("direct:ignore").toD().ignoreInvalidEndpoint(true).allowedSchemes("mock").uri("${header.target}");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
 
b/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
index dc001845851a..cae1868f093d 100644
--- 
a/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
+++ 
b/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java
@@ -2614,6 +2614,7 @@ public class JavaDslModelWriter extends 
JavaDslModelWriterSupport {
         doWriteAttribute(sb, "ignoreInvalidEndpoint", 
def.getIgnoreInvalidEndpoint(), null);
         doWriteAttribute(sb, "allowOptimisedComponents", 
def.getAllowOptimisedComponents(), "true");
         doWriteAttribute(sb, "autoStartComponents", 
def.getAutoStartComponents(), "true");
+        doWriteAttribute(sb, "allowedSchemes", def.getAllowedSchemes(), null);
     }
     protected void doWriteToDynamicDefinition(StringBuilder sb, 
ToDynamicDefinition def) {
         doWriteToDynamicDefinitionAttributes(sb, def);
diff --git 
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java 
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index e8213adb2cac..19c5adcb7258 100644
--- 
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++ 
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -1281,6 +1281,7 @@ public class ModelParser extends BaseParser {
     protected <T extends ToDynamicDefinition> AttributeHandler<T> 
toDynamicDefinitionAttributeHandler() {
         return (def, key, val) -> switch (key) {
             case "allowOptimisedComponents": 
def.setAllowOptimisedComponents(val); yield true;
+            case "allowedSchemes": def.setAllowedSchemes(val); yield true;
             case "autoStartComponents": def.setAutoStartComponents(val); yield 
true;
             case "cacheSize": def.setCacheSize(val); yield true;
             case "ignoreInvalidEndpoint": def.setIgnoreInvalidEndpoint(val); 
yield true;
diff --git 
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
 
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
index 00128f8a4b65..1b949e1a840c 100644
--- 
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
+++ 
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
@@ -1884,6 +1884,7 @@ public class ModelWriter extends BaseWriter {
         doWriteAttribute("ignoreInvalidEndpoint", 
def.getIgnoreInvalidEndpoint(), null);
         doWriteAttribute("allowOptimisedComponents", 
def.getAllowOptimisedComponents(), "true");
         doWriteAttribute("autoStartComponents", def.getAutoStartComponents(), 
"true");
+        doWriteAttribute("allowedSchemes", def.getAllowedSchemes(), null);
     }
     protected void doWriteToDynamicDefinition(String name, ToDynamicDefinition 
def) throws IOException {
         startElement(name);
diff --git 
a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
 
b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
index 16c4a74440de..460305fcbcba 100644
--- 
a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
+++ 
b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java
@@ -1880,6 +1880,7 @@ public class YamlModelWriter extends 
YamlModelWriterSupport {
         doWriteAttribute(jo, "ignoreInvalidEndpoint", 
def.getIgnoreInvalidEndpoint(), null);
         doWriteAttribute(jo, "allowOptimisedComponents", 
def.getAllowOptimisedComponents(), "true");
         doWriteAttribute(jo, "autoStartComponents", 
def.getAutoStartComponents(), "true");
+        doWriteAttribute(jo, "allowedSchemes", def.getAllowedSchemes(), null);
     }
     protected JsonObject doWriteToDynamicDefinition(ToDynamicDefinition def) {
         JsonObject jo = new JsonObject();
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index 01e0cd561c18..50b7c6e090a6 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -18416,6 +18416,7 @@ public final class ModelDeserializers extends 
YamlDeserializerSupport {
             deprecated = false,
             properties = {
                     @YamlProperty(name = "allowOptimisedComponents", type = 
"boolean", defaultValue = "true", description = "Whether to allow components to 
optimise toD if they are SendDynamicAware.", displayName = "Allow Optimised 
Components"),
+                    @YamlProperty(name = "allowedSchemes", type = "string", 
description = "Sets an optional comma-separated allow-list of component schemes 
that the dynamic recipient may resolve to (e.g. http,https). When set, a 
dynamic endpoint whose scheme is not in the list is rejected. This is a 
defence-in-depth restriction, useful for low-code / Kamelet deployments; by 
default (unset) any scheme is allowed.", displayName = "Allowed Schemes"),
                     @YamlProperty(name = "autoStartComponents", type = 
"boolean", defaultValue = "true", description = "Whether to auto startup 
components when toD is starting up.", displayName = "Auto Start Components"),
                     @YamlProperty(name = "cacheSize", type = "number", 
description = "Sets the maximum size used by the ProducerCache which is used to 
cache and reuse producers when uris are reused. Use 0 for default cache size, 
or -1 to turn cache off.", displayName = "Cache Size"),
                     @YamlProperty(name = "description", type = "string", 
description = "The description for this node", displayName = "Description"),
@@ -18461,6 +18462,11 @@ public final class ModelDeserializers extends 
YamlDeserializerSupport {
                     target.setAllowOptimisedComponents(val);
                     break;
                 }
+                case "allowedSchemes": {
+                    String val = asText(node);
+                    target.setAllowedSchemes(val);
+                    break;
+                }
                 case "autoStartComponents": {
                     String val = asText(node);
                     target.setAutoStartComponents(val);
@@ -20491,6 +20497,7 @@ public final class ModelDeserializers extends 
YamlDeserializerSupport {
             deprecated = false,
             properties = {
                     @YamlProperty(name = "allowOptimisedComponents", type = 
"boolean", defaultValue = "true", description = "Whether to allow components to 
optimise toD if they are SendDynamicAware.", displayName = "Allow Optimised 
Components"),
+                    @YamlProperty(name = "allowedSchemes", type = "string", 
description = "Sets an optional comma-separated allow-list of component schemes 
that the dynamic recipient may resolve to (e.g. http,https). When set, a 
dynamic endpoint whose scheme is not in the list is rejected. This is a 
defence-in-depth restriction, useful for low-code / Kamelet deployments; by 
default (unset) any scheme is allowed.", displayName = "Allowed Schemes"),
                     @YamlProperty(name = "autoStartComponents", type = 
"boolean", defaultValue = "true", description = "Whether to auto startup 
components when toD is starting up.", displayName = "Auto Start Components"),
                     @YamlProperty(name = "cacheSize", type = "number", 
description = "Sets the maximum size used by the ProducerCache which is used to 
cache and reuse producers when uris are reused. Use 0 for default cache size, 
or -1 to turn cache off.", displayName = "Cache Size"),
                     @YamlProperty(name = "copy", type = "boolean", 
defaultValue = "true", description = "Whether to use a copy of the original 
exchange.", displayName = "Copy"),
@@ -20534,6 +20541,11 @@ public final class ModelDeserializers extends 
YamlDeserializerSupport {
                     target.setAllowOptimisedComponents(val);
                     break;
                 }
+                case "allowedSchemes": {
+                    String val = asText(node);
+                    target.setAllowedSchemes(val);
+                    break;
+                }
                 case "autoStartComponents": {
                     String val = asText(node);
                     target.setAutoStartComponents(val);
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
index f80e09111ed4..3f82b7efc540 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json
@@ -5252,6 +5252,11 @@
             "description" : "Whether to allow components to optimise toD if 
they are SendDynamicAware.",
             "default" : true
           },
+          "allowedSchemes" : {
+            "type" : "string",
+            "title" : "Allowed Schemes",
+            "description" : "Sets an optional comma-separated allow-list of 
component schemes that the dynamic recipient may resolve to (e.g. http,https). 
When set, a dynamic endpoint whose scheme is not in the list is rejected. This 
is a defence-in-depth restriction, useful for low-code / Kamelet deployments; 
by default (unset) any scheme is allowed."
+          },
           "autoStartComponents" : {
             "type" : "boolean",
             "title" : "Auto Start Components",
@@ -5826,6 +5831,11 @@
             "description" : "Whether to allow components to optimise toD if 
they are SendDynamicAware.",
             "default" : true
           },
+          "allowedSchemes" : {
+            "type" : "string",
+            "title" : "Allowed Schemes",
+            "description" : "Sets an optional comma-separated allow-list of 
component schemes that the dynamic recipient may resolve to (e.g. http,https). 
When set, a dynamic endpoint whose scheme is not in the list is rejected. This 
is a defence-in-depth restriction, useful for low-code / Kamelet deployments; 
by default (unset) any scheme is allowed."
+          },
           "autoStartComponents" : {
             "type" : "boolean",
             "title" : "Auto Start Components",
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index 03bf74cf5316..72766d7318fa 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -8103,6 +8103,11 @@
               "description" : "Whether to allow components to optimise toD if 
they are SendDynamicAware.",
               "default" : true
             },
+            "allowedSchemes" : {
+              "type" : "string",
+              "title" : "Allowed Schemes",
+              "description" : "Sets an optional comma-separated allow-list of 
component schemes that the dynamic recipient may resolve to (e.g. http,https). 
When set, a dynamic endpoint whose scheme is not in the list is rejected. This 
is a defence-in-depth restriction, useful for low-code / Kamelet deployments; 
by default (unset) any scheme is allowed."
+            },
             "autoStartComponents" : {
               "type" : "boolean",
               "title" : "Auto Start Components",
@@ -9313,6 +9318,11 @@
             "description" : "Whether to allow components to optimise toD if 
they are SendDynamicAware.",
             "default" : true
           },
+          "allowedSchemes" : {
+            "type" : "string",
+            "title" : "Allowed Schemes",
+            "description" : "Sets an optional comma-separated allow-list of 
component schemes that the dynamic recipient may resolve to (e.g. http,https). 
When set, a dynamic endpoint whose scheme is not in the list is rejected. This 
is a defence-in-depth restriction, useful for low-code / Kamelet deployments; 
by default (unset) any scheme is allowed."
+          },
           "autoStartComponents" : {
             "type" : "boolean",
             "title" : "Auto Start Components",

Reply via email to