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();