VYPR
High severityNVD Advisory· Published Feb 23, 2026· Updated Apr 3, 2026

Apache Camel LevelDB: Deserialization of Untrusted Data in Camel LevelDB

CVE-2026-25747

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.

PackageAffected versionsPatched versions
org.apache.camel:camel-leveldbMaven
>= 3.0.0, < 4.10.94.10.9
org.apache.camel:camel-leveldbMaven
>= 4.11.0, < 4.14.54.14.5
org.apache.camel:camel-leveldbMaven
>= 4.15.0, < 4.18.04.18.0

Affected products

2
  • Apache/Camelllm-fuzzy
    Range: >=4.10.0 <4.10.8, >=4.14.0 <4.14.5, >=4.15.0 <4.18.0
  • Apache Software Foundation/Apache Camel LevelDBv5
    Range: 3.0.0

Patches

3
5f343367f7b2

CAMEL-22966 - Camel-LevelDB: Add ObjectInputFilter String pattern parameter in LevelDBAggregationRepository to be used in unmarshall operations

https://github.com/apache/camelAndrea CosentinoFeb 6, 2026via ghsa
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);
    +            }
    +        });
    +    }
     }
    
af2f2e9571b3

CAMEL-22966 - Camel-LevelDB: Add ObjectInputFilter String pattern parameter in LevelDBAggregationRepository to be used in unmarshall operations

https://github.com/apache/camelAndrea CosentinoFeb 6, 2026via ghsa
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);
    +            }
    +        });
    +    }
     }
    
0e3ac39e2041

CAMEL-22966 - Camel-LevelDB: Add ObjectInputFilter String pattern parameter in LevelDBAggregationRepository to be used in unmarshall operations

https://github.com/apache/camelAndrea CosentinoFeb 6, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.