You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/07/28 09:32:14 UTC

(maven) 01/01: Fix #12427: Reject path-traversal segments in coordinate ids and versions

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

gnodet pushed a commit to branch backport/12410-path-traversal
in repository https://gitbox.apache.org/repos/asf/maven.git

commit e0ef1e7b8af5ff5f28c581ca0a0ab35150fd038a
Author: jmestwa-coder <jm...@gmail.com>
AuthorDate: Tue Jul 28 15:01:04 2026 +0530

    Fix #12427: Reject path-traversal segments in coordinate ids and versions
    
    Coordinate ids (groupId, artifactId) and versions equal to '.' or '..'
    pass character validation but become path-traversal segments in the
    local repository layout. Both the maven-impl and compat
    maven-model-builder validators now reject these values in id checks,
    wildcard-id checks, and version checks. Tests added for both modules.
    
    Closes #12410
---
 .../model/validation/DefaultModelValidator.java    | 28 +++++++++++++++
 .../validation/DefaultModelValidatorTest.java      | 37 ++++++++++++++++++++
 .../coordinate-ids-path-traversal-dot-pom.xml      | 34 ++++++++++++++++++
 .../coordinate-ids-path-traversal-pom.xml          | 34 ++++++++++++++++++
 .../maven/impl/model/DefaultModelValidator.java    | 28 +++++++++++++++
 .../impl/model/DefaultModelValidatorTest.java      | 40 ++++++++++++++++++++++
 .../coordinate-ids-path-traversal-dot-pom.xml      | 34 ++++++++++++++++++
 .../coordinate-ids-path-traversal-pom.xml          | 34 ++++++++++++++++++
 8 files changed, 269 insertions(+)

diff --git a/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java b/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
index 20e04147c6..704991b5a0 100644
--- a/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
+++ b/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
@@ -1151,6 +1151,9 @@ private boolean validateId(
     }
 
     private boolean isValidId(String id) {
+        if (isPathTraversalSegment(id)) {
+            return false;
+        }
         for (int i = 0; i < id.length(); i++) {
             char c = id.charAt(i);
             if (!isValidIdCharacter(c)) {
@@ -1160,6 +1163,16 @@ private boolean isValidId(String id) {
         return true;
     }
 
+    /**
+     * {@code .} and {@code ..} pass the allowed-character checks, but the default local repository layout uses
+     * ids and versions verbatim as directory names, so these values map onto the {@code .} and {@code ..}
+     * filesystem path segments and escape the coordinate's directory. They are rejected because of that mapping,
+     * not because the names themselves are otherwise invalid.
+     */
+    private static boolean isPathTraversalSegment(String id) {
+        return ".".equals(id) || "..".equals(id);
+    }
+
     private boolean isValidIdCharacter(char c) {
         return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.';
     }
@@ -1193,6 +1206,9 @@ private boolean validateIdWithWildcards(
     }
 
     private boolean isValidIdWithWildCards(String id) {
+        if (isPathTraversalSegment(id)) {
+            return false;
+        }
         for (int i = 0; i < id.length(); i++) {
             char c = id.charAt(i);
             if (!isValidIdWithWildCardCharacter(c)) {
@@ -1644,6 +1660,18 @@ private boolean validateVersion(
             return false;
         }
 
+        if (isPathTraversalSegment(string)) {
+            addViolation(
+                    problems,
+                    severity,
+                    version,
+                    prefix + fieldName,
+                    sourceHint,
+                    "must be a valid version but is '" + string + "'.",
+                    tracker);
+            return false;
+        }
+
         return validateBannedCharacters(
                 prefix, fieldName, problems, severity, version, string, sourceHint, tracker, ILLEGAL_VERSION_CHARS);
     }
diff --git a/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java b/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
index cfa3b44a70..2952d29c68 100644
--- a/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
+++ b/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
@@ -180,6 +180,43 @@ void testInvalidCoordinateIds() throws Exception {
                 result.getErrors().get(1));
     }
 
+    @Test
+    void testCoordinateIdsWithPathTraversal() throws Exception {
+        SimpleProblemCollector result = validate("coordinate-ids-path-traversal-pom.xml");
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m -> m.contains("'artifactId'") && m.contains("does not match a valid id pattern")),
+                "artifactId '..' must be rejected: " + result.getErrors());
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m ->
+                                m.contains("dependencies.dependency.version") && m.contains("must be a valid version")),
+                "dependency version '..' must be rejected: " + result.getErrors());
+    }
+
+    @Test
+    void testCoordinateIdsWithSingleDotPathTraversal() throws Exception {
+        SimpleProblemCollector result = validate("coordinate-ids-path-traversal-dot-pom.xml");
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m -> m.contains("'groupId'") && m.contains("does not match a valid id pattern")),
+                "groupId '..' must be rejected: " + result.getErrors());
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m -> m.contains("'artifactId'") && m.contains("does not match a valid id pattern")),
+                "artifactId '.' must be rejected: " + result.getErrors());
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m ->
+                                m.contains("dependencies.dependency.version") && m.contains("must be a valid version")),
+                "dependency version '.' must be rejected: " + result.getErrors());
+    }
+
     @Test
     void testMissingType() throws Exception {
         SimpleProblemCollector result = validate("missing-type-pom.xml");
diff --git a/compat/maven-model-builder/src/test/resources/poms/validation/coordinate-ids-path-traversal-dot-pom.xml b/compat/maven-model-builder/src/test/resources/poms/validation/coordinate-ids-path-traversal-dot-pom.xml
new file mode 100644
index 0000000000..109789d890
--- /dev/null
+++ b/compat/maven-model-builder/src/test/resources/poms/validation/coordinate-ids-path-traversal-dot-pom.xml
@@ -0,0 +1,34 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>..</groupId>
+  <artifactId>.</artifactId>
+  <version>1.0</version>
+  <packaging>jar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>other.group</groupId>
+      <artifactId>lib</artifactId>
+      <version>.</version>
+      <type>jar</type>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/compat/maven-model-builder/src/test/resources/poms/validation/coordinate-ids-path-traversal-pom.xml b/compat/maven-model-builder/src/test/resources/poms/validation/coordinate-ids-path-traversal-pom.xml
new file mode 100644
index 0000000000..e42e51f2b4
--- /dev/null
+++ b/compat/maven-model-builder/src/test/resources/poms/validation/coordinate-ids-path-traversal-pom.xml
@@ -0,0 +1,34 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>test.group</groupId>
+  <artifactId>..</artifactId>
+  <version>1.0</version>
+  <packaging>jar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>other.group</groupId>
+      <artifactId>lib</artifactId>
+      <version>..</version>
+      <type>jar</type>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java
index e503c1f200..e213c852eb 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java
@@ -1687,6 +1687,9 @@ private boolean validateCoordinatesId(
     }
 
     private boolean isValidCoordinatesId(String id) {
+        if (isPathTraversalSegment(id)) {
+            return false;
+        }
         for (int i = 0; i < id.length(); i++) {
             char c = id.charAt(i);
             if (!isValidCoordinatesIdCharacter(c)) {
@@ -1696,6 +1699,16 @@ private boolean isValidCoordinatesId(String id) {
         return true;
     }
 
+    /**
+     * {@code .} and {@code ..} pass the allowed-character checks, but the default local repository layout uses
+     * coordinate ids and versions verbatim as directory names, so these values map onto the {@code .} and
+     * {@code ..} filesystem path segments and escape the coordinate's directory. They are rejected because of
+     * that mapping, not because the names themselves are otherwise invalid.
+     */
+    private static boolean isPathTraversalSegment(String id) {
+        return ".".equals(id) || "..".equals(id);
+    }
+
     private boolean isValidCoordinatesIdCharacter(char c) {
         return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.';
     }
@@ -1771,6 +1784,9 @@ private boolean validateCoordinatesIdWithWildcards(
     }
 
     private boolean isValidCoordinatesIdWithWildCards(String id) {
+        if (isPathTraversalSegment(id)) {
+            return false;
+        }
         for (int i = 0; i < id.length(); i++) {
             char c = id.charAt(i);
             if (!isValidCoordinatesIdWithWildCardCharacter(c)) {
@@ -2234,6 +2250,18 @@ private boolean validateVersion(
             return false;
         }
 
+        if (isPathTraversalSegment(string)) {
+            addViolation(
+                    problems,
+                    severity,
+                    version,
+                    prefix + fieldName,
+                    sourceHint,
+                    "must be a valid version but is '" + string + "'.",
+                    tracker);
+            return false;
+        }
+
         return validateBannedCharacters(
                 prefix, fieldName, problems, severity, version, string, sourceHint, tracker, ILLEGAL_VERSION_CHARS);
     }
diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java
index c65391872c..1f0329795d 100644
--- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java
+++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java
@@ -224,6 +224,46 @@ void testInvalidCoordinateIds() throws Exception {
                 result.getErrors().get(1));
     }
 
+    @Test
+    void testCoordinateIdsWithPathTraversal() throws Exception {
+        SimpleProblemCollector result = validate("coordinate-ids-path-traversal-pom.xml");
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m -> m.contains("'artifactId'")
+                                && m.contains("does not match a valid coordinate id pattern")),
+                "artifactId '..' must be rejected: " + result.getErrors());
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m ->
+                                m.contains("dependencies.dependency.version") && m.contains("must be a valid version")),
+                "dependency version '..' must be rejected: " + result.getErrors());
+    }
+
+    @Test
+    void testCoordinateIdsWithSingleDotPathTraversal() throws Exception {
+        SimpleProblemCollector result = validate("coordinate-ids-path-traversal-dot-pom.xml");
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m ->
+                                m.contains("'groupId'") && m.contains("does not match a valid coordinate id pattern")),
+                "groupId '..' must be rejected: " + result.getErrors());
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m -> m.contains("'artifactId'")
+                                && m.contains("does not match a valid coordinate id pattern")),
+                "artifactId '.' must be rejected: " + result.getErrors());
+
+        assertTrue(
+                result.getErrors().stream()
+                        .anyMatch(m ->
+                                m.contains("dependencies.dependency.version") && m.contains("must be a valid version")),
+                "dependency version '.' must be rejected: " + result.getErrors());
+    }
+
     @Test
     void testMissingType() throws Exception {
         SimpleProblemCollector result = validate("missing-type-pom.xml");
diff --git a/impl/maven-impl/src/test/resources/poms/validation/coordinate-ids-path-traversal-dot-pom.xml b/impl/maven-impl/src/test/resources/poms/validation/coordinate-ids-path-traversal-dot-pom.xml
new file mode 100644
index 0000000000..109789d890
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/validation/coordinate-ids-path-traversal-dot-pom.xml
@@ -0,0 +1,34 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>..</groupId>
+  <artifactId>.</artifactId>
+  <version>1.0</version>
+  <packaging>jar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>other.group</groupId>
+      <artifactId>lib</artifactId>
+      <version>.</version>
+      <type>jar</type>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/impl/maven-impl/src/test/resources/poms/validation/coordinate-ids-path-traversal-pom.xml b/impl/maven-impl/src/test/resources/poms/validation/coordinate-ids-path-traversal-pom.xml
new file mode 100644
index 0000000000..e42e51f2b4
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/validation/coordinate-ids-path-traversal-pom.xml
@@ -0,0 +1,34 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>test.group</groupId>
+  <artifactId>..</artifactId>
+  <version>1.0</version>
+  <packaging>jar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>other.group</groupId>
+      <artifactId>lib</artifactId>
+      <version>..</version>
+      <type>jar</type>
+    </dependency>
+  </dependencies>
+</project>