Apache Camel LevelDB: Deserialization of Untrusted Data in Camel LevelDB
Description
Deserialization of Untrusted Data vulnerability in Apache Camel LevelDB component.
The Camel-LevelDB DefaultLevelDBSerializer class deserializes data read from the LevelDB aggregation repository using java.io.ObjectInputStream without applying any ObjectInputFilter or class-loading restrictions. An attacker who can write to the LevelDB database files used by a Camel application can inject a crafted serialized Java object that, when deserialized during normal aggregation repository operations, results in arbitrary code execution in the context of the application. This issue affects Apache Camel: from 4.10.0 before 4.10.8, from 4.14.0 before 4.14.5, from 4.15.0 before 4.18.0.
Users are recommended to upgrade to version 4.18.0, which fixes the issue. For the 4.10.x LTS releases, users are recommended to upgrade to 4.10.9, while for 4.14.x LTS releases, users are recommended to upgrade to 4.14.5
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Camel LevelDB component deserializes untrusted data from LevelDB files, allowing arbitrary code execution via crafted serialized objects.
Vulnerability
CVE-2026-25747 is a deserialization vulnerability in the Apache Camel LevelDB component. The DefaultLevelDBSerializer class in Camel-LevelDB uses java.io.ObjectInputStream to deserialize data from the LevelDB aggregation repository without any ObjectInputFilter or class-loading restrictions. This allows an attacker who can write to the LevelDB database files to inject a crafted serialized Java object that, upon deserialization during normal repository operations, leads to arbitrary code execution in the context of the application [1][2][3][4].
Exploitation
Exploitation requires the attacker to have write access to the LevelDB database files used by a Camel application. This could be achieved through an existing write primitive in the application, misconfigured file permissions, or other means. During normal aggregation repository operations, the malicious serialized object is deserialized, triggering code execution. No authentication is needed for the deserialization itself, but the attacker must first gain the ability to modify the LevelDB files [4].
Impact
Successful exploitation results in arbitrary code execution with the privileges of the Camel application. This could lead to complete compromise of the application, data exfiltration, or lateral movement within the environment. The vulnerability is rated with a high CVSS score due to the ease of achieving remote code execution once file write access is obtained [4].
Mitigation
Apache recommends upgrading to Camel version 4.18.0 for the main branch, or to 4.10.9 for 4.10.x LTS releases and 4.14.5 for 4.14.x LTS releases. The fix adds an ObjectInputFilter string pattern parameter to restrict deserialization to safe classes, as shown in the commits [1][2][3]. Users unable to upgrade should ensure strict file permissions on LevelDB storage directories and monitor for unauthorized file modifications.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.camel:camel-leveldbMaven | >= 3.0.0, < 4.10.9 | 4.10.9 |
org.apache.camel:camel-leveldbMaven | >= 4.11.0, < 4.14.5 | 4.14.5 |
org.apache.camel:camel-leveldbMaven | >= 4.15.0, < 4.18.0 | 4.18.0 |
Affected products
2- Apache Software Foundation/Apache Camel LevelDBv5Range: 3.0.0
Patches
35f343367f7b2CAMEL-22966 - Camel-LevelDB: Add ObjectInputFilter String pattern parameter in LevelDBAggregationRepository to be used in unmarshall operations
8 files changed · +95 −8
catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/LevelDBAggregationRepository.json+1 −1 modified@@ -10,7 +10,7 @@ "groupId": "org.apache.camel", "artifactId": "camel-leveldb", "version": "4.18.0-SNAPSHOT", - "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5000, "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" } } + "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5000, "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" }, "deserializationFilter": { "index": 10, "kind": "property", "displayName": "Deserialization Filter", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "java.**;org.apache.camel.**;!*", "description": "Sets a deserialization filter while reading Object from Aggregation Repository. By default the filter will allow all java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be deserialized." } } } }
components/camel-leveldb/src/generated/java/org/apache/camel/component/leveldb/LevelDBAggregationRepositoryConfigurer.java+6 −0 modified@@ -27,6 +27,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "allowSerializedHeaders": target.setAllowSerializedHeaders(property(camelContext, boolean.class, value)); return true; case "deadletteruri": case "deadLetterUri": target.setDeadLetterUri(property(camelContext, java.lang.String.class, value)); return true; + case "deserializationfilter": + case "deserializationFilter": target.setDeserializationFilter(property(camelContext, java.lang.String.class, value)); return true; case "maximumredeliveries": case "maximumRedeliveries": target.setMaximumRedeliveries(property(camelContext, int.class, value)); return true; case "persistentfilename": @@ -52,6 +54,8 @@ public Class<?> getOptionType(String name, boolean ignoreCase) { case "allowSerializedHeaders": return boolean.class; case "deadletteruri": case "deadLetterUri": return java.lang.String.class; + case "deserializationfilter": + case "deserializationFilter": return java.lang.String.class; case "maximumredeliveries": case "maximumRedeliveries": return int.class; case "persistentfilename": @@ -78,6 +82,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "allowSerializedHeaders": return target.isAllowSerializedHeaders(); case "deadletteruri": case "deadLetterUri": return target.getDeadLetterUri(); + case "deserializationfilter": + case "deserializationFilter": return target.getDeserializationFilter(); case "maximumredeliveries": case "maximumRedeliveries": return target.getMaximumRedeliveries(); case "persistentfilename":
components/camel-leveldb/src/generated/resources/META-INF/services/org/apache/camel/bean/LevelDBAggregationRepository.json+1 −1 modified@@ -10,7 +10,7 @@ "groupId": "org.apache.camel", "artifactId": "camel-leveldb", "version": "4.18.0-SNAPSHOT", - "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5000, "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" } } + "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5000, "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" }, "deserializationFilter": { "index": 10, "kind": "property", "displayName": "Deserialization Filter", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "java.**;org.apache.camel.**;!*", "description": "Sets a deserialization filter while reading Object from Aggregation Repository. By default the filter will allow all java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be deserialized." } } } }
components/camel-leveldb/src/main/docs/leveldb.adoc+8 −0 modified@@ -71,6 +71,14 @@ option must also be provided. |`deadLetterUri` |String |An endpoint uri for a Dead Letter Channel where exhausted recovered Exchanges will be moved. If this option is used then the `maximumRedeliveries` option must also be provided. + +|`deserializationFilter` |String |A deserialization filter used when +deserializing exchange data from the LevelDB store. The filter uses the +standard Java `ObjectInputFilter` pattern syntax. By default, the filter +is set to `java.**;org.apache.camel.**;!*` which allows Java platform +classes and Apache Camel classes. If your application stores custom objects, +you can adjust this filter to include additional packages +(e.g., `java.**;org.apache.camel.**;com.mycompany.**;!*`). |======================================================================= The `repositoryName` option must be provided. Then either the
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/LevelDBAggregationRepository.java+28 −6 modified@@ -78,6 +78,20 @@ public class LevelDBAggregationRepository extends ServiceSupport implements Reco description = "To use a custom serializer for LevelDB") private LevelDBSerializer serializer; + /** + * Sets a deserialization filter while reading Object from Aggregation Repository. By default the filter will allow + * all java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be + * blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be + * deserialized. + */ + @Metadata(label = "advanced", + description = "Sets a deserialization filter while reading Object from Aggregation Repository." + + " By default the filter will allow all java packages and subpackages and all org.apache.camel packages and subpackages," + + " while the remaining will be blacklisted and not deserialized." + + " This parameter should be customized if you're using classes you trust to be deserialized.", + defaultValue = "java.**;org.apache.camel.**;!*") + private String deserializationFilter = "java.**;org.apache.camel.**;!*"; + /** * Creates an aggregation repository */ @@ -142,9 +156,9 @@ public Exchange add(final CamelContext camelContext, final String key, final Exc // only return old exchange if enabled if (isReturnOldExchange()) { - return codec().unmarshallExchange(camelContext, rc); + return codec().unmarshallExchange(camelContext, rc, deserializationFilter); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException("Error adding to repository " + repositoryName + " with key " + key, e); } @@ -161,9 +175,9 @@ public Exchange get(final CamelContext camelContext, final String key) { byte[] rc = levelDBFile.getDb().get(lDbKey); if (rc != null) { - answer = codec().unmarshallExchange(camelContext, rc); + answer = codec().unmarshallExchange(camelContext, rc, deserializationFilter); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException("Error getting key " + key + " from repository " + repositoryName, e); } @@ -308,9 +322,9 @@ public Exchange recover(CamelContext camelContext, final String exchangeId) { byte[] rc = levelDBFile.getDb().get(completedLDBKey); if (rc != null) { - answer = codec().unmarshallExchange(camelContext, rc); + answer = codec().unmarshallExchange(camelContext, rc, deserializationFilter); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException( "Error recovering exchangeId " + exchangeId + " from repository " + repositoryName, e); } @@ -496,6 +510,14 @@ public void setSerializer(LevelDBSerializer serializer) { this.serializer = serializer; } + public String getDeserializationFilter() { + return deserializationFilter; + } + + public void setDeserializationFilter(String deserializationFilter) { + this.deserializationFilter = deserializationFilter; + } + public LevelDBCamelCodec codec() { if (codec == null) { codec = new LevelDBCamelCodec(serializer);
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/LevelDBCamelCodec.java+15 −0 modified@@ -62,4 +62,19 @@ public Exchange unmarshallExchange(CamelContext camelContext, byte[] buffer) thr } return answer; } + + public Exchange unmarshallExchange(CamelContext camelContext, byte[] buffer, String deserializationFilter) + throws IOException, ClassNotFoundException { + Exchange answer = serializer.deserializeExchange(camelContext, buffer, deserializationFilter); + + // restore the from endpoint + String fromEndpointUri = (String) answer.removeProperty("CamelAggregatedFromEndpoint"); + if (fromEndpointUri != null) { + Endpoint fromEndpoint = camelContext.hasEndpoint(fromEndpointUri); + if (fromEndpoint != null) { + answer.getExchangeExtension().setFromEndpoint(fromEndpoint); + } + } + return answer; + } }
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/LevelDBSerializer.java+18 −0 modified@@ -31,4 +31,22 @@ byte[] serializeExchange(CamelContext camelContext, Exchange exchange, boolean a throws IOException; Exchange deserializeExchange(CamelContext camelContext, byte[] buffer) throws IOException; + + /** + * Deserializes an exchange from a byte buffer with a deserialization filter for security. + * + * @param camelContext the CamelContext + * @param buffer the byte buffer containing serialized exchange data + * @param deserializationFilter the deserialization filter pattern to apply (e.g., + * "java.**;org.apache.camel.**;!*") + * @return the deserialized Exchange + * @throws IOException if an I/O error occurs + * @throws ClassNotFoundException if a class cannot be found during deserialization + */ + default Exchange deserializeExchange(CamelContext camelContext, byte[] buffer, String deserializationFilter) + throws IOException, ClassNotFoundException { + // Default implementation for backward compatibility - delegates to the original method + // Subclasses should override this to apply the filter + return deserializeExchange(camelContext, buffer); + } }
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/serializer/DefaultLevelDBSerializer.java+18 −0 modified@@ -19,12 +19,14 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.support.DefaultExchangeHolder; +import org.apache.camel.util.ClassLoadingAwareObjectInputStream; public class DefaultLevelDBSerializer extends AbstractLevelDBSerializer { @@ -70,4 +72,20 @@ public Exchange deserializeExchange(CamelContext camelContext, byte[] buffer) th } }); } + + @Override + public Exchange deserializeExchange(CamelContext camelContext, byte[] buffer, String deserializationFilter) + throws IOException, ClassNotFoundException { + return deserializeExchange(camelContext, buffer, b -> { + ClassLoader classLoader = camelContext.getApplicationContextClassLoader(); + try (final ObjectInputStream ois = new ClassLoadingAwareObjectInputStream( + classLoader, + new ByteArrayInputStream(buffer))) { + ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter(deserializationFilter)); + return (DefaultExchangeHolder) ois.readObject(); + } catch (ClassNotFoundException e) { + throw new IOException("Failed to deserialize exchange", e); + } + }); + } }
af2f2e9571b3CAMEL-22966 - Camel-LevelDB: Add ObjectInputFilter String pattern parameter in LevelDBAggregationRepository to be used in unmarshall operations
8 files changed · +95 −8
catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/LevelDBAggregationRepository.json+1 −1 modified@@ -10,7 +10,7 @@ "groupId": "org.apache.camel", "artifactId": "camel-leveldb", "version": "4.10.9-SNAPSHOT", - "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "true", "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "5000", "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" } } + "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "true", "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "5000", "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" }, "deserializationFilter": { "index": 10, "kind": "property", "displayName": "Deserialization Filter", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "java.**;org.apache.camel.**;!*", "description": "Sets a deserialization filter while reading Object from Aggregation Repository. By default the filter will allow all java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be deserialized." } } } }
components/camel-leveldb/src/generated/java/org/apache/camel/component/leveldb/LevelDBAggregationRepositoryConfigurer.java+6 −0 modified@@ -27,6 +27,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "allowSerializedHeaders": target.setAllowSerializedHeaders(property(camelContext, boolean.class, value)); return true; case "deadletteruri": case "deadLetterUri": target.setDeadLetterUri(property(camelContext, java.lang.String.class, value)); return true; + case "deserializationfilter": + case "deserializationFilter": target.setDeserializationFilter(property(camelContext, java.lang.String.class, value)); return true; case "maximumredeliveries": case "maximumRedeliveries": target.setMaximumRedeliveries(property(camelContext, int.class, value)); return true; case "persistentfilename": @@ -52,6 +54,8 @@ public Class<?> getOptionType(String name, boolean ignoreCase) { case "allowSerializedHeaders": return boolean.class; case "deadletteruri": case "deadLetterUri": return java.lang.String.class; + case "deserializationfilter": + case "deserializationFilter": return java.lang.String.class; case "maximumredeliveries": case "maximumRedeliveries": return int.class; case "persistentfilename": @@ -78,6 +82,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "allowSerializedHeaders": return target.isAllowSerializedHeaders(); case "deadletteruri": case "deadLetterUri": return target.getDeadLetterUri(); + case "deserializationfilter": + case "deserializationFilter": return target.getDeserializationFilter(); case "maximumredeliveries": case "maximumRedeliveries": return target.getMaximumRedeliveries(); case "persistentfilename":
components/camel-leveldb/src/generated/resources/META-INF/services/org/apache/camel/bean/LevelDBAggregationRepository.json+1 −1 modified@@ -10,7 +10,7 @@ "groupId": "org.apache.camel", "artifactId": "camel-leveldb", "version": "4.10.9-SNAPSHOT", - "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "true", "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "5000", "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" } } + "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "true", "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "5000", "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" }, "deserializationFilter": { "index": 10, "kind": "property", "displayName": "Deserialization Filter", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "java.**;org.apache.camel.**;!*", "description": "Sets a deserialization filter while reading Object from Aggregation Repository. By default the filter will allow all java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be deserialized." } } } }
components/camel-leveldb/src/main/docs/leveldb.adoc+8 −0 modified@@ -71,6 +71,14 @@ option must also be provided. |`deadLetterUri` |String |An endpoint uri for a Dead Letter Channel where exhausted recovered Exchanges will be moved. If this option is used then the `maximumRedeliveries` option must also be provided. + +|`deserializationFilter` |String |A deserialization filter used when +deserializing exchange data from the LevelDB store. The filter uses the +standard Java `ObjectInputFilter` pattern syntax. By default, the filter +is set to `java.**;org.apache.camel.**;!*` which allows Java platform +classes and Apache Camel classes. If your application stores custom objects, +you can adjust this filter to include additional packages +(e.g., `java.**;org.apache.camel.**;com.mycompany.**;!*`). |======================================================================= The `repositoryName` option must be provided. Then either the
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/LevelDBAggregationRepository.java+28 −6 modified@@ -78,6 +78,20 @@ public class LevelDBAggregationRepository extends ServiceSupport implements Reco description = "To use a custom serializer for LevelDB") private LevelDBSerializer serializer; + /** + * Sets a deserialization filter while reading Object from Aggregation Repository. By default the filter will allow + * all java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be + * blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be + * deserialized. + */ + @Metadata(label = "advanced", + description = "Sets a deserialization filter while reading Object from Aggregation Repository." + + " By default the filter will allow all java packages and subpackages and all org.apache.camel packages and subpackages," + + " while the remaining will be blacklisted and not deserialized." + + " This parameter should be customized if you're using classes you trust to be deserialized.", + defaultValue = "java.**;org.apache.camel.**;!*") + private String deserializationFilter = "java.**;org.apache.camel.**;!*"; + /** * Creates an aggregation repository */ @@ -142,9 +156,9 @@ public Exchange add(final CamelContext camelContext, final String key, final Exc // only return old exchange if enabled if (isReturnOldExchange()) { - return codec().unmarshallExchange(camelContext, rc); + return codec().unmarshallExchange(camelContext, rc, deserializationFilter); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException("Error adding to repository " + repositoryName + " with key " + key, e); } @@ -161,9 +175,9 @@ public Exchange get(final CamelContext camelContext, final String key) { byte[] rc = levelDBFile.getDb().get(lDbKey); if (rc != null) { - answer = codec().unmarshallExchange(camelContext, rc); + answer = codec().unmarshallExchange(camelContext, rc, deserializationFilter); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException("Error getting key " + key + " from repository " + repositoryName, e); } @@ -308,9 +322,9 @@ public Exchange recover(CamelContext camelContext, final String exchangeId) { byte[] rc = levelDBFile.getDb().get(completedLDBKey); if (rc != null) { - answer = codec().unmarshallExchange(camelContext, rc); + answer = codec().unmarshallExchange(camelContext, rc, deserializationFilter); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException( "Error recovering exchangeId " + exchangeId + " from repository " + repositoryName, e); } @@ -496,6 +510,14 @@ public void setSerializer(LevelDBSerializer serializer) { this.serializer = serializer; } + public String getDeserializationFilter() { + return deserializationFilter; + } + + public void setDeserializationFilter(String deserializationFilter) { + this.deserializationFilter = deserializationFilter; + } + public LevelDBCamelCodec codec() { if (codec == null) { codec = new LevelDBCamelCodec(serializer);
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/LevelDBCamelCodec.java+15 −0 modified@@ -62,4 +62,19 @@ public Exchange unmarshallExchange(CamelContext camelContext, byte[] buffer) thr } return answer; } + + public Exchange unmarshallExchange(CamelContext camelContext, byte[] buffer, String deserializationFilter) + throws IOException, ClassNotFoundException { + Exchange answer = serializer.deserializeExchange(camelContext, buffer, deserializationFilter); + + // restore the from endpoint + String fromEndpointUri = (String) answer.removeProperty("CamelAggregatedFromEndpoint"); + if (fromEndpointUri != null) { + Endpoint fromEndpoint = camelContext.hasEndpoint(fromEndpointUri); + if (fromEndpoint != null) { + answer.getExchangeExtension().setFromEndpoint(fromEndpoint); + } + } + return answer; + } }
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/LevelDBSerializer.java+18 −0 modified@@ -31,4 +31,22 @@ byte[] serializeExchange(CamelContext camelContext, Exchange exchange, boolean a throws IOException; Exchange deserializeExchange(CamelContext camelContext, byte[] buffer) throws IOException; + + /** + * Deserializes an exchange from a byte buffer with a deserialization filter for security. + * + * @param camelContext the CamelContext + * @param buffer the byte buffer containing serialized exchange data + * @param deserializationFilter the deserialization filter pattern to apply (e.g., + * "java.**;org.apache.camel.**;!*") + * @return the deserialized Exchange + * @throws IOException if an I/O error occurs + * @throws ClassNotFoundException if a class cannot be found during deserialization + */ + default Exchange deserializeExchange(CamelContext camelContext, byte[] buffer, String deserializationFilter) + throws IOException, ClassNotFoundException { + // Default implementation for backward compatibility - delegates to the original method + // Subclasses should override this to apply the filter + return deserializeExchange(camelContext, buffer); + } }
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/serializer/DefaultLevelDBSerializer.java+18 −0 modified@@ -19,12 +19,14 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.support.DefaultExchangeHolder; +import org.apache.camel.util.ClassLoadingAwareObjectInputStream; public class DefaultLevelDBSerializer extends AbstractLevelDBSerializer { @@ -70,4 +72,20 @@ public Exchange deserializeExchange(CamelContext camelContext, byte[] buffer) th } }); } + + @Override + public Exchange deserializeExchange(CamelContext camelContext, byte[] buffer, String deserializationFilter) + throws IOException, ClassNotFoundException { + return deserializeExchange(camelContext, buffer, b -> { + ClassLoader classLoader = camelContext.getApplicationContextClassLoader(); + try (final ObjectInputStream ois = new ClassLoadingAwareObjectInputStream( + classLoader, + new ByteArrayInputStream(buffer))) { + ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter(deserializationFilter)); + return (DefaultExchangeHolder) ois.readObject(); + } catch (ClassNotFoundException e) { + throw new IOException("Failed to deserialize exchange", e); + } + }); + } }
0e3ac39e2041CAMEL-22966 - Camel-LevelDB: Add ObjectInputFilter String pattern parameter in LevelDBAggregationRepository to be used in unmarshall operations
8 files changed · +95 −8
catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/LevelDBAggregationRepository.json+1 −1 modified@@ -10,7 +10,7 @@ "groupId": "org.apache.camel", "artifactId": "camel-leveldb", "version": "4.14.5-SNAPSHOT", - "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5000, "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" } } + "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5000, "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" }, "deserializationFilter": { "index": 10, "kind": "property", "displayName": "Deserialization Filter", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "java.**;org.apache.camel.**;!*", "description": "Sets a deserialization filter while reading Object from Aggregation Repository. By default the filter will allow all java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be deserialized." } } } }
components/camel-leveldb/src/generated/java/org/apache/camel/component/leveldb/LevelDBAggregationRepositoryConfigurer.java+6 −0 modified@@ -27,6 +27,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "allowSerializedHeaders": target.setAllowSerializedHeaders(property(camelContext, boolean.class, value)); return true; case "deadletteruri": case "deadLetterUri": target.setDeadLetterUri(property(camelContext, java.lang.String.class, value)); return true; + case "deserializationfilter": + case "deserializationFilter": target.setDeserializationFilter(property(camelContext, java.lang.String.class, value)); return true; case "maximumredeliveries": case "maximumRedeliveries": target.setMaximumRedeliveries(property(camelContext, int.class, value)); return true; case "persistentfilename": @@ -52,6 +54,8 @@ public Class<?> getOptionType(String name, boolean ignoreCase) { case "allowSerializedHeaders": return boolean.class; case "deadletteruri": case "deadLetterUri": return java.lang.String.class; + case "deserializationfilter": + case "deserializationFilter": return java.lang.String.class; case "maximumredeliveries": case "maximumRedeliveries": return int.class; case "persistentfilename": @@ -78,6 +82,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "allowSerializedHeaders": return target.isAllowSerializedHeaders(); case "deadletteruri": case "deadLetterUri": return target.getDeadLetterUri(); + case "deserializationfilter": + case "deserializationFilter": return target.getDeserializationFilter(); case "maximumredeliveries": case "maximumRedeliveries": return target.getMaximumRedeliveries(); case "persistentfilename":
components/camel-leveldb/src/generated/resources/META-INF/services/org/apache/camel/bean/LevelDBAggregationRepository.json+1 −1 modified@@ -10,7 +10,7 @@ "groupId": "org.apache.camel", "artifactId": "camel-leveldb", "version": "4.14.5-SNAPSHOT", - "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5000, "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" } } + "properties": { "persistentFileName": { "index": 0, "kind": "property", "displayName": "Persistent File Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of file to use for storing data" }, "repositoryName": { "index": 1, "kind": "property", "displayName": "Repository Name", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of repository" }, "sync": { "index": 2, "kind": "property", "displayName": "Sync", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether LevelDB should sync writes" }, "returnOldExchange": { "index": 3, "kind": "property", "displayName": "Return Old Exchange", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether to return the old exchange when adding new exchanges to the repository" }, "useRecovery": { "index": 4, "kind": "property", "displayName": "Use Recovery", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether or not recovery is enabled" }, "recoveryInterval": { "index": 5, "kind": "property", "displayName": "Recovery Interval", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 5000, "description": "Sets the interval between recovery scans" }, "maximumRedeliveries": { "index": 6, "kind": "property", "displayName": "Maximum Redeliveries", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional limit of the number of redelivery attempt of recovered Exchange should be attempted, before its exhausted. When this limit is hit, then the Exchange is moved to the dead letter channel." }, "deadLetterUri": { "index": 7, "kind": "property", "displayName": "Dead Letter Uri", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an optional dead letter channel which exhausted recovered Exchange should be send to." }, "allowSerializedHeaders": { "index": 8, "kind": "property", "displayName": "Allow Serialized Headers", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether headers on the Exchange that are Java objects and Serializable should be included and saved to the repository" }, "serializer": { "index": 9, "kind": "property", "displayName": "Serializer", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.leveldb.LevelDBSerializer", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom serializer for LevelDB" }, "deserializationFilter": { "index": 10, "kind": "property", "displayName": "Deserialization Filter", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "java.**;org.apache.camel.**;!*", "description": "Sets a deserialization filter while reading Object from Aggregation Repository. By default the filter will allow all java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be deserialized." } } } }
components/camel-leveldb/src/main/docs/leveldb.adoc+8 −0 modified@@ -71,6 +71,14 @@ option must also be provided. |`deadLetterUri` |String |An endpoint uri for a Dead Letter Channel where exhausted recovered Exchanges will be moved. If this option is used then the `maximumRedeliveries` option must also be provided. + +|`deserializationFilter` |String |A deserialization filter used when +deserializing exchange data from the LevelDB store. The filter uses the +standard Java `ObjectInputFilter` pattern syntax. By default, the filter +is set to `java.**;org.apache.camel.**;!*` which allows Java platform +classes and Apache Camel classes. If your application stores custom objects, +you can adjust this filter to include additional packages +(e.g., `java.**;org.apache.camel.**;com.mycompany.**;!*`). |======================================================================= The `repositoryName` option must be provided. Then either the
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/LevelDBAggregationRepository.java+28 −6 modified@@ -78,6 +78,20 @@ public class LevelDBAggregationRepository extends ServiceSupport implements Reco description = "To use a custom serializer for LevelDB") private LevelDBSerializer serializer; + /** + * Sets a deserialization filter while reading Object from Aggregation Repository. By default the filter will allow + * all java packages and subpackages and all org.apache.camel packages and subpackages, while the remaining will be + * blacklisted and not deserialized. This parameter should be customized if you're using classes you trust to be + * deserialized. + */ + @Metadata(label = "advanced", + description = "Sets a deserialization filter while reading Object from Aggregation Repository." + + " By default the filter will allow all java packages and subpackages and all org.apache.camel packages and subpackages," + + " while the remaining will be blacklisted and not deserialized." + + " This parameter should be customized if you're using classes you trust to be deserialized.", + defaultValue = "java.**;org.apache.camel.**;!*") + private String deserializationFilter = "java.**;org.apache.camel.**;!*"; + /** * Creates an aggregation repository */ @@ -142,9 +156,9 @@ public Exchange add(final CamelContext camelContext, final String key, final Exc // only return old exchange if enabled if (isReturnOldExchange()) { - return codec().unmarshallExchange(camelContext, rc); + return codec().unmarshallExchange(camelContext, rc, deserializationFilter); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException("Error adding to repository " + repositoryName + " with key " + key, e); } @@ -161,9 +175,9 @@ public Exchange get(final CamelContext camelContext, final String key) { byte[] rc = levelDBFile.getDb().get(lDbKey); if (rc != null) { - answer = codec().unmarshallExchange(camelContext, rc); + answer = codec().unmarshallExchange(camelContext, rc, deserializationFilter); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException("Error getting key " + key + " from repository " + repositoryName, e); } @@ -308,9 +322,9 @@ public Exchange recover(CamelContext camelContext, final String exchangeId) { byte[] rc = levelDBFile.getDb().get(completedLDBKey); if (rc != null) { - answer = codec().unmarshallExchange(camelContext, rc); + answer = codec().unmarshallExchange(camelContext, rc, deserializationFilter); } - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeCamelException( "Error recovering exchangeId " + exchangeId + " from repository " + repositoryName, e); } @@ -496,6 +510,14 @@ public void setSerializer(LevelDBSerializer serializer) { this.serializer = serializer; } + public String getDeserializationFilter() { + return deserializationFilter; + } + + public void setDeserializationFilter(String deserializationFilter) { + this.deserializationFilter = deserializationFilter; + } + public LevelDBCamelCodec codec() { if (codec == null) { codec = new LevelDBCamelCodec(serializer);
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/LevelDBCamelCodec.java+15 −0 modified@@ -62,4 +62,19 @@ public Exchange unmarshallExchange(CamelContext camelContext, byte[] buffer) thr } return answer; } + + public Exchange unmarshallExchange(CamelContext camelContext, byte[] buffer, String deserializationFilter) + throws IOException, ClassNotFoundException { + Exchange answer = serializer.deserializeExchange(camelContext, buffer, deserializationFilter); + + // restore the from endpoint + String fromEndpointUri = (String) answer.removeProperty("CamelAggregatedFromEndpoint"); + if (fromEndpointUri != null) { + Endpoint fromEndpoint = camelContext.hasEndpoint(fromEndpointUri); + if (fromEndpoint != null) { + answer.getExchangeExtension().setFromEndpoint(fromEndpoint); + } + } + return answer; + } }
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/LevelDBSerializer.java+18 −0 modified@@ -31,4 +31,22 @@ byte[] serializeExchange(CamelContext camelContext, Exchange exchange, boolean a throws IOException; Exchange deserializeExchange(CamelContext camelContext, byte[] buffer) throws IOException; + + /** + * Deserializes an exchange from a byte buffer with a deserialization filter for security. + * + * @param camelContext the CamelContext + * @param buffer the byte buffer containing serialized exchange data + * @param deserializationFilter the deserialization filter pattern to apply (e.g., + * "java.**;org.apache.camel.**;!*") + * @return the deserialized Exchange + * @throws IOException if an I/O error occurs + * @throws ClassNotFoundException if a class cannot be found during deserialization + */ + default Exchange deserializeExchange(CamelContext camelContext, byte[] buffer, String deserializationFilter) + throws IOException, ClassNotFoundException { + // Default implementation for backward compatibility - delegates to the original method + // Subclasses should override this to apply the filter + return deserializeExchange(camelContext, buffer); + } }
components/camel-leveldb/src/main/java/org/apache/camel/component/leveldb/serializer/DefaultLevelDBSerializer.java+18 −0 modified@@ -19,12 +19,14 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.support.DefaultExchangeHolder; +import org.apache.camel.util.ClassLoadingAwareObjectInputStream; public class DefaultLevelDBSerializer extends AbstractLevelDBSerializer { @@ -70,4 +72,20 @@ public Exchange deserializeExchange(CamelContext camelContext, byte[] buffer) th } }); } + + @Override + public Exchange deserializeExchange(CamelContext camelContext, byte[] buffer, String deserializationFilter) + throws IOException, ClassNotFoundException { + return deserializeExchange(camelContext, buffer, b -> { + ClassLoader classLoader = camelContext.getApplicationContextClassLoader(); + try (final ObjectInputStream ois = new ClassLoadingAwareObjectInputStream( + classLoader, + new ByteArrayInputStream(buffer))) { + ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter(deserializationFilter)); + return (DefaultExchangeHolder) ois.readObject(); + } catch (ClassNotFoundException e) { + throw new IOException("Failed to deserialize exchange", e); + } + }); + } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- camel.apache.org/security/CVE-2026-25747.htmlghsavendor-advisoryWEB
- github.com/advisories/GHSA-429q-mrc4-38frghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-25747ghsaADVISORY
- www.openwall.com/lists/oss-security/2026/02/18/6ghsaWEB
- github.com/apache/camel/commit/0e3ac39e20416c91af6df2cfce3f7d795e75ad89ghsaWEB
- github.com/apache/camel/commit/5f343367f7b25646b7d12be26c3e87381c7a7ecbghsaWEB
- github.com/apache/camel/commit/af2f2e9571b3b03a36b771bd9eb10427886d9636ghsaWEB
- issues.apache.org/jira/browse/CAMEL-22966ghsaWEB
News mentions
0No linked articles in our index yet.