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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-text.git


The following commit(s) were added to refs/heads/master by this push:
     new c68fe3c2 Fix path fence bypass for relative paths with leading ".." 
(#745)
c68fe3c2 is described below

commit c68fe3c2a373a837b1d064809e8ebaec6e510f9c
Author: Javid Khan <[email protected]>
AuthorDate: Mon Jun 1 21:54:17 2026 +0530

    Fix path fence bypass for relative paths with leading ".." (#745)
    
    * fix path fence bypass for relative paths with leading ..
    
    * Add regression test for relative parent path fence bypass
    
    * Add test for fence root normalization in constructor
---
 .../org/apache/commons/text/lookup/PathFence.java  |  4 ++--
 .../commons/text/lookup/FileStringLookupTest.java  | 23 ++++++++++++++++++++++
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/commons/text/lookup/PathFence.java 
b/src/main/java/org/apache/commons/text/lookup/PathFence.java
index 95b9b615..6c83082e 100644
--- a/src/main/java/org/apache/commons/text/lookup/PathFence.java
+++ b/src/main/java/org/apache/commons/text/lookup/PathFence.java
@@ -82,7 +82,7 @@ final class PathFence {
      * @param builder A builder.
      */
     private PathFence(final Builder builder) {
-        this.roots = 
Arrays.stream(builder.roots).map(Path::toAbsolutePath).collect(Collectors.toList());
+        this.roots = Arrays.stream(builder.roots).map(p -> 
p.toAbsolutePath().normalize()).collect(Collectors.toList());
     }
 
     /**
@@ -97,7 +97,7 @@ final class PathFence {
         if (roots.isEmpty()) {
             return path;
         }
-        final Path pathAbs = path.normalize().toAbsolutePath();
+        final Path pathAbs = path.toAbsolutePath().normalize();
         final Optional<Path> first = 
roots.stream().filter(pathAbs::startsWith).findFirst();
         if (first.isPresent()) {
             return path;
diff --git 
a/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java 
b/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java
index d87be3ae..301dd609 100644
--- a/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java
+++ b/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java
@@ -31,6 +31,7 @@ import java.nio.file.Paths;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.text.StringSubstitutor;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 /**
  * Tests {@link FileStringLookup}.
@@ -118,6 +119,28 @@ public class FileStringLookupTest {
         testFence(expectedString, fileStringLookup);
     }
 
+    @Test
+    void testFenceRelativeParentTraversal(@TempDir final Path tempDir) throws 
Exception {
+        // A real, readable file that lives outside the fence but is reachable 
from the working
+        // directory through leading ".." segments. The fence must reject it; 
if the leading ".."
+        // survives unresolved, the prefix check passes and the file is read, 
escaping the fence.
+        final Path secret = Files.write(tempDir.resolve("secret.txt"), 
"secret".getBytes(StandardCharsets.UTF_8));
+        final Path relativeEscape = 
CURRENT_PATH.toAbsolutePath().relativize(secret);
+        final FileStringLookup fileStringLookup = new 
FileStringLookup(CURRENT_PATH);
+        assertThrows(IllegalArgumentException.class, () -> 
fileStringLookup.apply("UTF-8:" + relativeEscape));
+    }
+
+    @Test
+    void testFenceRootWithParentSegment() throws Exception {
+        // A fence root that itself carries an unresolved ".." segment must be 
normalized when the
+        // fence is built. Otherwise the component-wise prefix check never 
matches and a file that
+        // really is inside the fence is wrongly rejected. Here "target/.." 
resolves to the working
+        // directory, so the in-fence document must still be readable.
+        final String expectedString = readDocumentFixtureString();
+        final FileStringLookup fileStringLookup = new 
FileStringLookup(Paths.get("target/.."));
+        assertEquals(expectedString, 
fileStringLookup.apply("UTF-8:src/test/resources/org/apache/commons/text/document.properties"));
+    }
+
     @Test
     void testFenceEmptyOne() throws Exception {
         final String expectedString = readDocumentFixtureString();

Reply via email to