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 20e04147c66b..704991b5a0ba 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 cfa3b44a7044..2952d29c68da 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 000000000000..109789d890e5
--- /dev/null
+++ b/compat/maven-model-builder/src/test/resources/poms/validation/coordinate-ids-path-traversal-dot-pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+ ..
+ .
+ 1.0
+ jar
+
+
+ other.group
+ lib
+ .
+ jar
+
+
+
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 000000000000..e42e51f2b4f8
--- /dev/null
+++ b/compat/maven-model-builder/src/test/resources/poms/validation/coordinate-ids-path-traversal-pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+ test.group
+ ..
+ 1.0
+ jar
+
+
+ other.group
+ lib
+ ..
+ jar
+
+
+
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 e503c1f200dc..e213c852eb58 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 c65391872cf8..1f0329795d4c 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 000000000000..109789d890e5
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/validation/coordinate-ids-path-traversal-dot-pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+ ..
+ .
+ 1.0
+ jar
+
+
+ other.group
+ lib
+ .
+ jar
+
+
+
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 000000000000..e42e51f2b4f8
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/validation/coordinate-ids-path-traversal-pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+ test.group
+ ..
+ 1.0
+ jar
+
+
+ other.group
+ lib
+ ..
+ jar
+
+
+