CVE-2026-95664
Camel-AWS2-S3: The producer evaluated an object key taken from the CamelAwsS3Key header as a Simple expression, so a route forwarding objects between buckets evaluated object names chosen by anyone able to upload to the source bucket
Description
For the object it uploads, reads or deletes, the camel-aws2-s3 producer takes the key from the CamelAwsS3Key message header and falls back to the keyName option configured on the endpoint only when that header is absent. Since 4.8.0, AWS2S3Utils.determineKey() also passed the resulting value to the Simple language whenever it contained ${ or $simple{, so that a route could use a dynamic key, and the evaluation applied to a value taken from the header as well as to the configured option. The header is not route configuration, however: the aws2-s3 consumer sets CamelAwsS3Key on the exchanges it creates to the key of the object it read, that is, the name of the object in the bucket. In a route that does nothing more than consume from one aws2-s3 endpoint and produce to another, the name of each consumed object therefore reached the producer, and a name containing either marker was evaluated as an expression, although the route author never asked for any evaluation, and configuring keyName on the producer did not prevent it, because the header took precedence. A key that a route copied into the header from other message content was evaluated in the same way. Anyone able to choose the name of an object in a bucket that such a route consumes, typically a principal allowed to upload to that bucket, could therefore have Simple expressions of their choosing evaluated in the Camel process. The Simple language can read JVM system properties, environment variables and configuration properties, which commonly carry credentials, and, where camel-bean is on the classpath, invoke methods on beans in the registry. The evaluated result became the key the producer used, so reading a value back additionally required the ability to see object names in the destination bucket, while a method invocation took effect without it; together with the need to name an object in a consumed bucket, this is why the severity is assessed medium. The bucket name went through the same evaluation, but the consumer only ever places the bucket configured on its own endpoint in a bucket header, never a value derived from a remote object, so the bucket name was not reachable in this way. Releases before 4.8.0 used the key from the header as given and are not affected.
Mitigation
Credit
This issue was discovered by Andrea Cosentino from Apache Software Foundation
Notes
The JIRA ticket: https://issues.apache.org/jira/browse/CAMEL-24901 refers to the commits that resolved the issue, and has more details.
The fix was merged on main in https://github.com/apache/camel/pull/26746 (commit 1fd7a667913ad068c8571b39c96b0a9c2dd4f3ef), and backported to camel-4.22.x in https://github.com/apache/camel/pull/26840 (commit 19a672aa791480bff6b61c0551c79b2fa079da6e) and to camel-4.18.x in https://github.com/apache/camel/pull/26842 (commit 124bf3da3cfb5d8e1994471e1301b6421bfe96d2). Two follow-up documentation changes on main added the upgrade-guide entries for the 4.22.x and 4.18.x release lines, in https://github.com/apache/camel/pull/26841 (commit aa48bb8a90adfae80a657a9ef427002d6634af31), and described the contract between header values and configured options in the Javadoc of AWS2S3Utils, in https://github.com/apache/camel/pull/26889 (commit 60c2c38b98ebbbaf56c10aff491291afe13df474).
AWS2S3Utils.determineKey() and determineBucketName() now evaluate the Simple language only for the keyName and bucketName options configured on the endpoint, which the route author writes. A key supplied through the CamelAwsS3Key header, or a bucket name supplied through the CamelAwsS3OverrideBucketName header, is used literally, so a forwarding route now writes an object whose name contains ${ under exactly that name. Configured dynamic keys and bucket names are unchanged. This changes the behaviour of a route that placed an unevaluated Simple expression in one of these headers and relied on the producer to evaluate it, as the dynamic-key support allowed: such a route must now evaluate the value before the producer, for example with setHeader(AWS2S3Constants.KEY, simple(…)), or move the expression to the keyName or bucketName option. A configured keyName or bucketName whose Simple expression resolves to null now also fails with an IllegalArgumentException at the producer, instead of passing a null key or bucket name to the AWS SDK. The production change is identical on all three branches, and the change of behaviour is documented in the upgrade guides.
The evaluation has been present since 4.8.0, when https://issues.apache.org/jira/browse/CAMEL-20971 added dynamic key and bucket names to the producer, and it applied to the header value from that release on, so every release from 4.8.0 up to the fixed versions carries the issue. Of the release lines involved, only 4.18.x and 4.22.x are still supported. Before 4.10.0 the producer read an overriding bucket name from the CamelAwsS3BucketName header, which the consumer sets to the bucket configured on its own endpoint, and from 4.10.0 it reads CamelAwsS3OverrideBucketName, which the consumer does not set, so in neither case did the name of a remote object reach the bucket name.
This issue belongs to the same family as CVE-2026-78164, where the HTTP producers resolved property placeholders in message-supplied URI override headers, and CVE-2026-83531, where the dynamic-router-control endpoint compiled predicate expressions supplied by control messages: in each case the framework evaluated a value that arrived with the message instead of treating it as data. It is distinct from the Camel header-injection advisories such as CVE-2025-27636, which concern consumers that admit Camel headers from external senders; here the header is set by the aws2-s3 consumer itself from the object name, so an inbound HeaderFilterStrategy does not remove it. It is classified as CWE-917 (Improper Neutralization of Special Elements used in an Expression Language Statement (‘Expression Language Injection’)).