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 4b1fc499a0ce CAMEL-24677: camel-smooks - align XML reader
configuration with the other XML components (#26270)
4b1fc499a0ce is described below
commit 4b1fc499a0ceff909612b5310152cd71517888f7
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Sep 10 17:02:54 2026 +0200
CAMEL-24677: camel-smooks - align XML reader configuration with the other
XML components (#26270)
The Smooks data format and component parsed XML input with the default
reader, which
resolves external XML entities, while the other Camel XML components
disable external
entity resolution. Untrusted XML is now parsed with a parser that does not
resolve
external general or parameter entities before it is handed to Smooks.
The Smooks reader is shared across every source type, and reconfiguring it
breaks the
non-XML readers used for marshalling and for EDI, CSV and JSON, so
untrusted XML is
parsed into a DOM with a hardened DocumentBuilder. This applies only when
the
configuration parses XML with the default reader; configurations that
declare their
own reader, and the marshalling direction, are unchanged.
A new allowExternalEntities option (default false) on the data format and
the endpoint
restores the previous behaviour.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
.../apache/camel/catalog/components/smooks.json | 3 +-
.../apache/camel/catalog/dataformats/smooks.json | 3 +-
.../org/apache/camel/catalog/models/smooks.json | 3 +-
.../apache/camel/catalog/schemas/camel-spring.xsd | 11 ++
.../apache/camel/catalog/schemas/camel-xml-io.xsd | 11 ++
.../camel/catalog/schemas/camelYamlDsl-model.json | 11 ++
.../component/smooks/SmooksEndpointConfigurer.java | 6 +
.../component/smooks/SmooksEndpointUriFactory.java | 3 +-
.../smooks/SmooksDataFormatConfigurer.java | 7 ++
.../org/apache/camel/component/smooks/smooks.json | 3 +-
.../org/apache/camel/dataformat/smooks/smooks.json | 3 +-
.../camel/component/smooks/SmooksEndpoint.java | 14 +++
.../camel/component/smooks/SmooksProcessor.java | 29 ++++-
.../component/smooks/SmooksSecuritySupport.java | 113 ++++++++++++++++++
.../camel/dataformat/smooks/SmooksDataFormat.java | 26 ++++-
.../smooks/SmooksComponentXmlSecurityTest.java | 90 ++++++++++++++
.../smooks/SmooksDataFormatXmlSecurityTest.java | 129 +++++++++++++++++++++
.../org/apache/camel/model/dataformat/smooks.json | 3 +-
.../camel/model/dataformat/SmooksDataFormat.java | 40 +++++++
.../dataformat/SmooksDataFormatReifier.java | 1 +
.../apache/camel/java/out/JavaDslModelWriter.java | 1 +
.../java/org/apache/camel/util/SecurityUtils.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 +
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 16 +++
.../endpoint/dsl/SmooksEndpointBuilderFactory.java | 38 ++++++
.../dsl/yaml/deserializers/ModelDeserializers.java | 6 +
.../resources/schema/camelYamlDsl-canonical.json | 6 +
.../resources/schema/camelYamlDsl-model.json | 11 ++
.../generated/resources/schema/camelYamlDsl.json | 6 +
31 files changed, 585 insertions(+), 12 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/smooks.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/smooks.json
index 6a4b6313c345..cba26f0cf7d9 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/smooks.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/smooks.json
@@ -35,6 +35,7 @@
"smooksConfig": { "index": 0, "kind": "path", "displayName": "Smooks
Config", "group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "supportFileReference": true,
"description": "Path to the Smooks configuration file" },
"reportPath": { "index": 1, "kind": "parameter", "displayName": "Report
Path", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "File path to place the generated HTML
execution report. The report is a useful tool in the developers arsenal for
diagnosing issues or comprehending a transformation. Do not set in production
since this is a major performance drain" },
"lazyStartProducer": { "index": 2, "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 [...]
- "allowExecutionContextFromHeader": { "index": 3, "kind": "parameter",
"displayName": "Allow Execution Context From Header", "group": "advanced",
"label": "advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allow execution context to be set from
the CamelSmooksExecutionContext header" }
+ "allowExecutionContextFromHeader": { "index": 3, "kind": "parameter",
"displayName": "Allow Execution Context From Header", "group": "advanced",
"label": "advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allow execution context to be set from
the CamelSmooksExecutionContext header" },
+ "allowExternalEntities": { "index": 4, "kind": "parameter", "displayName":
"Allow External Entities", "group": "security", "label": "security",
"required": false, "type": "boolean", "javaType": "boolean", "deprecated":
false, "autowired": false, "secret": false, "security": "insecure:dev",
"insecureValue": "true", "defaultValue": false, "description": "Whether to
allow the XML reader used by Smooks to resolve external XML entities (external
general and parameter entities) when parsin [...]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/smooks.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/smooks.json
index be2dd625cb79..dc4c6f528076 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/smooks.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/smooks.json
@@ -17,6 +17,7 @@
},
"properties": {
"id": { "index": 0, "kind": "attribute", "displayName": "Id", "group":
"common", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "The
id of this node" },
- "smooksConfig": { "index": 1, "kind": "attribute", "displayName": "Smooks
Config", "group": "common", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Path to the Smooks configuration file." }
+ "smooksConfig": { "index": 1, "kind": "attribute", "displayName": "Smooks
Config", "group": "common", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Path to the Smooks configuration file." },
+ "allowExternalEntities": { "index": 2, "kind": "attribute", "displayName":
"Allow External Entities", "group": "security", "label": "security",
"required": false, "type": "boolean", "javaType": "java.lang.Boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "description": "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external general and parameter entities) when
parsing XML input. This is disabled by default so [...]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/smooks.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/smooks.json
index eeb80f9ecef6..d6f7a9210412 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/smooks.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/smooks.json
@@ -14,6 +14,7 @@
},
"properties": {
"id": { "index": 0, "kind": "attribute", "displayName": "Id", "group":
"common", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "The
id of this node" },
- "smooksConfig": { "index": 1, "kind": "attribute", "displayName": "Smooks
Config", "group": "common", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Path to the Smooks configuration file." }
+ "smooksConfig": { "index": 1, "kind": "attribute", "displayName": "Smooks
Config", "group": "common", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Path to the Smooks configuration file." },
+ "allowExternalEntities": { "index": 2, "kind": "attribute", "displayName":
"Allow External Entities", "group": "security", "label": "security",
"required": false, "type": "boolean", "javaType": "java.lang.Boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "description": "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external general and parameter entities) when
parsing XML input. This is disabled by default so [...]
}
}
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 a24d1842ee16..74709394fc46 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
@@ -10026,6 +10026,17 @@ value: true
<xs:documentation xml:lang="en">
<![CDATA[
Path to the Smooks configuration file.
+]]>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="allowExternalEntities" type="xs:string">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+<![CDATA[
+Whether to allow the XML reader used by Smooks to resolve external XML
entities (external general and parameter
+entities) when parsing XML input. This is disabled by default so that external
entities in the message body are not
+resolved; enable it only for trusted legacy configurations that rely on
external entity resolution. Default value: false
]]>
</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 59314ecc11c7..c0902dd541fe 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
@@ -9148,6 +9148,17 @@ value: true
<xs:documentation xml:lang="en">
<![CDATA[
Path to the Smooks configuration file.
+]]>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="allowExternalEntities" type="xs:string">
+ <xs:annotation>
+ <xs:documentation xml:lang="en">
+<![CDATA[
+Whether to allow the XML reader used by Smooks to resolve external XML
entities (external general and parameter
+entities) when parsing XML input. This is disabled by default so that external
entities in the message body are not
+resolved; enable it only for trusted legacy configurations that rely on
external entity resolution. Default value: false
]]>
</xs:documentation>
</xs:annotation>
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
index ee751df7923e..f2c0369f14ce 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json
@@ -13238,6 +13238,17 @@
"kind" : "attribute",
"index" : 1,
"group" : "common"
+ }, {
+ "name" : "allowExternalEntities",
+ "type" : "boolean",
+ "description" : "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external general and parameter entities) when
parsing XML input. This is disabled by default so that external entities in the
message body are not resolved; enable it only for trusted legacy configurations
that rely on external entity resolution.",
+ "title" : "Allow External Entities",
+ "default" : "false",
+ "displayName" : "Allow External Entities",
+ "kind" : "attribute",
+ "index" : 2,
+ "group" : "security",
+ "label" : "security"
} ]
},
"soap" : {
diff --git
a/components/camel-smooks/src/generated/java/org/apache/camel/component/smooks/SmooksEndpointConfigurer.java
b/components/camel-smooks/src/generated/java/org/apache/camel/component/smooks/SmooksEndpointConfigurer.java
index 5358bdf4c616..49056ad6a61b 100644
---
a/components/camel-smooks/src/generated/java/org/apache/camel/component/smooks/SmooksEndpointConfigurer.java
+++
b/components/camel-smooks/src/generated/java/org/apache/camel/component/smooks/SmooksEndpointConfigurer.java
@@ -25,6 +25,8 @@ public class SmooksEndpointConfigurer extends
PropertyConfigurerSupport implemen
switch (ignoreCase ? name.toLowerCase() : name) {
case "allowexecutioncontextfromheader":
case "allowExecutionContextFromHeader":
target.setAllowExecutionContextFromHeader(property(camelContext,
java.lang.Boolean.class, value)); return true;
+ case "allowexternalentities":
+ case "allowExternalEntities":
target.setAllowExternalEntities(property(camelContext, boolean.class, value));
return true;
case "lazystartproducer":
case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
case "reportpath":
@@ -38,6 +40,8 @@ public class SmooksEndpointConfigurer extends
PropertyConfigurerSupport implemen
switch (ignoreCase ? name.toLowerCase() : name) {
case "allowexecutioncontextfromheader":
case "allowExecutionContextFromHeader": return java.lang.Boolean.class;
+ case "allowexternalentities":
+ case "allowExternalEntities": return boolean.class;
case "lazystartproducer":
case "lazyStartProducer": return boolean.class;
case "reportpath":
@@ -52,6 +56,8 @@ public class SmooksEndpointConfigurer extends
PropertyConfigurerSupport implemen
switch (ignoreCase ? name.toLowerCase() : name) {
case "allowexecutioncontextfromheader":
case "allowExecutionContextFromHeader": return
target.getAllowExecutionContextFromHeader();
+ case "allowexternalentities":
+ case "allowExternalEntities": return target.isAllowExternalEntities();
case "lazystartproducer":
case "lazyStartProducer": return target.isLazyStartProducer();
case "reportpath":
diff --git
a/components/camel-smooks/src/generated/java/org/apache/camel/component/smooks/SmooksEndpointUriFactory.java
b/components/camel-smooks/src/generated/java/org/apache/camel/component/smooks/SmooksEndpointUriFactory.java
index 152206165aa4..babc444727b9 100644
---
a/components/camel-smooks/src/generated/java/org/apache/camel/component/smooks/SmooksEndpointUriFactory.java
+++
b/components/camel-smooks/src/generated/java/org/apache/camel/component/smooks/SmooksEndpointUriFactory.java
@@ -24,8 +24,9 @@ public class SmooksEndpointUriFactory extends
org.apache.camel.support.component
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(4);
+ Set<String> props = new HashSet<>(5);
props.add("allowExecutionContextFromHeader");
+ props.add("allowExternalEntities");
props.add("lazyStartProducer");
props.add("reportPath");
props.add("smooksConfig");
diff --git
a/components/camel-smooks/src/generated/java/org/apache/camel/dataformat/smooks/SmooksDataFormatConfigurer.java
b/components/camel-smooks/src/generated/java/org/apache/camel/dataformat/smooks/SmooksDataFormatConfigurer.java
index 67a92fc0be7a..319969c9ce98 100644
---
a/components/camel-smooks/src/generated/java/org/apache/camel/dataformat/smooks/SmooksDataFormatConfigurer.java
+++
b/components/camel-smooks/src/generated/java/org/apache/camel/dataformat/smooks/SmooksDataFormatConfigurer.java
@@ -22,6 +22,7 @@ public class SmooksDataFormatConfigurer extends
org.apache.camel.support.compone
private static final Map<String, Object> ALL_OPTIONS;
static {
Map<String, Object> map = new CaseInsensitiveMap();
+ map.put("AllowExternalEntities", boolean.class);
map.put("SmooksConfig", java.lang.String.class);
ALL_OPTIONS = map;
}
@@ -30,6 +31,8 @@ public class SmooksDataFormatConfigurer extends
org.apache.camel.support.compone
public boolean configure(CamelContext camelContext, Object obj, String
name, Object value, boolean ignoreCase) {
SmooksDataFormat target = (SmooksDataFormat) obj;
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "allowexternalentities":
+ case "allowExternalEntities":
target.setAllowExternalEntities(property(camelContext, boolean.class, value));
return true;
case "smooksconfig":
case "smooksConfig": target.setSmooksConfig(property(camelContext,
java.lang.String.class, value)); return true;
default: return false;
@@ -44,6 +47,8 @@ public class SmooksDataFormatConfigurer extends
org.apache.camel.support.compone
@Override
public Class<?> getOptionType(String name, boolean ignoreCase) {
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "allowexternalentities":
+ case "allowExternalEntities": return boolean.class;
case "smooksconfig":
case "smooksConfig": return java.lang.String.class;
default: return null;
@@ -54,6 +59,8 @@ public class SmooksDataFormatConfigurer extends
org.apache.camel.support.compone
public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
SmooksDataFormat target = (SmooksDataFormat) obj;
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "allowexternalentities":
+ case "allowExternalEntities": return target.isAllowExternalEntities();
case "smooksconfig":
case "smooksConfig": return target.getSmooksConfig();
default: return null;
diff --git
a/components/camel-smooks/src/generated/resources/META-INF/org/apache/camel/component/smooks/smooks.json
b/components/camel-smooks/src/generated/resources/META-INF/org/apache/camel/component/smooks/smooks.json
index 6a4b6313c345..cba26f0cf7d9 100644
---
a/components/camel-smooks/src/generated/resources/META-INF/org/apache/camel/component/smooks/smooks.json
+++
b/components/camel-smooks/src/generated/resources/META-INF/org/apache/camel/component/smooks/smooks.json
@@ -35,6 +35,7 @@
"smooksConfig": { "index": 0, "kind": "path", "displayName": "Smooks
Config", "group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "supportFileReference": true,
"description": "Path to the Smooks configuration file" },
"reportPath": { "index": 1, "kind": "parameter", "displayName": "Report
Path", "group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "File path to place the generated HTML
execution report. The report is a useful tool in the developers arsenal for
diagnosing issues or comprehending a transformation. Do not set in production
since this is a major performance drain" },
"lazyStartProducer": { "index": 2, "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 [...]
- "allowExecutionContextFromHeader": { "index": 3, "kind": "parameter",
"displayName": "Allow Execution Context From Header", "group": "advanced",
"label": "advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allow execution context to be set from
the CamelSmooksExecutionContext header" }
+ "allowExecutionContextFromHeader": { "index": 3, "kind": "parameter",
"displayName": "Allow Execution Context From Header", "group": "advanced",
"label": "advanced", "required": false, "type": "boolean", "javaType":
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allow execution context to be set from
the CamelSmooksExecutionContext header" },
+ "allowExternalEntities": { "index": 4, "kind": "parameter", "displayName":
"Allow External Entities", "group": "security", "label": "security",
"required": false, "type": "boolean", "javaType": "boolean", "deprecated":
false, "autowired": false, "secret": false, "security": "insecure:dev",
"insecureValue": "true", "defaultValue": false, "description": "Whether to
allow the XML reader used by Smooks to resolve external XML entities (external
general and parameter entities) when parsin [...]
}
}
diff --git
a/components/camel-smooks/src/generated/resources/META-INF/org/apache/camel/dataformat/smooks/smooks.json
b/components/camel-smooks/src/generated/resources/META-INF/org/apache/camel/dataformat/smooks/smooks.json
index be2dd625cb79..dc4c6f528076 100644
---
a/components/camel-smooks/src/generated/resources/META-INF/org/apache/camel/dataformat/smooks/smooks.json
+++
b/components/camel-smooks/src/generated/resources/META-INF/org/apache/camel/dataformat/smooks/smooks.json
@@ -17,6 +17,7 @@
},
"properties": {
"id": { "index": 0, "kind": "attribute", "displayName": "Id", "group":
"common", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "The
id of this node" },
- "smooksConfig": { "index": 1, "kind": "attribute", "displayName": "Smooks
Config", "group": "common", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Path to the Smooks configuration file." }
+ "smooksConfig": { "index": 1, "kind": "attribute", "displayName": "Smooks
Config", "group": "common", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Path to the Smooks configuration file." },
+ "allowExternalEntities": { "index": 2, "kind": "attribute", "displayName":
"Allow External Entities", "group": "security", "label": "security",
"required": false, "type": "boolean", "javaType": "java.lang.Boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "description": "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external general and parameter entities) when
parsing XML input. This is disabled by default so [...]
}
}
diff --git
a/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksEndpoint.java
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksEndpoint.java
index 22ee794fba87..a86a20829bbb 100644
---
a/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksEndpoint.java
+++
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksEndpoint.java
@@ -46,6 +46,12 @@ public class SmooksEndpoint extends DefaultEndpoint {
label = "advanced",
defaultValue = "false")
private Boolean allowExecutionContextFromHeader = false;
+ @UriParam(description = "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external"
+ + " general and parameter entities) when parsing
XML input. This is disabled by default so"
+ + " that external entities in the message body are
not resolved; enable it only for trusted"
+ + " legacy configurations that rely on external
entity resolution.",
+ label = "security", defaultValue = "false", security =
"insecure:dev", insecureValue = "true")
+ private boolean allowExternalEntities;
private final SmooksProcessor smooksProcessor;
@@ -99,4 +105,12 @@ public class SmooksEndpoint extends DefaultEndpoint {
public void setAllowExecutionContextFromHeader(Boolean
allowExecutionContextFromHeader) {
this.allowExecutionContextFromHeader = allowExecutionContextFromHeader;
}
+
+ public boolean isAllowExternalEntities() {
+ return allowExternalEntities;
+ }
+
+ public void setAllowExternalEntities(boolean allowExternalEntities) {
+ this.allowExternalEntities = allowExternalEntities;
+ }
}
diff --git
a/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksProcessor.java
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksProcessor.java
index 832a448cc7ff..1f5124526b01 100644
---
a/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksProcessor.java
+++
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksProcessor.java
@@ -85,6 +85,8 @@ public class SmooksProcessor extends ServiceSupport
implements Processor, CamelC
private String reportPath;
private Boolean allowExecutionContextFromHeader = false;
private Boolean lazyStartProducer = false;
+ private boolean allowExternalEntities;
+ private boolean hardenXmlInput;
private final Set<VisitorAppender> visitorAppender = new HashSet<>();
private final Map<String, Visitor> selectorVisitorMap = new HashMap<>();
@@ -120,6 +122,14 @@ public class SmooksProcessor extends ServiceSupport
implements Processor, CamelC
this.allowExecutionContextFromHeader = allowExecutionContextFromHeader;
}
+ public boolean isAllowExternalEntities() {
+ return allowExternalEntities;
+ }
+
+ public void setAllowExternalEntities(boolean allowExternalEntities) {
+ this.allowExternalEntities = allowExternalEntities;
+ }
+
public void process(final Exchange exchange) {
ExecutionContext executionContext = null;
if (allowExecutionContextFromHeader) {
@@ -188,12 +198,21 @@ public class SmooksProcessor extends ServiceSupport
implements Processor, CamelC
return source;
}
- if (payload instanceof byte[] byteArray) {
- return new ByteSource(byteArray);
+ if (payload instanceof Node node) {
+ return new DOMSource(node);
+ }
+
+ if (hardenXmlInput) {
+ // Untrusted XML: parse with a reader that does not resolve
external entities before Smooks sees it
+ try {
+ return
SmooksSecuritySupport.secureXmlSource(exchange.getIn().getMandatoryBody(InputStream.class));
+ } catch (IOException | SAXException e) {
+ throw new SmooksException(e);
+ }
}
- if (payload instanceof Node) {
- return new DOMSource((Node) payload);
+ if (payload instanceof byte[] byteArray) {
+ return new ByteSource(byteArray);
}
if (payload instanceof InputStream inputstream) {
@@ -300,6 +319,8 @@ public class SmooksProcessor extends ServiceSupport
implements Processor, CamelC
(NotAppContextScoped.Ref<CamelContext>) () ->
camelContext);
}
+ hardenXmlInput = !allowExternalEntities &&
SmooksSecuritySupport.usesDefaultXmlReader(smooks);
+
addAppender(smooks, visitorAppender);
addVisitor(smooks, selectorVisitorMap);
diff --git
a/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksSecuritySupport.java
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksSecuritySupport.java
new file mode 100644
index 000000000000..a8d7174b1410
--- /dev/null
+++
b/components/camel-smooks/src/main/java/org/apache/camel/component/smooks/SmooksSecuritySupport.java
@@ -0,0 +1,113 @@
+/*
+ * 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.smooks;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import org.smooks.Smooks;
+import org.smooks.api.Registry;
+import org.smooks.api.io.Source;
+import org.smooks.api.resource.config.ResourceConfig;
+import org.smooks.engine.lookup.ResourceConfigsLookup;
+import org.smooks.engine.resource.config.DefaultConfigSearch;
+import org.smooks.io.source.DOMSource;
+
+/**
+ * Secures the XML input handed to Smooks against XML external entity
resolution.
+ * <p/>
+ * When Smooks parses XML with its default reader (Woodstox), external XML
entities are resolved. Rather than
+ * reconfiguring the Smooks reader - which is shared across every source type
and would break the non-XML readers used
+ * for marshalling and for EDI/CSV/JSON - this helper parses untrusted XML
with a {@code DocumentBuilder} that does not
+ * resolve external entities, and hands Smooks the resulting DOM. This matches
the secure XML parser configuration
+ * applied by the other Camel XML components.
+ * <p/>
+ * Hardening is only relevant when the Smooks configuration parses XML with
its default reader. Configurations that
+ * declare their own reader (for example EDI, CSV, JSON, or a custom reader)
do not use the XML reader and are left
+ * untouched.
+ */
+public final class SmooksSecuritySupport {
+
+ static final String ORG_XML_SAX_DRIVER = "org.xml.sax.driver";
+
+ private static final String EXTERNAL_GENERAL_ENTITIES =
"http://xml.org/sax/features/external-general-entities";
+ private static final String EXTERNAL_PARAMETER_ENTITIES =
"http://xml.org/sax/features/external-parameter-entities";
+ private static final String LOAD_EXTERNAL_DTD =
"http://apache.org/xml/features/nonvalidating/load-external-dtd";
+
+ private static final DocumentBuilderFactory
SECURE_DOCUMENT_BUILDER_FACTORY = createSecureDocumentBuilderFactory();
+
+ private SmooksSecuritySupport() {
+ }
+
+ /**
+ * Returns {@code true} when the given Smooks instance parses input with
its default XML reader, i.e. the
+ * configuration does not declare its own reader (such as EDI, CSV, JSON,
or a custom reader). Only the default XML
+ * reader resolves XML external entities, so only that case needs
hardening.
+ */
+ public static boolean usesDefaultXmlReader(Smooks smooks) {
+ Registry registry = smooks.getApplicationContext().getRegistry();
+ List<ResourceConfig> readerConfigs
+ = registry.lookup(new ResourceConfigsLookup(registry, new
DefaultConfigSearch().selector(ORG_XML_SAX_DRIVER)));
+ return readerConfigs == null || readerConfigs.isEmpty();
+ }
+
+ /**
+ * Parses the given XML input stream with a parser that does not resolve
external entities and wraps it as a Smooks
+ * {@link Source} backed by the resulting DOM.
+ */
+ public static Source secureXmlSource(InputStream inputStream) throws
IOException, SAXException {
+ DocumentBuilder documentBuilder;
+ synchronized (SECURE_DOCUMENT_BUILDER_FACTORY) {
+ try {
+ documentBuilder =
SECURE_DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new SAXException(e);
+ }
+ }
+ Document document = documentBuilder.parse(new
InputSource(inputStream));
+ return new DOMSource(document);
+ }
+
+ private static DocumentBuilderFactory createSecureDocumentBuilderFactory()
{
+ DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ setFeature(documentBuilderFactory,
XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ setFeature(documentBuilderFactory, EXTERNAL_GENERAL_ENTITIES, false);
+ setFeature(documentBuilderFactory, EXTERNAL_PARAMETER_ENTITIES, false);
+ setFeature(documentBuilderFactory, LOAD_EXTERNAL_DTD, false);
+ return documentBuilderFactory;
+ }
+
+ private static void setFeature(DocumentBuilderFactory
documentBuilderFactory, String feature, boolean value) {
+ try {
+ documentBuilderFactory.setFeature(feature, value);
+ } catch (ParserConfigurationException e) {
+ // The parser in use does not recognise this feature; the
remaining controls still provide protection
+ }
+ }
+}
diff --git
a/components/camel-smooks/src/main/java/org/apache/camel/dataformat/smooks/SmooksDataFormat.java
b/components/camel-smooks/src/main/java/org/apache/camel/dataformat/smooks/SmooksDataFormat.java
index fe9728725b5c..fb979ccdf3a8 100644
---
a/components/camel-smooks/src/main/java/org/apache/camel/dataformat/smooks/SmooksDataFormat.java
+++
b/components/camel-smooks/src/main/java/org/apache/camel/dataformat/smooks/SmooksDataFormat.java
@@ -29,6 +29,7 @@ import org.apache.camel.Exchange;
import org.apache.camel.TypeConverter;
import org.apache.camel.component.smooks.SmooksComponent;
import org.apache.camel.component.smooks.SmooksProcessor;
+import org.apache.camel.component.smooks.SmooksSecuritySupport;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.spi.annotations.Dataformat;
import org.apache.camel.support.CamelContextHelper;
@@ -40,6 +41,7 @@ import org.smooks.SmooksFactory;
import org.smooks.api.ExecutionContext;
import org.smooks.api.SmooksException;
import org.smooks.api.io.Sink;
+import org.smooks.api.io.Source;
import org.smooks.engine.lookup.ExportsLookup;
import org.smooks.io.payload.Exports;
import org.smooks.io.sink.StringSink;
@@ -63,6 +65,8 @@ public class SmooksDataFormat extends ServiceSupport
implements DataFormat, Came
private Smooks smooks;
private CamelContext camelContext;
private String smooksConfig;
+ private boolean allowExternalEntities;
+ private boolean hardenXmlInput;
/**
* Marshals the Object 'fromBody' to an OutputStream 'toStream'
@@ -100,7 +104,18 @@ public class SmooksDataFormat extends ServiceSupport
implements DataFormat, Came
final ExecutionContext executionContext =
smooks.createExecutionContext();
final Exports exports =
smooks.getApplicationContext().getRegistry().lookup(new ExportsLookup());
final Sink[] sinks = exports.createSinks();
- smooks.filterSource(executionContext, new StreamSource<>(fromStream),
sinks);
+ final Source source;
+ if (hardenXmlInput) {
+ // Untrusted XML: parse with a reader that does not resolve
external entities before Smooks sees it
+ try {
+ source = SmooksSecuritySupport.secureXmlSource(fromStream);
+ } catch (IOException | SAXException e) {
+ throw new SmooksException(e.getMessage(), e);
+ }
+ } else {
+ source = new StreamSource<>(fromStream);
+ }
+ smooks.filterSource(executionContext, source, sinks);
return getResult(exports, sinks, exchange);
}
@@ -135,6 +150,7 @@ public class SmooksDataFormat extends ServiceSupport
implements DataFormat, Came
} catch (IOException | SAXException e) {
throw new SmooksException(e.getMessage(), e);
}
+ hardenXmlInput = !allowExternalEntities &&
SmooksSecuritySupport.usesDefaultXmlReader(smooks);
}
@Override
@@ -151,4 +167,12 @@ public class SmooksDataFormat extends ServiceSupport
implements DataFormat, Came
public void setSmooksConfig(String smooksConfig) {
this.smooksConfig = smooksConfig;
}
+
+ public boolean isAllowExternalEntities() {
+ return allowExternalEntities;
+ }
+
+ public void setAllowExternalEntities(boolean allowExternalEntities) {
+ this.allowExternalEntities = allowExternalEntities;
+ }
}
diff --git
a/components/camel-smooks/src/test/java/org/apache/camel/component/smooks/SmooksComponentXmlSecurityTest.java
b/components/camel-smooks/src/test/java/org/apache/camel/component/smooks/SmooksComponentXmlSecurityTest.java
new file mode 100644
index 000000000000..d8b932084922
--- /dev/null
+++
b/components/camel-smooks/src/test/java/org/apache/camel/component/smooks/SmooksComponentXmlSecurityTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.smooks;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dataformat.smooks.Customer;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies that the Smooks component does not resolve external XML entities
from the message body by default, and that
+ * the legacy behaviour can be explicitly restored with {@code
allowExternalEntities=true}.
+ */
+public class SmooksComponentXmlSecurityTest extends CamelTestSupport {
+
+ private static final String MARKER = "SECRET_MARKER_do_not_leak";
+
+ @TempDir
+ Path tempDir;
+
+ private Path secretFile;
+
+ @BeforeEach
+ public void writeSecret() throws Exception {
+ secretFile = tempDir.resolve("secret.txt");
+ Files.writeString(secretFile, MARKER);
+ }
+
+ @Test
+ public void externalEntityNotResolvedByDefault() {
+ String firstName;
+ try {
+ Customer customer = template.requestBody("direct:secure",
xxePayload(), Customer.class);
+ firstName = customer != null && customer.getFirstName() != null ?
customer.getFirstName() : "";
+ } catch (Exception e) {
+ // A parser that rejects the external entity reference is the
expected secure behaviour
+ firstName = "";
+ }
+ assertFalse(firstName.contains(MARKER),
+ "External entity must not be resolved by default, but file
content leaked into the bean");
+ }
+
+ @Test
+ public void externalEntityResolvedWhenExplicitlyAllowed() {
+ Customer customer = template.requestBody("direct:insecure",
xxePayload(), Customer.class);
+ // The opt-in restores the legacy behaviour; this also proves the
marker file is genuinely readable, so the
+ // secure test above is meaningful
+ assertTrue(customer.getFirstName().contains(MARKER),
+ "With allowExternalEntities=true the external entity should be
resolved (control assertion)");
+ }
+
+ private String xxePayload() {
+ String systemId = secretFile.toUri().toString();
+ return "<!DOCTYPE customer [<!ENTITY xxe SYSTEM \"" + systemId +
"\">]>\n"
+ + "<customer><firstName>&xxe;</firstName><lastName>d</lastName>"
+ +
"<gender>Male</gender><age>1</age><country>x</country></customer>";
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ public void configure() {
+ from("direct:secure").to("smooks://smooks-config.xml");
+
from("direct:insecure").to("smooks://smooks-config.xml?allowExternalEntities=true");
+ }
+ };
+ }
+}
diff --git
a/components/camel-smooks/src/test/java/org/apache/camel/dataformat/smooks/SmooksDataFormatXmlSecurityTest.java
b/components/camel-smooks/src/test/java/org/apache/camel/dataformat/smooks/SmooksDataFormatXmlSecurityTest.java
new file mode 100644
index 000000000000..12fdc4d4c2ad
--- /dev/null
+++
b/components/camel-smooks/src/test/java/org/apache/camel/dataformat/smooks/SmooksDataFormatXmlSecurityTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.dataformat.smooks;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.apache.camel.support.processor.UnmarshalProcessor;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies that the {@link SmooksDataFormat} does not resolve external XML
entities from the message body by default,
+ * and that the legacy behaviour can be explicitly restored with {@code
allowExternalEntities=true}.
+ */
+public class SmooksDataFormatXmlSecurityTest {
+
+ private static final String SMOOKS_CONFIG = "/smooks-config.xml";
+ private static final String MARKER = "SECRET_MARKER_do_not_leak";
+
+ @TempDir
+ Path tempDir;
+
+ private DefaultCamelContext camelContext;
+ private Path secretFile;
+
+ @BeforeEach
+ public void beforeEach() throws Exception {
+ camelContext = new DefaultCamelContext();
+ camelContext.start();
+ secretFile = tempDir.resolve("secret.txt");
+ Files.writeString(secretFile, MARKER);
+ }
+
+ @AfterEach
+ public void afterEach() {
+ camelContext.stop();
+ }
+
+ @Test
+ public void externalEntityNotResolvedByDefault() throws Exception {
+ SmooksDataFormat dataFormat = newDataFormat(false);
+ try {
+ String firstName = unmarshalFirstNameSafely(dataFormat,
xxePayload());
+ // Whether the parser rejects the external entity outright or
yields empty content, the file must never leak
+ assertFalse(firstName.contains(MARKER),
+ "External entity must not be resolved by default, but file
content leaked into the bean");
+ } finally {
+ dataFormat.stop();
+ }
+ }
+
+ @Test
+ public void externalEntityResolvedWhenExplicitlyAllowed() throws Exception
{
+ SmooksDataFormat dataFormat = newDataFormat(true);
+ try {
+ String firstName = unmarshalFirstName(dataFormat, xxePayload());
+ // The opt-in restores the legacy behaviour; this also proves the
marker file is genuinely readable, so the
+ // secure test above is meaningful
+ assertTrue(firstName.contains(MARKER),
+ "With allowExternalEntities=true the external entity
should be resolved (control assertion)");
+ } finally {
+ dataFormat.stop();
+ }
+ }
+
+ private SmooksDataFormat newDataFormat(boolean allowExternalEntities) {
+ SmooksDataFormat dataFormat = new SmooksDataFormat();
+ dataFormat.setSmooksConfig(SMOOKS_CONFIG);
+ dataFormat.setAllowExternalEntities(allowExternalEntities);
+ dataFormat.setCamelContext(camelContext);
+ dataFormat.start();
+ return dataFormat;
+ }
+
+ private String unmarshalFirstName(SmooksDataFormat dataFormat, String
payload) throws Exception {
+ UnmarshalProcessor unmarshalProcessor = new
UnmarshalProcessor(dataFormat);
+ DefaultExchange exchange = new DefaultExchange(camelContext);
+ exchange.getIn().setBody(new
ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8)));
+ unmarshalProcessor.process(exchange);
+ Customer customer = exchange.getMessage().getBody(Customer.class);
+ return customer.getFirstName();
+ }
+
+ private String unmarshalFirstNameSafely(SmooksDataFormat dataFormat,
String payload) {
+ try {
+ String firstName = unmarshalFirstName(dataFormat, payload);
+ return firstName == null ? "" : firstName;
+ } catch (Exception e) {
+ // A parser that rejects the external entity reference is the
expected secure behaviour
+ return "";
+ }
+ }
+
+ private String xxePayload() {
+ String systemId = secretFile.toUri().toString();
+ return "<!DOCTYPE customer [<!ENTITY xxe SYSTEM \"" + systemId +
"\">]>\n"
+ + "<customer>\n"
+ + " <firstName>&xxe;</firstName>\n"
+ + " <lastName>d</lastName>\n"
+ + " <gender>Male</gender>\n"
+ + " <age>1</age>\n"
+ + " <country>x</country>\n"
+ + "</customer>";
+ }
+}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/dataformat/smooks.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/dataformat/smooks.json
index eeb80f9ecef6..d6f7a9210412 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/dataformat/smooks.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/dataformat/smooks.json
@@ -14,6 +14,7 @@
},
"properties": {
"id": { "index": 0, "kind": "attribute", "displayName": "Id", "group":
"common", "required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "The
id of this node" },
- "smooksConfig": { "index": 1, "kind": "attribute", "displayName": "Smooks
Config", "group": "common", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Path to the Smooks configuration file." }
+ "smooksConfig": { "index": 1, "kind": "attribute", "displayName": "Smooks
Config", "group": "common", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "Path to the Smooks configuration file." },
+ "allowExternalEntities": { "index": 2, "kind": "attribute", "displayName":
"Allow External Entities", "group": "security", "label": "security",
"required": false, "type": "boolean", "javaType": "java.lang.Boolean",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
false, "description": "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external general and parameter entities) when
parsing XML input. This is disabled by default so [...]
}
}
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/SmooksDataFormat.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/SmooksDataFormat.java
index b7e2f453c280..913084984e03 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/SmooksDataFormat.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/SmooksDataFormat.java
@@ -38,6 +38,13 @@ public class SmooksDataFormat extends DataFormatDefinition {
@XmlAttribute
@Metadata(required = true, description = "Path to the Smooks configuration
file.")
private String smooksConfig;
+ @XmlAttribute
+ @Metadata(javaType = "java.lang.Boolean", defaultValue = "false", label =
"security",
+ description = "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external"
+ + " general and parameter entities) when parsing
XML input. This is disabled by default so"
+ + " that external entities in the message body are
not resolved; enable it only for trusted"
+ + " legacy configurations that rely on external
entity resolution.")
+ private String allowExternalEntities;
public SmooksDataFormat() {
super("smooks");
@@ -46,11 +53,13 @@ public class SmooksDataFormat extends DataFormatDefinition {
protected SmooksDataFormat(SmooksDataFormat source) {
super(source);
this.smooksConfig = source.smooksConfig;
+ this.allowExternalEntities = source.allowExternalEntities;
}
private SmooksDataFormat(Builder builder) {
this();
this.smooksConfig = builder.smooksConfig;
+ this.allowExternalEntities = builder.allowExternalEntities;
}
@Override
@@ -66,6 +75,14 @@ public class SmooksDataFormat extends DataFormatDefinition {
return smooksConfig;
}
+ public void setAllowExternalEntities(String allowExternalEntities) {
+ this.allowExternalEntities = allowExternalEntities;
+ }
+
+ public String getAllowExternalEntities() {
+ return allowExternalEntities;
+ }
+
/**
* {@code Builder} is a specific builder for {@link SmooksDataFormat}.
*/
@@ -73,6 +90,7 @@ public class SmooksDataFormat extends DataFormatDefinition {
public static class Builder implements DataFormatBuilder<SmooksDataFormat>
{
private String smooksConfig;
+ private String allowExternalEntities;
/**
* Path to the Smooks configuration file.
@@ -82,6 +100,28 @@ public class SmooksDataFormat extends DataFormatDefinition {
return this;
}
+ /**
+ * Whether to allow the XML reader used by Smooks to resolve external
XML entities (external general and
+ * parameter entities) when parsing XML input. This is disabled by
default so that external entities in the
+ * message body are not resolved; enable it only for trusted legacy
configurations that rely on external entity
+ * resolution.
+ */
+ public Builder allowExternalEntities(String allowExternalEntities) {
+ this.allowExternalEntities = allowExternalEntities;
+ return this;
+ }
+
+ /**
+ * Whether to allow the XML reader used by Smooks to resolve external
XML entities (external general and
+ * parameter entities) when parsing XML input. This is disabled by
default so that external entities in the
+ * message body are not resolved; enable it only for trusted legacy
configurations that rely on external entity
+ * resolution.
+ */
+ public Builder allowExternalEntities(boolean allowExternalEntities) {
+ this.allowExternalEntities =
Boolean.toString(allowExternalEntities);
+ return this;
+ }
+
@Override
public SmooksDataFormat end() {
return new SmooksDataFormat(this);
diff --git
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/SmooksDataFormatReifier.java
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/SmooksDataFormatReifier.java
index e334e6d90292..6082dcaf0707 100644
---
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/SmooksDataFormatReifier.java
+++
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/SmooksDataFormatReifier.java
@@ -31,6 +31,7 @@ public class SmooksDataFormatReifier extends
DataFormatReifier<SmooksDataFormat>
@Override
protected void prepareDataFormatConfig(Map<String, Object> properties) {
properties.put("smooksConfig", definition.getSmooksConfig());
+ properties.put("allowExternalEntities",
definition.getAllowExternalEntities());
}
}
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 ff8d6a542575..7c67c5722ef3 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
@@ -3284,6 +3284,7 @@ public class JavaDslModelWriter extends
JavaDslModelWriterSupport {
protected void doWriteSmooksDataFormat(StringBuilder sb, SmooksDataFormat
def) {
doWriteIdentifiedTypeAttributes(sb, def);
doWriteAttribute(sb, "smooksConfig", def.getSmooksConfig(), null);
+ doWriteAttribute(sb, "allowExternalEntities",
def.getAllowExternalEntities(), "false");
}
protected void doWriteSoapDataFormat(StringBuilder sb, SoapDataFormat def)
{
doWriteIdentifiedTypeAttributes(sb, def);
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 5433ce88b10d..d6dd43f6d57d 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
@@ -58,6 +58,7 @@ public final class SecurityUtils {
// Generated by camel build tools - do NOT edit this map!
// SECURITY-OPTIONS: START
map.put("allowcontrolheaders", new SecurityOption(INSECURE_DEV,
"true"));
+ map.put("allowexternalentities", new SecurityOption(INSECURE_DEV,
"true"));
map.put("allowfilepathsource", new SecurityOption(INSECURE_DEV,
"true"));
map.put("allowjavaserializedobject", new
SecurityOption(INSECURE_SERIALIZATION, "true"));
map.put("allowlocalwebhookurls", new SecurityOption(INSECURE_DEV,
"true"));
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 67d703d473f6..009c61821024 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
@@ -2024,6 +2024,7 @@ public class ModelParser extends BaseParser {
}
protected SmooksDataFormat doParseSmooksDataFormat() throws IOException,
XmlPullParserException {
return doParse(new SmooksDataFormat(), (def, key, val) -> switch (key)
{
+ case "allowExternalEntities":
def.setAllowExternalEntities(val); yield true;
case "smooksConfig": def.setSmooksConfig(val); yield true;
default: yield identifiedTypeAttributeHandler().accept(def,
key, val);
}, noElementHandler(), noValueHandler());
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 49eaecc6606f..d9636653097c 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
@@ -2657,6 +2657,7 @@ public class ModelWriter extends BaseWriter {
startElement(name);
doWriteIdentifiedTypeAttributes(def);
doWriteAttribute("smooksConfig", def.getSmooksConfig(), null);
+ doWriteAttribute("allowExternalEntities",
def.getAllowExternalEntities(), "false");
endElement(name);
}
protected void doWriteSoapDataFormat(String name, SoapDataFormat def)
throws IOException {
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 736ccd04ca76..9a2fdee0b5e0 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
@@ -2655,6 +2655,7 @@ public class YamlModelWriter extends
YamlModelWriterSupport {
JsonObject jo = new JsonObject();
doWriteIdentifiedTypeAttributes(jo, def);
doWriteAttribute(jo, "smooksConfig", def.getSmooksConfig(), null);
+ doWriteAttribute(jo, "allowExternalEntities",
def.getAllowExternalEntities(), "false");
return jo;
}
protected JsonObject doWriteSoapDataFormat(SoapDataFormat def) {
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 2a07916c1d7f..970218a9c878 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
@@ -1848,3 +1848,19 @@ as a header cannot take effect. A route that set either
header was silently igno
Configure the `target` and `credentials` endpoint options instead, or route to
a different endpoint
with `toD` when the destination varies per message. The constants remain in
place for backwards
compatibility and are now marked deprecated in the component metadata.
+
+=== camel-smooks - external XML entity resolution disabled by default
+
+The Smooks data format and the Smooks component now parse XML input with a
reader that does not
+resolve external general or parameter entities, aligning camel-smooks with the
XML parser
+configuration already applied by the other Camel XML components. When a Smooks
configuration parses
+XML with the default reader, an untrusted body can no longer pull in external
entities (for example
+`<!ENTITY x SYSTEM "file:///...">` or an `http://` reference).
+
+This only affects configurations that use the default XML reader.
Configurations that declare their
+own reader — EDI, CSV, JSON, DFDL, or a custom reader — are unchanged, and
marshalling is unchanged.
+Documents carrying an internal DTD subset still parse.
+
+To restore the previous behaviour and allow external entity resolution, set
the new
+`allowExternalEntities` option to `true` on the data format or on the endpoint
+(`smooks:config.xml?allowExternalEntities=true`).
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SmooksEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SmooksEndpointBuilderFactory.java
index bb04c7128207..d94dc9ccca3b 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SmooksEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SmooksEndpointBuilderFactory.java
@@ -62,6 +62,44 @@ public interface SmooksEndpointBuilderFactory {
doSetProperty("reportPath", reportPath);
return this;
}
+ /**
+ * Whether to allow the XML reader used by Smooks to resolve external
+ * XML entities (external general and parameter entities) when parsing
+ * XML input. This is disabled by default so that external entities in
+ * the message body are not resolved; enable it only for trusted legacy
+ * configurations that rely on external entity resolution.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param allowExternalEntities the value to set
+ * @return the dsl builder
+ */
+ default SmooksEndpointBuilder allowExternalEntities(boolean
allowExternalEntities) {
+ doSetProperty("allowExternalEntities", allowExternalEntities);
+ return this;
+ }
+ /**
+ * Whether to allow the XML reader used by Smooks to resolve external
+ * XML entities (external general and parameter entities) when parsing
+ * XML input. This is disabled by default so that external entities in
+ * the message body are not resolved; enable it only for trusted legacy
+ * configurations that rely on external entity resolution.
+ *
+ * The option will be converted to a <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param allowExternalEntities the value to set
+ * @return the dsl builder
+ */
+ default SmooksEndpointBuilder allowExternalEntities(String
allowExternalEntities) {
+ doSetProperty("allowExternalEntities", allowExternalEntities);
+ return this;
+ }
}
/**
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 8d713156f97a..afc9760da112 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
@@ -17009,6 +17009,7 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
description = "Transform and bind XML as well as non-XML data,
including EDI, CSV, JSON, and YAML using Smooks",
deprecated = false,
properties = {
+ @YamlProperty(name = "allowExternalEntities", type =
"boolean", defaultValue = "false", description = "Whether to allow the XML
reader used by Smooks to resolve external XML entities (external general and
parameter entities) when parsing XML input. This is disabled by default so that
external entities in the message body are not resolved; enable it only for
trusted legacy configurations that rely on external entity resolution.",
displayName = "Allow External Entities"),
@YamlProperty(name = "id", type = "string", description =
"The id of this node", displayName = "Id"),
@YamlProperty(name = "smooksConfig", type = "string",
required = true, description = "Path to the Smooks configuration file.",
displayName = "Smooks Config")
}
@@ -17028,6 +17029,11 @@ public final class ModelDeserializers extends
YamlDeserializerSupport {
String propertyName, Node node) {
propertyKey =
org.apache.camel.util.StringHelper.dashToCamelCase(propertyKey);
switch(propertyKey) {
+ case "allowExternalEntities": {
+ String val = asText(node);
+ target.setAllowExternalEntities(val);
+ break;
+ }
case "id": {
String val = asText(node);
target.setId(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 1b439cba21ff..7dbd5a6b7e3a 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
@@ -8390,6 +8390,12 @@
"type" : "object",
"additionalProperties" : false,
"properties" : {
+ "allowExternalEntities" : {
+ "type" : "boolean",
+ "title" : "Allow External Entities",
+ "description" : "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external general and parameter entities) when
parsing XML input. This is disabled by default so that external entities in the
message body are not resolved; enable it only for trusted legacy configurations
that rely on external entity resolution.",
+ "default" : false
+ },
"id" : {
"type" : "string",
"title" : "Id",
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
index ee751df7923e..f2c0369f14ce 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json
@@ -13238,6 +13238,17 @@
"kind" : "attribute",
"index" : 1,
"group" : "common"
+ }, {
+ "name" : "allowExternalEntities",
+ "type" : "boolean",
+ "description" : "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external general and parameter entities) when
parsing XML input. This is disabled by default so that external entities in the
message body are not resolved; enable it only for trusted legacy configurations
that rely on external entity resolution.",
+ "title" : "Allow External Entities",
+ "default" : "false",
+ "displayName" : "Allow External Entities",
+ "kind" : "attribute",
+ "index" : 2,
+ "group" : "security",
+ "label" : "security"
} ]
},
"soap" : {
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 7fc68a0cbc83..2bab80f12496 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
@@ -12010,6 +12010,12 @@
"type" : "object",
"additionalProperties" : false,
"properties" : {
+ "allowExternalEntities" : {
+ "type" : "boolean",
+ "title" : "Allow External Entities",
+ "description" : "Whether to allow the XML reader used by Smooks to
resolve external XML entities (external general and parameter entities) when
parsing XML input. This is disabled by default so that external entities in the
message body are not resolved; enable it only for trusted legacy configurations
that rely on external entity resolution.",
+ "default" : false
+ },
"id" : {
"type" : "string",
"title" : "Id",