From dca7046d8c94b1d4a3989981e404eb807c24fbe9 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 18 Apr 2026 10:35:15 +0100 Subject: [PATCH 1/5] Make inline expectation comments specify query --- .../tests/PartialPathTraversalTest.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java index b1986c1b6694..4c5f83e433a6 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java @@ -10,14 +10,14 @@ public class PartialPathTraversalTest { public void esapiExample(File parent) throws IOException { - if (!dir().getCanonicalPath().startsWith(parent.getCanonicalPath())) { // $ Alert + if (!dir().getCanonicalPath().startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @SuppressWarnings("ResultOfMethodCallIgnored") void foo1(File parent) throws IOException { - (dir().getCanonicalPath()).startsWith((parent.getCanonicalPath())); // $ Alert + (dir().getCanonicalPath()).startsWith((parent.getCanonicalPath())); // $ Alert[java/partial-path-traversal-from-remote] } void foo2(File parent) throws IOException { @@ -29,31 +29,31 @@ void foo2(File parent) throws IOException { void foo3(File parent) throws IOException { String parentPath = parent.getCanonicalPath(); - if (!dir().getCanonicalPath().startsWith(parentPath)) { // $ Alert + if (!dir().getCanonicalPath().startsWith(parentPath)) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } void foo4() throws IOException { - if (!dir().getCanonicalPath().startsWith("/usr" + "/dir")) { // $ Alert + if (!dir().getCanonicalPath().startsWith("/usr" + "/dir")) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } void foo5(File parent) throws IOException { String canonicalPath = dir().getCanonicalPath(); - if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert + if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } void foo6(File parent) throws IOException { String canonicalPath = dir().getCanonicalPath(); - if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert + if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } String canonicalPath2 = dir().getCanonicalPath(); - if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $ Alert + if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -61,10 +61,10 @@ void foo6(File parent) throws IOException { void foo7(File dir, File parent) throws IOException { String canonicalPath = dir().getCanonicalPath(); String canonicalPath2 = dir().getCanonicalPath(); - if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert + if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } - if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $ Alert + if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -94,7 +94,7 @@ void foo10(File parent) throws IOException { void foo11(File parent) throws IOException { String parentCanonical = parent.getCanonicalPath(); - if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert + if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -102,10 +102,10 @@ void foo11(File parent) throws IOException { void foo12(File parent) throws IOException { String parentCanonical = parent.getCanonicalPath(); String parentCanonical2 = parent.getCanonicalPath(); - if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert + if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } - if (!dir().getCanonicalPath().startsWith(parentCanonical2)) { // $ Alert + if (!dir().getCanonicalPath().startsWith(parentCanonical2)) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -173,7 +173,7 @@ void foo18(File dir, File parent, boolean branch) throws IOException { void foo19(File parent) throws IOException { String parentCanonical = parent.getCanonicalPath() + "/potato"; - if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert + if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -191,7 +191,7 @@ InputStream foo20() { String filePath = sb.toString(); File encodedFile = new File(filePath); try { - if (!encodedFile.getCanonicalPath().startsWith(cacheDir.getCanonicalPath())) { // $ Alert + if (!encodedFile.getCanonicalPath().startsWith(cacheDir.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] return null; } return Files.newInputStream(encodedFile.toPath()); @@ -209,7 +209,7 @@ void foo21(File parent) throws IOException { void foo22(File dir2, File parent, boolean conditional) throws IOException { String canonicalPath = conditional ? dir().getCanonicalPath() : dir2.getCanonicalPath(); - if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert + if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } From 63d20a54d46c2b87190f3631f1606075215f22f5 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 18 Apr 2026 10:48:14 +0100 Subject: [PATCH 2/5] Use inline expectations with second test Co-authored-by: Copilot --- .../semmle/tests/PartialPathTraversal.qlref | 5 ++- .../tests/PartialPathTraversalTest.java | 32 +++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.qlref b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.qlref index 431556c90afa..9d7e47fca707 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.qlref +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.qlref @@ -1 +1,4 @@ -Security/CWE/CWE-023/PartialPathTraversal.ql \ No newline at end of file +query: Security/CWE/CWE-023/PartialPathTraversal.ql +postprocess: + - utils/test/PrettyPrintModels.ql + - utils/test/InlineExpectationsTestQuery.ql diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java index 4c5f83e433a6..a8d0b7396aed 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java @@ -10,14 +10,14 @@ public class PartialPathTraversalTest { public void esapiExample(File parent) throws IOException { - if (!dir().getCanonicalPath().startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] + if (!dir().getCanonicalPath().startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @SuppressWarnings("ResultOfMethodCallIgnored") void foo1(File parent) throws IOException { - (dir().getCanonicalPath()).startsWith((parent.getCanonicalPath())); // $ Alert[java/partial-path-traversal-from-remote] + (dir().getCanonicalPath()).startsWith((parent.getCanonicalPath())); // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] } void foo2(File parent) throws IOException { @@ -29,31 +29,31 @@ void foo2(File parent) throws IOException { void foo3(File parent) throws IOException { String parentPath = parent.getCanonicalPath(); - if (!dir().getCanonicalPath().startsWith(parentPath)) { // $ Alert[java/partial-path-traversal-from-remote] + if (!dir().getCanonicalPath().startsWith(parentPath)) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } void foo4() throws IOException { - if (!dir().getCanonicalPath().startsWith("/usr" + "/dir")) { // $ Alert[java/partial-path-traversal-from-remote] + if (!dir().getCanonicalPath().startsWith("/usr" + "/dir")) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } void foo5(File parent) throws IOException { String canonicalPath = dir().getCanonicalPath(); - if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] + if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } void foo6(File parent) throws IOException { String canonicalPath = dir().getCanonicalPath(); - if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] + if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } String canonicalPath2 = dir().getCanonicalPath(); - if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] + if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -61,10 +61,10 @@ void foo6(File parent) throws IOException { void foo7(File dir, File parent) throws IOException { String canonicalPath = dir().getCanonicalPath(); String canonicalPath2 = dir().getCanonicalPath(); - if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] + if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } - if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] + if (!canonicalPath2.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -75,7 +75,7 @@ File getChild() { void foo8(File parent) throws IOException { String canonicalPath = getChild().getCanonicalPath(); - if (!canonicalPath.startsWith(parent.getCanonicalPath())) { + if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + getChild().getCanonicalPath()); } } @@ -94,7 +94,7 @@ void foo10(File parent) throws IOException { void foo11(File parent) throws IOException { String parentCanonical = parent.getCanonicalPath(); - if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert[java/partial-path-traversal-from-remote] + if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -102,10 +102,10 @@ void foo11(File parent) throws IOException { void foo12(File parent) throws IOException { String parentCanonical = parent.getCanonicalPath(); String parentCanonical2 = parent.getCanonicalPath(); - if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert[java/partial-path-traversal-from-remote] + if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } - if (!dir().getCanonicalPath().startsWith(parentCanonical2)) { // $ Alert[java/partial-path-traversal-from-remote] + if (!dir().getCanonicalPath().startsWith(parentCanonical2)) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -173,7 +173,7 @@ void foo18(File dir, File parent, boolean branch) throws IOException { void foo19(File parent) throws IOException { String parentCanonical = parent.getCanonicalPath() + "/potato"; - if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert[java/partial-path-traversal-from-remote] + if (!dir().getCanonicalPath().startsWith(parentCanonical)) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } @@ -191,7 +191,7 @@ InputStream foo20() { String filePath = sb.toString(); File encodedFile = new File(filePath); try { - if (!encodedFile.getCanonicalPath().startsWith(cacheDir.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] + if (!encodedFile.getCanonicalPath().startsWith(cacheDir.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] return null; } return Files.newInputStream(encodedFile.toPath()); @@ -209,7 +209,7 @@ void foo21(File parent) throws IOException { void foo22(File dir2, File parent, boolean conditional) throws IOException { String canonicalPath = conditional ? dir().getCanonicalPath() : dir2.getCanonicalPath(); - if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] + if (!canonicalPath.startsWith(parent.getCanonicalPath())) { // $ Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } From 6099c5d034c28bed9dfe1ff46e5bc63f42aae5dd Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 18 Apr 2026 19:56:19 +0100 Subject: [PATCH 3/5] Add SPURIOUS test for `+= File.separator` --- .../tests/PartialPathTraversal.expected | 1 + ...artialPathTraversalFromRemoteTest.expected | 103 +++++++++--------- .../tests/PartialPathTraversalTest.java | 8 ++ 3 files changed, 63 insertions(+), 49 deletions(-) diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.expected b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.expected index 5379de2403b4..a30ff929df8a 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.expected +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.expected @@ -14,3 +14,4 @@ | PartialPathTraversalTest.java:176:14:176:65 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal. | | PartialPathTraversalTest.java:194:18:194:87 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal. | | PartialPathTraversalTest.java:212:14:212:64 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal. | +| PartialPathTraversalTest.java:234:14:234:54 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal. | diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.expected b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.expected index f2af01542ee9..0c88cb8107f7 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.expected +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.expected @@ -1,19 +1,20 @@ #select -| PartialPathTraversalTest.java:13:14:13:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:13:14:13:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:20:10:20:33 | getCanonicalPath(...) | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:20:10:20:33 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:32:14:32:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:32:14:32:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:38:14:38:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:38:14:38:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:45:14:45:26 | canonicalPath | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:45:14:45:26 | canonicalPath | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:52:14:52:26 | canonicalPath | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:52:14:52:26 | canonicalPath | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:56:14:56:27 | canonicalPath2 | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:56:14:56:27 | canonicalPath2 | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:64:14:64:26 | canonicalPath | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:64:14:64:26 | canonicalPath | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:67:14:67:27 | canonicalPath2 | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:67:14:67:27 | canonicalPath2 | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:97:14:97:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:97:14:97:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:105:14:105:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:105:14:105:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:108:14:108:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:108:14:108:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:176:14:176:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:176:14:176:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:194:18:194:47 | getCanonicalPath(...) | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:194:18:194:47 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:13:14:13:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:13:14:13:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:20:10:20:33 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:20:10:20:33 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:32:14:32:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:32:14:32:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:38:14:38:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:38:14:38:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:45:14:45:26 | canonicalPath | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:45:14:45:26 | canonicalPath | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:52:14:52:26 | canonicalPath | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:52:14:52:26 | canonicalPath | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:56:14:56:27 | canonicalPath2 | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:56:14:56:27 | canonicalPath2 | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:64:14:64:26 | canonicalPath | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:64:14:64:26 | canonicalPath | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:67:14:67:27 | canonicalPath2 | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:67:14:67:27 | canonicalPath2 | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:97:14:97:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:97:14:97:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:105:14:105:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:105:14:105:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:108:14:108:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:108:14:108:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:176:14:176:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:176:14:176:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:194:18:194:47 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:194:18:194:47 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | +| PartialPathTraversalTest.java:234:14:234:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:234:14:234:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | edges | PartialPathTraversalTest.java:13:14:13:18 | dir(...) : File | PartialPathTraversalTest.java:13:14:13:37 | getCanonicalPath(...) | provenance | MaD:6 | | PartialPathTraversalTest.java:20:10:20:14 | dir(...) : File | PartialPathTraversalTest.java:20:10:20:33 | getCanonicalPath(...) | provenance | MaD:6 | @@ -43,30 +44,32 @@ edges | PartialPathTraversalTest.java:194:18:194:28 | encodedFile : File | PartialPathTraversalTest.java:194:18:194:47 | getCanonicalPath(...) | provenance | MaD:6 | | PartialPathTraversalTest.java:211:46:211:50 | dir(...) : File | PartialPathTraversalTest.java:211:46:211:69 | getCanonicalPath(...) : String | provenance | MaD:6 | | PartialPathTraversalTest.java:211:46:211:69 | getCanonicalPath(...) : String | PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | provenance | | -| PartialPathTraversalTest.java:252:45:252:117 | new BufferedReader(...) : BufferedReader | PartialPathTraversalTest.java:253:31:253:44 | filenameReader : BufferedReader | provenance | | -| PartialPathTraversalTest.java:252:64:252:116 | new InputStreamReader(...) : InputStreamReader | PartialPathTraversalTest.java:252:45:252:117 | new BufferedReader(...) : BufferedReader | provenance | MaD:2 | -| PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:252:64:252:116 | new InputStreamReader(...) : InputStreamReader | provenance | Src:MaD:1 MaD:7 | -| PartialPathTraversalTest.java:253:31:253:44 | filenameReader : BufferedReader | PartialPathTraversalTest.java:253:31:253:55 | readLine(...) : String | provenance | MaD:3 | -| PartialPathTraversalTest.java:253:31:253:55 | readLine(...) : String | PartialPathTraversalTest.java:254:29:254:36 | filename : String | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:13:14:13:18 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:20:10:20:14 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:32:14:32:18 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:38:14:38:18 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:44:32:44:36 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:51:32:51:36 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:55:33:55:37 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:62:32:62:36 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:63:33:63:37 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:97:14:97:18 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:105:14:105:18 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:108:14:108:18 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:176:14:176:18 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:211:46:211:50 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | PartialPathTraversalTest.java:261:16:261:20 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:254:29:254:36 | filename : String | PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | provenance | MaD:4 | -| PartialPathTraversalTest.java:261:16:261:20 | dir(...) : File | PartialPathTraversalTest.java:261:16:261:38 | getAbsolutePath(...) : String | provenance | MaD:5 | -| PartialPathTraversalTest.java:261:16:261:38 | getAbsolutePath(...) : String | PartialPathTraversalTest.java:261:16:261:60 | split(...) : String[] | provenance | MaD:10 | -| PartialPathTraversalTest.java:261:16:261:60 | split(...) : String[] | PartialPathTraversalTest.java:186:25:186:30 | path(...) : String[] | provenance | | +| PartialPathTraversalTest.java:234:14:234:18 | dir(...) : File | PartialPathTraversalTest.java:234:14:234:37 | getCanonicalPath(...) | provenance | MaD:6 | +| PartialPathTraversalTest.java:260:45:260:117 | new BufferedReader(...) : BufferedReader | PartialPathTraversalTest.java:261:31:261:44 | filenameReader : BufferedReader | provenance | | +| PartialPathTraversalTest.java:260:64:260:116 | new InputStreamReader(...) : InputStreamReader | PartialPathTraversalTest.java:260:45:260:117 | new BufferedReader(...) : BufferedReader | provenance | MaD:2 | +| PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:260:64:260:116 | new InputStreamReader(...) : InputStreamReader | provenance | Src:MaD:1 MaD:7 | +| PartialPathTraversalTest.java:261:31:261:44 | filenameReader : BufferedReader | PartialPathTraversalTest.java:261:31:261:55 | readLine(...) : String | provenance | MaD:3 | +| PartialPathTraversalTest.java:261:31:261:55 | readLine(...) : String | PartialPathTraversalTest.java:262:29:262:36 | filename : String | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:13:14:13:18 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:20:10:20:14 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:32:14:32:18 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:38:14:38:18 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:44:32:44:36 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:51:32:51:36 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:55:33:55:37 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:62:32:62:36 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:63:33:63:37 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:97:14:97:18 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:105:14:105:18 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:108:14:108:18 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:176:14:176:18 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:211:46:211:50 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:234:14:234:18 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:269:16:269:20 | dir(...) : File | provenance | | +| PartialPathTraversalTest.java:262:29:262:36 | filename : String | PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | provenance | MaD:4 | +| PartialPathTraversalTest.java:269:16:269:20 | dir(...) : File | PartialPathTraversalTest.java:269:16:269:38 | getAbsolutePath(...) : String | provenance | MaD:5 | +| PartialPathTraversalTest.java:269:16:269:38 | getAbsolutePath(...) : String | PartialPathTraversalTest.java:269:16:269:60 | split(...) : String[] | provenance | MaD:10 | +| PartialPathTraversalTest.java:269:16:269:60 | split(...) : String[] | PartialPathTraversalTest.java:186:25:186:30 | path(...) : String[] | provenance | | models | 1 | Source: java.net; Socket; false; getInputStream; (); ; ReturnValue; remote; manual | | 2 | Summary: java.io; BufferedReader; false; BufferedReader; ; ; Argument[0]; Argument[this]; taint; manual | @@ -122,14 +125,16 @@ nodes | PartialPathTraversalTest.java:211:46:211:50 | dir(...) : File | semmle.label | dir(...) : File | | PartialPathTraversalTest.java:211:46:211:69 | getCanonicalPath(...) : String | semmle.label | getCanonicalPath(...) : String | | PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | semmle.label | canonicalPath | -| PartialPathTraversalTest.java:252:45:252:117 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader | -| PartialPathTraversalTest.java:252:64:252:116 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader | -| PartialPathTraversalTest.java:252:86:252:106 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | -| PartialPathTraversalTest.java:253:31:253:44 | filenameReader : BufferedReader | semmle.label | filenameReader : BufferedReader | -| PartialPathTraversalTest.java:253:31:253:55 | readLine(...) : String | semmle.label | readLine(...) : String | -| PartialPathTraversalTest.java:254:20:254:37 | new File(...) : File | semmle.label | new File(...) : File | -| PartialPathTraversalTest.java:254:29:254:36 | filename : String | semmle.label | filename : String | -| PartialPathTraversalTest.java:261:16:261:20 | dir(...) : File | semmle.label | dir(...) : File | -| PartialPathTraversalTest.java:261:16:261:38 | getAbsolutePath(...) : String | semmle.label | getAbsolutePath(...) : String | -| PartialPathTraversalTest.java:261:16:261:60 | split(...) : String[] | semmle.label | split(...) : String[] | +| PartialPathTraversalTest.java:234:14:234:18 | dir(...) : File | semmle.label | dir(...) : File | +| PartialPathTraversalTest.java:234:14:234:37 | getCanonicalPath(...) | semmle.label | getCanonicalPath(...) | +| PartialPathTraversalTest.java:260:45:260:117 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader | +| PartialPathTraversalTest.java:260:64:260:116 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader | +| PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | +| PartialPathTraversalTest.java:261:31:261:44 | filenameReader : BufferedReader | semmle.label | filenameReader : BufferedReader | +| PartialPathTraversalTest.java:261:31:261:55 | readLine(...) : String | semmle.label | readLine(...) : String | +| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | semmle.label | new File(...) : File | +| PartialPathTraversalTest.java:262:29:262:36 | filename : String | semmle.label | filename : String | +| PartialPathTraversalTest.java:269:16:269:20 | dir(...) : File | semmle.label | dir(...) : File | +| PartialPathTraversalTest.java:269:16:269:38 | getAbsolutePath(...) : String | semmle.label | getAbsolutePath(...) : String | +| PartialPathTraversalTest.java:269:16:269:60 | split(...) : String[] | semmle.label | split(...) : String[] | subpaths diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java index a8d0b7396aed..89fb6aaf469a 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java @@ -228,6 +228,14 @@ void foo24(File parent) throws IOException { } } + void foo25(File parent) throws IOException { + String path = parent.getCanonicalPath(); + path += File.separator; + if (!dir().getCanonicalPath().startsWith(path)) { // $ SPURIOUS: Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] + throw new IOException("Invalid directory: " + dir().getCanonicalPath()); + } + } + public void doesNotFlagOptimalSafeVersion(File parent) throws IOException { if (!dir().toPath().normalize().startsWith(parent.toPath())) { // Safe throw new IOException("Path traversal attempt: " + dir().getCanonicalPath()); From 6d4a3974ced27ce770b2d0864801adb813079b9e Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 18 Apr 2026 20:02:32 +0100 Subject: [PATCH 4/5] Fix bug so `+= File.separator` is recognized --- .../lib/semmle/code/java/security/PartialPathTraversal.qll | 7 +++++-- .../CWE-023/semmle/tests/PartialPathTraversal.expected | 1 - .../tests/PartialPathTraversalFromRemoteTest.expected | 5 ----- .../CWE-023/semmle/tests/PartialPathTraversalTest.java | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/java/ql/lib/semmle/code/java/security/PartialPathTraversal.qll b/java/ql/lib/semmle/code/java/security/PartialPathTraversal.qll index 63ffb62ef63b..a7b0c50b0827 100644 --- a/java/ql/lib/semmle/code/java/security/PartialPathTraversal.qll +++ b/java/ql/lib/semmle/code/java/security/PartialPathTraversal.qll @@ -40,8 +40,11 @@ private class CharacterLiteralFileSeparatorExpr extends FileSeparatorExpr, Chara CharacterLiteralFileSeparatorExpr() { this.getValue() = "/" or this.getValue() = "\\" } } -private class FileSeparatorAppend extends AddExpr { - FileSeparatorAppend() { this.getRightOperand() instanceof FileSeparatorExpr } +private class FileSeparatorAppend extends BinaryExpr { + FileSeparatorAppend() { + this.(AddExpr).getRightOperand() instanceof FileSeparatorExpr or + this.(AssignAddExpr).getRightOperand() instanceof FileSeparatorExpr + } } private predicate isSafe(Expr expr) { diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.expected b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.expected index a30ff929df8a..5379de2403b4 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.expected +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversal.expected @@ -14,4 +14,3 @@ | PartialPathTraversalTest.java:176:14:176:65 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal. | | PartialPathTraversalTest.java:194:18:194:87 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal. | | PartialPathTraversalTest.java:212:14:212:64 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal. | -| PartialPathTraversalTest.java:234:14:234:54 | startsWith(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal. | diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.expected b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.expected index 0c88cb8107f7..156adced6b08 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.expected +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalFromRemoteTest.expected @@ -14,7 +14,6 @@ | PartialPathTraversalTest.java:176:14:176:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:176:14:176:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | | PartialPathTraversalTest.java:194:18:194:47 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:194:18:194:47 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | | PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | -| PartialPathTraversalTest.java:234:14:234:37 | getCanonicalPath(...) | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:234:14:234:37 | getCanonicalPath(...) | Partial Path Traversal Vulnerability due to insufficient guard against path traversal from $@. | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | user-supplied data | edges | PartialPathTraversalTest.java:13:14:13:18 | dir(...) : File | PartialPathTraversalTest.java:13:14:13:37 | getCanonicalPath(...) | provenance | MaD:6 | | PartialPathTraversalTest.java:20:10:20:14 | dir(...) : File | PartialPathTraversalTest.java:20:10:20:33 | getCanonicalPath(...) | provenance | MaD:6 | @@ -44,7 +43,6 @@ edges | PartialPathTraversalTest.java:194:18:194:28 | encodedFile : File | PartialPathTraversalTest.java:194:18:194:47 | getCanonicalPath(...) | provenance | MaD:6 | | PartialPathTraversalTest.java:211:46:211:50 | dir(...) : File | PartialPathTraversalTest.java:211:46:211:69 | getCanonicalPath(...) : String | provenance | MaD:6 | | PartialPathTraversalTest.java:211:46:211:69 | getCanonicalPath(...) : String | PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | provenance | | -| PartialPathTraversalTest.java:234:14:234:18 | dir(...) : File | PartialPathTraversalTest.java:234:14:234:37 | getCanonicalPath(...) | provenance | MaD:6 | | PartialPathTraversalTest.java:260:45:260:117 | new BufferedReader(...) : BufferedReader | PartialPathTraversalTest.java:261:31:261:44 | filenameReader : BufferedReader | provenance | | | PartialPathTraversalTest.java:260:64:260:116 | new InputStreamReader(...) : InputStreamReader | PartialPathTraversalTest.java:260:45:260:117 | new BufferedReader(...) : BufferedReader | provenance | MaD:2 | | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | PartialPathTraversalTest.java:260:64:260:116 | new InputStreamReader(...) : InputStreamReader | provenance | Src:MaD:1 MaD:7 | @@ -64,7 +62,6 @@ edges | PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:108:14:108:18 | dir(...) : File | provenance | | | PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:176:14:176:18 | dir(...) : File | provenance | | | PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:211:46:211:50 | dir(...) : File | provenance | | -| PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:234:14:234:18 | dir(...) : File | provenance | | | PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | PartialPathTraversalTest.java:269:16:269:20 | dir(...) : File | provenance | | | PartialPathTraversalTest.java:262:29:262:36 | filename : String | PartialPathTraversalTest.java:262:20:262:37 | new File(...) : File | provenance | MaD:4 | | PartialPathTraversalTest.java:269:16:269:20 | dir(...) : File | PartialPathTraversalTest.java:269:16:269:38 | getAbsolutePath(...) : String | provenance | MaD:5 | @@ -125,8 +122,6 @@ nodes | PartialPathTraversalTest.java:211:46:211:50 | dir(...) : File | semmle.label | dir(...) : File | | PartialPathTraversalTest.java:211:46:211:69 | getCanonicalPath(...) : String | semmle.label | getCanonicalPath(...) : String | | PartialPathTraversalTest.java:212:14:212:26 | canonicalPath | semmle.label | canonicalPath | -| PartialPathTraversalTest.java:234:14:234:18 | dir(...) : File | semmle.label | dir(...) : File | -| PartialPathTraversalTest.java:234:14:234:37 | getCanonicalPath(...) | semmle.label | getCanonicalPath(...) | | PartialPathTraversalTest.java:260:45:260:117 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader | | PartialPathTraversalTest.java:260:64:260:116 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader | | PartialPathTraversalTest.java:260:86:260:106 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | diff --git a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java index 89fb6aaf469a..42e70b2c53d3 100644 --- a/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java +++ b/java/ql/test/query-tests/security/CWE-023/semmle/tests/PartialPathTraversalTest.java @@ -231,7 +231,7 @@ void foo24(File parent) throws IOException { void foo25(File parent) throws IOException { String path = parent.getCanonicalPath(); path += File.separator; - if (!dir().getCanonicalPath().startsWith(path)) { // $ SPURIOUS: Alert[java/partial-path-traversal-from-remote] Alert[java/partial-path-traversal] + if (!dir().getCanonicalPath().startsWith(path)) { throw new IOException("Invalid directory: " + dir().getCanonicalPath()); } } From c6f641eac4ef37ba8a6edcdc11c62ae57dd4f12a Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 18 Apr 2026 20:16:49 +0100 Subject: [PATCH 5/5] Add change note Co-authored-by: Copilot --- .../lib/change-notes/2026-04-18-partial-path-traversal-fix.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 java/ql/lib/change-notes/2026-04-18-partial-path-traversal-fix.md diff --git a/java/ql/lib/change-notes/2026-04-18-partial-path-traversal-fix.md b/java/ql/lib/change-notes/2026-04-18-partial-path-traversal-fix.md new file mode 100644 index 000000000000..8c15a346552e --- /dev/null +++ b/java/ql/lib/change-notes/2026-04-18-partial-path-traversal-fix.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `java/partial-path-traversal` and `java/partial-path-traversal-from-remote` queries now correctly recognize file separator appends using `+=`.