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

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


The following commit(s) were added to refs/heads/main by this push:
     new 0972796fa5bb CAMEL-23868: camel-file - make local work directory / 
starting directory containment checks path-boundary aware (#24377)
0972796fa5bb is described below

commit 0972796fa5bbe6359ab9dfe906989674cc451803
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Jul 6 09:53:06 2026 +0200

    CAMEL-23868: camel-file - make local work directory / starting directory 
containment checks path-boundary aware (#24377)
    
    The localWorkDirectory containment check 
(GenericFileHelper.jailToLocalWorkDirectory,
    added in CAMEL-23765) and the starting-directory check 
(GenericFileProducer.jailedCheck)
    both used a bare String.startsWith prefix test, which ignores path-segment 
boundaries.
    When the compacted directory has no trailing separator (as 
jailToLocalWorkDirectory
    produces via File.getPath), a sibling directory whose name merely extends 
the work
    directory's name (for example .../localwork versus .../localworkEVIL) 
passed the check.
    
    Introduce a shared, boundary-aware GenericFileHelper.isWithinDirectory and 
route both
    checks through it so they cannot diverge. It tolerates a trailing separator 
(the producer
    supplies one) and treats an empty directory as no boundary, preserving 
existing behaviour.
    
    Add GenericFileHelperTest coverage for a name-prefixed sibling and for the 
shared helper's
    boundary, trailing-separator and empty-directory cases.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../camel/component/file/GenericFileHelper.java    | 26 +++++++++++++++++++++-
 .../camel/component/file/GenericFileProducer.java  |  2 +-
 .../component/file/GenericFileHelperTest.java      | 26 ++++++++++++++++++++++
 3 files changed, 52 insertions(+), 2 deletions(-)

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 ba40b2948e78..8bb09439b40a 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
@@ -41,7 +41,7 @@ public final class GenericFileHelper {
         // 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)) {
+        if (!isWithinDirectory(compactTarget, compactWork)) {
             throw new GenericFileOperationFailedException(
                     "Cannot retrieve file to local work file: " + compactTarget
                                                           + " as it is jailed 
to the local work directory: "
@@ -49,6 +49,30 @@ public final class GenericFileHelper {
         }
     }
 
+    /**
+     * Determines whether a compacted target path is contained within a 
compacted directory path, using a path-segment
+     * boundary comparison. Unlike a bare string prefix test, a sibling 
directory whose name merely extends the
+     * directory name (for example {@code /work} versus {@code /workspace}) is 
not considered contained. A trailing
+     * separator on the directory is tolerated, and an empty directory imposes 
no boundary.
+     *
+     * @param  compactTarget the compacted target path (see {@link 
FileUtil#compactPath(String)})
+     * @param  compactDir    the compacted directory the target must stay 
within
+     * @return               {@code true} if the target is the directory 
itself or a path inside it
+     */
+    public static boolean isWithinDirectory(String compactTarget, String 
compactDir) {
+        if (compactDir.isEmpty()) {
+            // no directory boundary configured
+            return true;
+        }
+        // drop a trailing separator (if any) so the boundary comparison is 
exact, regardless of whether the
+        // directory path was supplied with or without one
+        String dir = compactDir;
+        if (dir.charAt(dir.length() - 1) == File.separatorChar) {
+            dir = dir.substring(0, dir.length() - 1);
+        }
+        return compactTarget.equals(dir) || compactTarget.startsWith(dir + 
File.separator);
+    }
+
     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/main/java/org/apache/camel/component/file/GenericFileProducer.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileProducer.java
index 688fd4826d1e..ab50db46a208 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileProducer.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileProducer.java
@@ -432,7 +432,7 @@ public class GenericFileProducer<T> extends 
DefaultAsyncProducer {
         // first as the name can be using relative paths via ../ etc)
         String compatchAnswer = FileUtil.compactPath(answer);
         String compatchBaseDir = FileUtil.compactPath(baseDir);
-        if (!compatchAnswer.startsWith(compatchBaseDir)) {
+        if (!GenericFileHelper.isWithinDirectory(compatchAnswer, 
compatchBaseDir)) {
             throw new IllegalArgumentException(
                     "Cannot write file with name: " + compatchAnswer
                                                + " as the filename is jailed 
to the starting directory: "
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
index 5eefdbfcd907..e52a63ffa497 100644
--- 
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
@@ -21,7 +21,9 @@ 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.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class GenericFileHelperTest {
 
@@ -44,5 +46,29 @@ public class GenericFileHelperTest {
                 () -> GenericFileHelper.jailToLocalWorkDirectory(new 
File(workDir, "../../etc/passwd"), workDir));
         assertThrows(GenericFileOperationFailedException.class,
                 () -> GenericFileHelper.jailToLocalWorkDirectory(new 
File(workDir, "sub/../../escape.txt"), workDir));
+        // a sibling directory whose name merely extends the work directory 
name must also be rejected
+        assertThrows(GenericFileOperationFailedException.class,
+                () -> GenericFileHelper.jailToLocalWorkDirectory(new 
File(workDir, "../localworkEVIL/file.txt"), workDir));
+    }
+
+    @Test
+    public void isWithinDirectoryRespectsPathBoundaries() {
+        String sep = File.separator;
+        String work = sep + "data" + sep + "work";
+
+        // the directory itself and real children are contained
+        assertTrue(GenericFileHelper.isWithinDirectory(work, work));
+        assertTrue(GenericFileHelper.isWithinDirectory(work + sep + "a.txt", 
work));
+        assertTrue(GenericFileHelper.isWithinDirectory(work + sep + "sub" + 
sep + "a.txt", work));
+
+        // a trailing separator on the directory (as supplied by the file 
producer) is tolerated
+        assertTrue(GenericFileHelper.isWithinDirectory(work + sep + "a.txt", 
work + sep));
+
+        // a sibling whose name merely extends the directory name is NOT 
contained
+        assertFalse(GenericFileHelper.isWithinDirectory(sep + "data" + sep + 
"workspace" + sep + "a.txt", work));
+        assertFalse(GenericFileHelper.isWithinDirectory(sep + "data" + sep + 
"workspace" + sep + "a.txt", work + sep));
+
+        // an empty directory imposes no boundary
+        assertTrue(GenericFileHelper.isWithinDirectory("anything.txt", ""));
     }
 }

Reply via email to