From 962bf1ec45a304d57e3da3bec45a05b90b657005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Sat, 26 Sep 2026 11:42:12 +0300 Subject: [PATCH] Fix remote resource path traversal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Efe Gökdemir --- .../AbstractProcessRemoteResourcesMojo.java | 16 +++++++++++++- .../remote/RemoteResourcesMojoTest.java | 22 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java b/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java index b5974fc..9e5a59b 100644 --- a/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java +++ b/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java @@ -934,7 +934,7 @@ protected void processResourceBundles(ClassLoader classLoader, VelocityContext c // Don't overwrite resource that are already being provided. - File outputFile = new File(outputDirectory, projectResource); + File outputFile = resolveOutputFile(outputDirectory, projectResource); FileUtils.mkdir(outputFile.getParentFile().getAbsolutePath()); @@ -992,6 +992,20 @@ protected void processResourceBundles(ClassLoader classLoader, VelocityContext c } } + static File resolveOutputFile(File outputDirectory, String resourceName) + throws IOException, MojoExecutionException { + if (new File(resourceName).isAbsolute()) { + throw new MojoExecutionException("Remote resource is outside the output directory: " + resourceName); + } + File canonicalOutputDirectory = outputDirectory.getCanonicalFile(); + File outputFile = new File(outputDirectory, resourceName).getCanonicalFile(); + if (outputFile.equals(canonicalOutputDirectory) + || !outputFile.toPath().startsWith(canonicalOutputDirectory.toPath())) { + throw new MojoExecutionException("Remote resource is outside the output directory: " + resourceName); + } + return outputFile; + } + private void verifyRequiredProperties(RemoteResourcesBundle bundle, URL url) throws MojoExecutionException { if (bundle.getRequiredProjectProperties() == null || bundle.getRequiredProjectProperties().isEmpty()) { diff --git a/src/test/java/org/apache/maven/plugin/resources/remote/RemoteResourcesMojoTest.java b/src/test/java/org/apache/maven/plugin/resources/remote/RemoteResourcesMojoTest.java index 78d895e..32d2cc1 100644 --- a/src/test/java/org/apache/maven/plugin/resources/remote/RemoteResourcesMojoTest.java +++ b/src/test/java/org/apache/maven/plugin/resources/remote/RemoteResourcesMojoTest.java @@ -89,6 +89,28 @@ public void testNoBundles() throws Exception { mojo.execute(); } + public void testRemoteResourceCannotEscapeOutputDirectory() throws Exception { + File outputDirectory = new File("target/remote-resource-output"); + + try { + AbstractProcessRemoteResourcesMojo.resolveOutputFile(outputDirectory, "../outside.txt"); + fail("expected a resource outside the output directory to be rejected"); + } catch (MojoExecutionException e) { + assertTrue(e.getMessage().contains("outside the output directory")); + } + + try { + AbstractProcessRemoteResourcesMojo.resolveOutputFile( + outputDirectory, new File(outputDirectory.getParentFile(), "absolute.txt").getAbsolutePath()); + fail("expected an absolute resource path outside the output directory to be rejected"); + } catch (MojoExecutionException e) { + assertTrue(e.getMessage().contains("outside the output directory")); + } + + File outputFile = AbstractProcessRemoteResourcesMojo.resolveOutputFile(outputDirectory, "nested/resource.txt"); + assertEquals(new File(outputDirectory, "nested/resource.txt").getCanonicalFile(), outputFile); + } + public void testConfigureLocatorRequiresProjectFile() throws Exception { final MavenProjectResourcesStub project = createTestProject("default-null-pom"); final ProcessRemoteResourcesMojo mojo = lookupProcessMojoWithDefaultSettings(project);