This is an automated email from the ASF dual-hosted git repository.

oscerd pushed a commit to branch camel-4.14.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.14.x by this push:
     new 2f0ec28fbcea [backport camel-4.14.x] CAMEL-23765: remote-file 
consumers - contain localWorkDirectory downloads within the work directory 
(#24220)
2f0ec28fbcea is described below

commit 2f0ec28fbcea753609b581e8bacfc18acd96c9b7
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Jun 24 15:20:36 2026 +0200

    [backport camel-4.14.x] CAMEL-23765: remote-file consumers - contain 
localWorkDirectory downloads within the work directory (#24220)
    
    CAMEL-23765: remote-file consumers - contain localWorkDirectory downloads 
within the work directory
    
    Backport of #24180 to camel-4.14.x.
    
    When localWorkDirectory was enabled, the remote-file consumers built the 
local
    work file path from the remote file name without ensuring the result stayed
    within the configured work directory, so a remote file name containing ../
    sequences could resolve to a path outside it. A shared
    GenericFileHelper.jailToLocalWorkDirectory containment check (compactPath +
    startsWith, mirroring the file producer's jail) is now applied to both the
    in-progress temp file and the final file, before mkdirs, in FtpOperations,
    SftpOperations, FilesOperations and SmbOperations. It reuses the existing
    jailStartingDirectory option (default true).
    
    Note: camel-mina-sftp does not exist on camel-4.14.x (added later), so its
    MinaSftpOperations change from the main PR is not applicable here. 
Source-only
    backport: the jailStartingDirectory producer->common label change and its
    regenerated metadata are intentionally omitted; the upgrade-guide entry 
lives on
    main.
    
    Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
 .../component/file/azure/FilesOperations.java      |  8 ++++
 .../camel/component/file/GenericFileHelper.java    | 23 +++++++++++
 .../component/file/GenericFileHelperTest.java      | 48 ++++++++++++++++++++++
 .../camel/component/file/remote/FtpOperations.java |  8 ++++
 .../component/file/remote/SftpOperations.java      |  8 ++++
 .../apache/camel/component/smb/SmbOperations.java  | 12 +++++-
 6 files changed, 105 insertions(+), 2 deletions(-)

diff --git 
a/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesOperations.java
 
b/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesOperations.java
index d669187fbbe6..423d03efdaed 100644
--- 
a/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesOperations.java
+++ 
b/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesOperations.java
@@ -47,6 +47,7 @@ import org.apache.camel.component.file.FileComponent;
 import org.apache.camel.component.file.GenericFile;
 import org.apache.camel.component.file.GenericFileEndpoint;
 import org.apache.camel.component.file.GenericFileExist;
+import org.apache.camel.component.file.GenericFileHelper;
 import org.apache.camel.component.file.GenericFileOperationFailedException;
 import org.apache.camel.component.file.remote.RemoteFile;
 import org.apache.camel.component.file.remote.RemoteFileConfiguration;
@@ -301,9 +302,16 @@ public class FilesOperations extends NormalizedOperations {
                     "Exchange should have the " + 
FileComponent.FILE_EXCHANGE_FILE + " set");
             String relativeName = target.getRelativeFilePath();
 
+            File localWorkDir = local;
             inProgress = new File(local, relativeName + ".inprogress");
             local = new File(local, relativeName);
 
+            // ensure the local work file stays within the local work 
directory (CAMEL-23765)
+            if (endpoint.isJailStartingDirectory()) {
+                GenericFileHelper.jailToLocalWorkDirectory(inProgress, 
localWorkDir);
+                GenericFileHelper.jailToLocalWorkDirectory(local, 
localWorkDir);
+            }
+
             // create directory to local work file
             boolean result = local.mkdirs();
             if (!result) {
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileHelper.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileHelper.java
index b86351c010d6..ba40b2948e78 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileHelper.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileHelper.java
@@ -16,16 +16,39 @@
  */
 package org.apache.camel.component.file;
 
+import java.io.File;
 import java.util.function.Supplier;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.support.MessageHelper;
+import org.apache.camel.util.FileUtil;
 
 public final class GenericFileHelper {
 
     private GenericFileHelper() {
     }
 
+    /**
+     * Ensures the resolved local work file stays within the configured local 
work directory. The remote file name used
+     * to build the local work file path may contain {@code ../} sequences 
that would otherwise resolve to a path
+     * outside the work directory.
+     *
+     * @param  target                              the resolved local work 
file (or its in-progress temp file)
+     * @param  localWorkDirectory                  the local work directory 
the file must stay within
+     * @throws GenericFileOperationFailedException if the target resolves 
outside the local work directory
+     */
+    public static void jailToLocalWorkDirectory(File target, File 
localWorkDirectory) {
+        // compact first as the remote relative name can use ../ etc
+        String compactTarget = FileUtil.compactPath(target.getPath());
+        String compactWork = 
FileUtil.compactPath(localWorkDirectory.getPath());
+        if (!compactTarget.startsWith(compactWork)) {
+            throw new GenericFileOperationFailedException(
+                    "Cannot retrieve file to local work file: " + compactTarget
+                                                          + " as it is jailed 
to the local work directory: "
+                                                          + compactWork);
+        }
+    }
+
     public static String asExclusiveReadLockKey(GenericFile file, String key) {
         // use the copy from absolute path as that was the original path of the
         // file when the lock was acquired
diff --git 
a/components/camel-file/src/test/java/org/apache/camel/component/file/GenericFileHelperTest.java
 
b/components/camel-file/src/test/java/org/apache/camel/component/file/GenericFileHelperTest.java
new file mode 100644
index 000000000000..5eefdbfcd907
--- /dev/null
+++ 
b/components/camel-file/src/test/java/org/apache/camel/component/file/GenericFileHelperTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.file;
+
+import java.io.File;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class GenericFileHelperTest {
+
+    private final File workDir = new File("target/localwork");
+
+    @Test
+    public void shouldAllowFilesWithinLocalWorkDirectory() {
+        // a plain name, a nested name, and a ../ that still resolves within 
the work directory are all allowed
+        assertDoesNotThrow(() -> 
GenericFileHelper.jailToLocalWorkDirectory(new File(workDir, "file.txt"), 
workDir));
+        assertDoesNotThrow(() -> 
GenericFileHelper.jailToLocalWorkDirectory(new File(workDir, 
"sub/dir/file.txt"), workDir));
+        assertDoesNotThrow(() -> 
GenericFileHelper.jailToLocalWorkDirectory(new File(workDir, 
"sub/../file.txt"), workDir));
+    }
+
+    @Test
+    public void shouldRejectFilesEscapingLocalWorkDirectory() {
+        // a remote file name that resolves outside the configured local work 
directory must be rejected
+        assertThrows(GenericFileOperationFailedException.class,
+                () -> GenericFileHelper.jailToLocalWorkDirectory(new 
File(workDir, "../escape.txt"), workDir));
+        assertThrows(GenericFileOperationFailedException.class,
+                () -> GenericFileHelper.jailToLocalWorkDirectory(new 
File(workDir, "../../etc/passwd"), workDir));
+        assertThrows(GenericFileOperationFailedException.class,
+                () -> GenericFileHelper.jailToLocalWorkDirectory(new 
File(workDir, "sub/../../escape.txt"), workDir));
+    }
+}
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
index d1dc62fcb5c8..55055192873e 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
@@ -32,6 +32,7 @@ import org.apache.camel.component.file.FileComponent;
 import org.apache.camel.component.file.GenericFile;
 import org.apache.camel.component.file.GenericFileEndpoint;
 import org.apache.camel.component.file.GenericFileExist;
+import org.apache.camel.component.file.GenericFileHelper;
 import org.apache.camel.component.file.GenericFileOperationFailedException;
 import org.apache.camel.support.ObjectHelper;
 import org.apache.camel.support.task.BlockingTask;
@@ -525,9 +526,16 @@ public class FtpOperations implements 
RemoteFileOperations<FTPFile> {
                     "Exchange should have the " + 
FileComponent.FILE_EXCHANGE_FILE + " set");
             String relativeName = target.getRelativeFilePath();
 
+            File localWorkDir = local;
             temp = new File(local, relativeName + ".inprogress");
             local = new File(local, relativeName);
 
+            // ensure the local work file stays within the local work 
directory (CAMEL-23765)
+            if (endpoint.isJailStartingDirectory()) {
+                GenericFileHelper.jailToLocalWorkDirectory(temp, localWorkDir);
+                GenericFileHelper.jailToLocalWorkDirectory(local, 
localWorkDir);
+            }
+
             // create directory to local work file
             boolean result = local.mkdirs();
             if (!result) {
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
index 76b40e95de38..f661f8c9ea48 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
@@ -55,6 +55,7 @@ import org.apache.camel.component.file.FileComponent;
 import org.apache.camel.component.file.GenericFile;
 import org.apache.camel.component.file.GenericFileEndpoint;
 import org.apache.camel.component.file.GenericFileExist;
+import org.apache.camel.component.file.GenericFileHelper;
 import org.apache.camel.component.file.GenericFileOperationFailedException;
 import org.apache.camel.spi.CamelLogger;
 import org.apache.camel.support.ResourceHelper;
@@ -935,9 +936,16 @@ public class SftpOperations implements 
RemoteFileOperations<SftpRemoteFile> {
             // use relative filename in local work directory
             String relativeName = file.getRelativeFilePath();
 
+            File localWorkDir = local;
             temp = new File(local, relativeName + ".inprogress");
             local = new File(local, relativeName);
 
+            // ensure the local work file stays within the local work 
directory (CAMEL-23765)
+            if (endpoint.isJailStartingDirectory()) {
+                GenericFileHelper.jailToLocalWorkDirectory(temp, localWorkDir);
+                GenericFileHelper.jailToLocalWorkDirectory(local, 
localWorkDir);
+            }
+
             // create directory to local work file
             local.mkdirs();
 
diff --git 
a/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
 
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
index ba038a897233..5b37affa2ef0 100644
--- 
a/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
+++ 
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
@@ -44,6 +44,7 @@ import org.apache.camel.component.file.FileComponent;
 import org.apache.camel.component.file.GenericFile;
 import org.apache.camel.component.file.GenericFileEndpoint;
 import org.apache.camel.component.file.GenericFileExist;
+import org.apache.camel.component.file.GenericFileHelper;
 import org.apache.camel.component.file.GenericFileOperationFailedException;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
@@ -291,11 +292,18 @@ public class SmbOperations implements SmbFileOperations {
             // use relative filename in local work directory
             String relativeName = file.getRelativeFilePath();
 
+            java.io.File localWorkDir = local;
             temp = new java.io.File(local, relativeName + ".inprogress");
+            local = new java.io.File(local, relativeName);
+
+            // ensure the local work file stays within the local work 
directory (CAMEL-23765)
+            if (endpoint.isJailStartingDirectory()) {
+                GenericFileHelper.jailToLocalWorkDirectory(temp, localWorkDir);
+                GenericFileHelper.jailToLocalWorkDirectory(local, 
localWorkDir);
+            }
 
             // create directory to local work file
-            local.mkdirs();
-            local = new java.io.File(local, relativeName);
+            localWorkDir.mkdirs();
 
             // delete any existing files
             if (temp.exists()) {

Reply via email to