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 2014bec6fee1 CAMEL-24297: make ldif URL-body dereferencing an explicit 
opt-in (#25310)
2014bec6fee1 is described below

commit 2014bec6fee1946eb3ad9018523ecdc3644f70ae
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Aug 3 18:57:28 2026 +0200

    CAMEL-24297: make ldif URL-body dereferencing an explicit opt-in (#25310)
    
    The ldif producer treated a message body that does not start with "version: 
1" as a URL and
    dereferenced it (URI.create(body).toURL().openStream()). This 
content-sniffed URL fetch is now gated
    by a new allowUrlBody option (default false, tagged 
security="insecure:dev"). With the default a
    non-LDIF body is rejected with an IllegalArgumentException instead of being 
fetched, avoiding a
    content-sniffed URL fetch (SSRF) from untrusted body content.
    
    Routes that rely on passing a URL as the body must set allowUrlBody=true. 
Adds a unit test for the
    default rejection, enables the option on the existing LdifRouteIT (which 
feeds URLs as the body), and
    documents the change in the upgrade guide.
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 .../org/apache/camel/catalog/components/ldif.json  |  3 +-
 .../component/ldif/LdifEndpointConfigurer.java     |  6 +++
 .../component/ldif/LdifEndpointUriFactory.java     |  3 +-
 .../org/apache/camel/component/ldif/ldif.json      |  3 +-
 .../apache/camel/component/ldif/LdifEndpoint.java  | 17 +++++++
 .../apache/camel/component/ldif/LdifProducer.java  | 11 ++++-
 .../camel/component/ldif/LdifAllowUrlBodyTest.java | 53 ++++++++++++++++++++++
 .../apache/camel/component/ldif/LdifRouteIT.java   |  3 +-
 .../java/org/apache/camel/util/SecurityUtils.java  |  1 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc    |  9 ++++
 .../endpoint/dsl/LdifEndpointBuilderFactory.java   | 40 ++++++++++++++++
 11 files changed, 143 insertions(+), 6 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ldif.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ldif.json
index 73c2de0d9330..009d5314aefe 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ldif.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ldif.json
@@ -29,6 +29,7 @@
   },
   "properties": {
     "ldapConnectionName": { "index": 0, "kind": "path", "displayName": "Ldap 
Connection Name", "group": "producer", "label": "", "required": true, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "The 
name of the LdapConnection bean to pull from the registry. Note that this must 
be of scope prototype to avoid it being shared among threads or using a 
connection that has timed out." },
-    "lazyStartProducer": { "index": 1, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
+    "lazyStartProducer": { "index": 1, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
+    "allowUrlBody": { "index": 2, "kind": "parameter", "displayName": "Allow 
Url Body", "group": "security", "label": "security", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "security": "insecure:dev", "defaultValue": false, 
"description": "Whether to allow a message body that is not LDIF content to be 
dereferenced as a URL and fetched. When disabled (default), a body that does 
not start with version: 1 is rejecte [...]
   }
 }
diff --git 
a/components/camel-ldif/src/generated/java/org/apache/camel/component/ldif/LdifEndpointConfigurer.java
 
b/components/camel-ldif/src/generated/java/org/apache/camel/component/ldif/LdifEndpointConfigurer.java
index b7a30cf4d9ca..d4cecef71358 100644
--- 
a/components/camel-ldif/src/generated/java/org/apache/camel/component/ldif/LdifEndpointConfigurer.java
+++ 
b/components/camel-ldif/src/generated/java/org/apache/camel/component/ldif/LdifEndpointConfigurer.java
@@ -23,6 +23,8 @@ public class LdifEndpointConfigurer extends 
PropertyConfigurerSupport implements
     public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
         LdifEndpoint target = (LdifEndpoint) obj;
         switch (ignoreCase ? name.toLowerCase() : name) {
+        case "allowurlbody":
+        case "allowUrlBody": target.setAllowUrlBody(property(camelContext, 
boolean.class, value)); return true;
         case "lazystartproducer":
         case "lazyStartProducer": 
target.setLazyStartProducer(property(camelContext, boolean.class, value)); 
return true;
         default: return false;
@@ -32,6 +34,8 @@ public class LdifEndpointConfigurer extends 
PropertyConfigurerSupport implements
     @Override
     public Class<?> getOptionType(String name, boolean ignoreCase) {
         switch (ignoreCase ? name.toLowerCase() : name) {
+        case "allowurlbody":
+        case "allowUrlBody": return boolean.class;
         case "lazystartproducer":
         case "lazyStartProducer": return boolean.class;
         default: return null;
@@ -42,6 +46,8 @@ public class LdifEndpointConfigurer extends 
PropertyConfigurerSupport implements
     public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
         LdifEndpoint target = (LdifEndpoint) obj;
         switch (ignoreCase ? name.toLowerCase() : name) {
+        case "allowurlbody":
+        case "allowUrlBody": return target.isAllowUrlBody();
         case "lazystartproducer":
         case "lazyStartProducer": return target.isLazyStartProducer();
         default: return null;
diff --git 
a/components/camel-ldif/src/generated/java/org/apache/camel/component/ldif/LdifEndpointUriFactory.java
 
b/components/camel-ldif/src/generated/java/org/apache/camel/component/ldif/LdifEndpointUriFactory.java
index 679fba82585b..4fad9d700596 100644
--- 
a/components/camel-ldif/src/generated/java/org/apache/camel/component/ldif/LdifEndpointUriFactory.java
+++ 
b/components/camel-ldif/src/generated/java/org/apache/camel/component/ldif/LdifEndpointUriFactory.java
@@ -24,7 +24,8 @@ public class LdifEndpointUriFactory extends 
org.apache.camel.support.component.E
     private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(2);
+        Set<String> props = new HashSet<>(3);
+        props.add("allowUrlBody");
         props.add("lazyStartProducer");
         props.add("ldapConnectionName");
         PROPERTY_NAMES = Collections.unmodifiableSet(props);
diff --git 
a/components/camel-ldif/src/generated/resources/META-INF/org/apache/camel/component/ldif/ldif.json
 
b/components/camel-ldif/src/generated/resources/META-INF/org/apache/camel/component/ldif/ldif.json
index 73c2de0d9330..009d5314aefe 100644
--- 
a/components/camel-ldif/src/generated/resources/META-INF/org/apache/camel/component/ldif/ldif.json
+++ 
b/components/camel-ldif/src/generated/resources/META-INF/org/apache/camel/component/ldif/ldif.json
@@ -29,6 +29,7 @@
   },
   "properties": {
     "ldapConnectionName": { "index": 0, "kind": "path", "displayName": "Ldap 
Connection Name", "group": "producer", "label": "", "required": true, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": "The 
name of the LdapConnection bean to pull from the registry. Note that this must 
be of scope prototype to avoid it being shared among threads or using a 
connection that has timed out." },
-    "lazyStartProducer": { "index": 1, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
+    "lazyStartProducer": { "index": 1, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
+    "allowUrlBody": { "index": 2, "kind": "parameter", "displayName": "Allow 
Url Body", "group": "security", "label": "security", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "security": "insecure:dev", "defaultValue": false, 
"description": "Whether to allow a message body that is not LDIF content to be 
dereferenced as a URL and fetched. When disabled (default), a body that does 
not start with version: 1 is rejecte [...]
   }
 }
diff --git 
a/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifEndpoint.java
 
b/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifEndpoint.java
index 27f3f12fc194..fda73cbb5fa0 100644
--- 
a/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifEndpoint.java
+++ 
b/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifEndpoint.java
@@ -22,6 +22,7 @@ import org.apache.camel.Processor;
 import org.apache.camel.Producer;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
+import org.apache.camel.spi.UriParam;
 import org.apache.camel.spi.UriPath;
 import org.apache.camel.support.DefaultEndpoint;
 
@@ -34,6 +35,8 @@ public class LdifEndpoint extends DefaultEndpoint {
     @UriPath
     @Metadata(required = true)
     private String ldapConnectionName;
+    @UriParam(label = "security", defaultValue = "false", security = 
"insecure:dev")
+    private boolean allowUrlBody;
 
     protected LdifEndpoint(String endpointUri, String remaining, LdifComponent 
component) {
         super(endpointUri, component);
@@ -61,4 +64,18 @@ public class LdifEndpoint extends DefaultEndpoint {
     public void setLdapConnectionName(String ldapConnectionName) {
         this.ldapConnectionName = ldapConnectionName;
     }
+
+    public boolean isAllowUrlBody() {
+        return allowUrlBody;
+    }
+
+    /**
+     * Whether to allow a message body that is not LDIF content to be 
dereferenced as a URL and fetched. When disabled
+     * (default), a body that does not start with <tt>version: 1</tt> is 
rejected with an
+     * {@link IllegalArgumentException} instead of being fetched as a URL, 
which avoids a content-sniffed URL fetch
+     * (SSRF) from untrusted body content.
+     */
+    public void setAllowUrlBody(boolean allowUrlBody) {
+        this.allowUrlBody = allowUrlBody;
+    }
 }
diff --git 
a/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifProducer.java
 
b/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifProducer.java
index 98c86f832c60..8b73240ea138 100644
--- 
a/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifProducer.java
+++ 
b/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifProducer.java
@@ -59,8 +59,11 @@ public class LdifProducer extends DefaultProducer {
      * Process the body. There are two options:
      * <ol>
      * <li>A String body that is the LDIF content. This needs to start with 
"version: 1".</li>
-     * <li>A String body that is a URL to ready the LDIF content from</li>
+     * <li>A String body that is a URL to read the LDIF content from - only 
when the allowUrlBody option is
+     * enabled.</li>
      * </ol>
+     * When the body is not LDIF content and allowUrlBody is disabled (the 
default), an {@link IllegalArgumentException}
+     * is thrown instead of dereferencing the body as a URL.
      */
     @Override
     public void process(Exchange exchange) throws Exception {
@@ -76,7 +79,7 @@ public class LdifProducer extends DefaultProducer {
         } else if (body.startsWith(LDIF_HEADER)) {
             LOG.debug("Reading from LDIF body");
             result = processLdif(new StringReader(body));
-        } else {
+        } else if (((LdifEndpoint) getEndpoint()).isAllowUrlBody()) {
             URL loc;
             try {
                 loc = URI.create(body).toURL();
@@ -88,6 +91,10 @@ public class LdifProducer extends DefaultProducer {
                 }
                 throw new InvalidPayloadException(exchange, String.class);
             }
+        } else {
+            throw new IllegalArgumentException(
+                    "LDIF body does not start with '" + LDIF_HEADER
+                                               + "'. To dereference a non-LDIF 
body as a URL, enable the allowUrlBody option on the ldif endpoint.");
         }
 
         exchange.getMessage().setBody(result);
diff --git 
a/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifAllowUrlBodyTest.java
 
b/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifAllowUrlBodyTest.java
new file mode 100644
index 000000000000..22be9280414e
--- /dev/null
+++ 
b/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifAllowUrlBodyTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.ldif;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A non-LDIF body (one that does not start with {@code version: 1}) is 
dereferenced as a URL only when
+ * {@code allowUrlBody} is enabled. By default it is rejected instead of being 
fetched, which avoids a content-sniffed
+ * URL fetch (SSRF) from untrusted body content. The rejection happens before 
any LDAP connection is used, so this test
+ * needs no LDAP server. See CAMEL-24297.
+ */
+class LdifAllowUrlBodyTest extends CamelTestSupport {
+
+    @Test
+    void nonLdifBodyIsRejectedByDefault() {
+        CamelExecutionException ex = 
assertThrows(CamelExecutionException.class,
+                () -> template.sendBody("direct:ldif", 
"http://example.com/evil.ldif";));
+        assertInstanceOf(IllegalArgumentException.class, ex.getCause());
+        assertTrue(ex.getCause().getMessage().contains("allowUrlBody"));
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:ldif").to("ldif:myConnection");
+            }
+        };
+    }
+}
diff --git 
a/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifRouteIT.java
 
b/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifRouteIT.java
index 5c547423dcfd..81159d936b64 100644
--- 
a/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifRouteIT.java
+++ 
b/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifRouteIT.java
@@ -61,7 +61,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 public class LdifRouteIT extends LdifTestSupport {
     // Constants
     private static final String LDAP_CONN_NAME = "conn";
-    private static final String ENDPOINT_LDIF = "ldif:" + LDAP_CONN_NAME;
+    // these tests feed a URL as the body, so URL dereferencing must be 
explicitly enabled (CAMEL-24297)
+    private static final String ENDPOINT_LDIF = "ldif:" + LDAP_CONN_NAME + 
"?allowUrlBody=true";
     private static final String ENDPOINT_START = "direct:start";
     private static final String ENDPOINT_SETUP_START = "direct:setup";
     private static final SearchControls SEARCH_CONTROLS
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 730ca59ab04c..1be9151d837a 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
@@ -60,6 +60,7 @@ public final class SecurityUtils {
         map.put("allowjavaserializedobject", new 
SecurityOption(INSECURE_SERIALIZATION, "true"));
         map.put("allowlocalwebhookurls", new SecurityOption(INSECURE_DEV, 
"true"));
         map.put("allowserializedheaders", new 
SecurityOption(INSECURE_SERIALIZATION, "true"));
+        map.put("allowurlbody", new SecurityOption(INSECURE_DEV, "true"));
         map.put("devconsoleenabled", new SecurityOption(INSECURE_DEV, "true"));
         map.put("downloadenabled", new SecurityOption(INSECURE_DEV, "true"));
         map.put("failonunknownhost", new SecurityOption(INSECURE_SSL, 
VALUE_FALSE));
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 0ab406ff49dd..53f4ccba879e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -1537,6 +1537,15 @@ data format and the iterator/splitter modes. The full, 
unmodified entry name rem
 so routes that intentionally recreate the archive's directory structure keep 
working — read it
 from `CamelTarFileEntryName` for tar and from `zipFileName` for zip instead of 
`CamelFileName`.
 
+=== camel-ldif - a non-LDIF body is no longer dereferenced as a URL by default
+
+The ldif producer previously treated a message body that does not start with 
`version: 1` as a URL
+and dereferenced it (`URI.create(body).toURL().openStream()`). This 
content-sniffed URL fetch is now
+opt-in: a new `allowUrlBody` option (default `false`) gates it. With the 
default, a body that is not
+LDIF content is rejected with an `IllegalArgumentException` instead of being 
fetched, which avoids a
+content-sniffed URL fetch (SSRF) from untrusted body content. Routes that rely 
on passing a URL as the
+body must set `allowUrlBody=true` on the `ldif` endpoint.
+
 === camel-snakeyaml - typeFilters are now also enforced by the SnakeYAML 
TagInspector
 
 When `typeFilters` (or `unmarshalType`) is configured, the allow-list is now 
also enforced by the
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LdifEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LdifEndpointBuilderFactory.java
index 27a52ad1ee1a..293173ed2898 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LdifEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LdifEndpointBuilderFactory.java
@@ -44,6 +44,46 @@ public interface LdifEndpointBuilderFactory {
             return (AdvancedLdifEndpointBuilder) this;
         }
 
+        /**
+         * Whether to allow a message body that is not LDIF content to be
+         * dereferenced as a URL and fetched. When disabled (default), a body
+         * that does not start with version: 1 is rejected with an
+         * IllegalArgumentException instead of being fetched as a URL, which
+         * avoids a content-sniffed URL fetch (SSRF) from untrusted body
+         * content.
+         * 
+         * The option is a: <code>boolean</code> type.
+         * 
+         * Default: false
+         * Group: security
+         * 
+         * @param allowUrlBody the value to set
+         * @return the dsl builder
+         */
+        default LdifEndpointBuilder allowUrlBody(boolean allowUrlBody) {
+            doSetProperty("allowUrlBody", allowUrlBody);
+            return this;
+        }
+        /**
+         * Whether to allow a message body that is not LDIF content to be
+         * dereferenced as a URL and fetched. When disabled (default), a body
+         * that does not start with version: 1 is rejected with an
+         * IllegalArgumentException instead of being fetched as a URL, which
+         * avoids a content-sniffed URL fetch (SSRF) from untrusted body
+         * content.
+         * 
+         * The option will be converted to a <code>boolean</code> type.
+         * 
+         * Default: false
+         * Group: security
+         * 
+         * @param allowUrlBody the value to set
+         * @return the dsl builder
+         */
+        default LdifEndpointBuilder allowUrlBody(String allowUrlBody) {
+            doSetProperty("allowUrlBody", allowUrlBody);
+            return this;
+        }
     }
 
     /**

Reply via email to