This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.18.x by this push:
new 007929bb732a [backport camel-4.18.x] CAMEL-23942: contain Azure
Storage Blob/DataLake fileDir downloads within the configured directory (#24581)
007929bb732a is described below
commit 007929bb732a8c5e3e49d4d3c6c8adc2aab13340
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Jul 10 15:19:14 2026 +0200
[backport camel-4.18.x] CAMEL-23942: contain Azure Storage Blob/DataLake
fileDir downloads within the configured directory (#24581)
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../azure/storage/blob/operations/BlobOperations.java | 17 ++++++++++++++++-
.../storage/blob/operations/BlobOperationsTest.java | 13 +++++++++++++
.../datalake/operations/DataLakeFileOperations.java | 17 ++++++++++++++++-
.../datalake/operations/DataLakeFileOperationTest.java | 14 ++++++++++++++
.../modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc | 10 ++++++++++
5 files changed, 69 insertions(+), 2 deletions(-)
diff --git
a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java
b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java
index c577d3604715..59ede92f47fa 100644
---
a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java
+++
b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java
@@ -20,6 +20,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.file.Path;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Collections;
@@ -145,7 +146,7 @@ public class BlobOperations {
throw new IllegalArgumentException("In order to download a blob,
you will need to specify the fileDir in the URI");
}
- final File fileToDownload = new File(fileDir, client.getBlobName());
+ final File fileToDownload = resolveWithinDirectory(fileDir,
client.getBlobName());
final BlobCommonRequestOptions commonRequestOptions =
getCommonRequestOptions(exchange);
final BlobRange blobRange = configurationProxy.getBlobRange(exchange);
final ParallelTransferOptions parallelTransferOptions =
configurationProxy.getParallelTransferOptions(exchange);
@@ -635,4 +636,18 @@ public class BlobOperations {
leaseClient.releaseLease();
}
}
+
+ private static File resolveWithinDirectory(String fileDir, String name) {
+ final File target = new File(fileDir, name);
+ // normalize lexically (removes ./ and ../ segments) and compare on
path-segment boundaries so a sibling
+ // directory whose name merely extends fileDir is not considered
contained
+ final Path normalizedDir = new File(fileDir).toPath().normalize();
+ final Path normalizedTarget = target.toPath().normalize();
+ if (!normalizedTarget.startsWith(normalizedDir)) {
+ throw new IllegalArgumentException(
+ "Cannot download to file '" + name
+ + "' as it resolves outside the
configured fileDir directory: " + fileDir);
+ }
+ return target;
+ }
}
diff --git
a/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java
b/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java
index 3e7755113dfe..6c4b23671a29 100644
---
a/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java
+++
b/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java
@@ -124,6 +124,19 @@ class BlobOperationsTest extends CamelTestSupport {
assertEquals("tag1", response3.getHeaders().get(BlobConstants.E_TAG));
}
+ @Test
+ void testDownloadBlobToFileRejectsPathTraversalName() {
+ // a blob name is influenced by whoever writes to the container and
may contain ../ segments;
+ // the download must stay within the configured fileDir
+ configuration.setFileDir("/tmp/camel-azure-blob-download");
+ when(client.getBlobName()).thenReturn("../escaped/PROOF_PWNED");
+
+ final BlobOperations operations = new BlobOperations(configuration,
client);
+ final Exchange exchange = new DefaultExchange(context);
+
+ assertThrows(IllegalArgumentException.class, () ->
operations.downloadBlobToFile(exchange));
+ }
+
@Test
void testUploadBlockBlob() throws Exception {
// mocking
diff --git
a/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java
b/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java
index 72ab8856a898..6a2c92a403af 100644
---
a/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java
+++
b/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.OpenOption;
+import java.nio.file.Path;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Map;
@@ -106,7 +107,7 @@ public class DataLakeFileOperations {
throw new IllegalArgumentException("to download a file, you need
to specify the fileDir in the URI");
}
final DataLakeFileClientWrapper fileClientWrapper =
getFileClientWrapper(exchange);
- final File recieverFile = new File(fileDir,
fileClientWrapper.getFileName());
+ final File recieverFile = resolveWithinDirectory(fileDir,
fileClientWrapper.getFileName());
final FileCommonRequestOptions commonRequestOptions =
getCommonRequestOptions(exchange);
final FileRange fileRange = configurationProxy.getFileRange(exchange);
final ParallelTransferOptions parallelTransferOptions =
configurationProxy.getParallelTransferOptions(exchange);
@@ -253,4 +254,18 @@ public class DataLakeFileOperations {
final DataLakeFileClient fileClient =
configurationProxy.getFileClient(exchange);
return null == fileClient ? client : new
DataLakeFileClientWrapper(fileClient);
}
+
+ private static File resolveWithinDirectory(String fileDir, String name) {
+ final File target = new File(fileDir, name);
+ // normalize lexically (removes ./ and ../ segments) and compare on
path-segment boundaries so a sibling
+ // directory whose name merely extends fileDir is not considered
contained
+ final Path normalizedDir = new File(fileDir).toPath().normalize();
+ final Path normalizedTarget = target.toPath().normalize();
+ if (!normalizedTarget.startsWith(normalizedDir)) {
+ throw new IllegalArgumentException(
+ "Cannot download to file '" + name
+ + "' as it resolves outside the
configured fileDir directory: " + fileDir);
+ }
+ return target;
+ }
}
diff --git
a/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java
b/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java
index 0c3380d43b7f..d2db2a7ac0f8 100644
---
a/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java
+++
b/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java
@@ -44,6 +44,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -93,6 +94,19 @@ public class DataLakeFileOperationTest extends
CamelTestSupport {
assertNotNull(secondResponse.getHeaders());
}
+ @Test
+ void testDownloadToFileRejectsPathTraversalName() {
+ // a file name is influenced by whoever writes to the file system and
may contain ../ segments;
+ // the download must stay within the configured fileDir
+ configuration.setFileDir("/tmp/camel-azure-datalake-download");
+ when(client.getFileName()).thenReturn("../escaped/PROOF_PWNED");
+
+ final DataLakeFileOperations operations = new
DataLakeFileOperations(configuration, client);
+ final Exchange exchange = new DefaultExchange(context);
+
+ assertThrows(IllegalArgumentException.class, () ->
operations.downloadToFile(exchange));
+ }
+
@Test
void testUploadFile() throws Exception {
final OffsetDateTime time = OffsetDateTime.now();
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
index 0358aaeba807..88002166e0b9 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
@@ -1488,3 +1488,13 @@ non-`Camel`-prefixed application headers and map them to
the corresponding
the `salesforce:` `to`. As defence-in-depth, strip inbound Camel-internal
headers
arriving from untrusted producers with `removeHeaders("CamelSalesforce*")` (or
the
broader `removeHeaders("Camel*")`) before the producer.
+
+=== camel-azure-storage-blob / camel-azure-storage-datalake - download
contained within fileDir
+
+When `fileDir` is configured, the Azure Storage Blob and DataLake consumers
now ensure the downloaded
+local file stays within the configured directory, so a remote object name
containing `../` sequences
+can no longer resolve to a path outside it. This is consistent with the
containment already performed
+by the file-based consumers.
+
+Ordinary object names are unaffected. A name that resolves outside `fileDir`
is now rejected with an
+`IllegalArgumentException`.