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/08/31 12:00:59 UTC
(maven-deploy-plugin) branch security/audit-low-robustness created (now 01b6a86)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a change to branch security/audit-low-robustness
in repository https://gitbox.apache.org/repos/asf/maven-deploy-plugin.git
at 01b6a86 fix: f012 (LOW) — Two targeted rejections rather than a pattern-order swap: reordering the
This branch includes the following new commits:
new f4f47f0 fix: f007 (LOW) — Shared fail-closed parser AbstractDeployMojo.parseSkipMode(value, parame
new 0885374 fix: f008 (LOW) — The dm selection now distinguishes 'no snapshotRepository declared' (doc
new e06b82d fix: f009 (LOW) — Warn-based client-side guard, honest about the static limit the audit re
new 749f9dd fix: f010 (LOW) — New AbstractDeployMojo.redactUrlUserInfo() masks scheme://userinfo@ as s
new 5b6da35 fix: f011 (LOW) — Opt-in containment knob, per the triage recommendation (pipeline variabl
new 01b6a86 fix: f012 (LOW) — Two targeted rejections rather than a pattern-order swap: reordering the
The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(maven-deploy-plugin) 03/06: fix: f009 (LOW) — Warn-based client-side guard, honest about the static limit the audit re
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch security/audit-low-robustness
in repository https://gitbox.apache.org/repos/asf/maven-deploy-plugin.git
commit e06b82d0f77460305d8976e42306469f6c91b9fd
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Mon Aug 31 13:57:51 2026 +0200
fix: f009 (LOW) — Warn-based client-side guard, honest about the static limit the audit re
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../apache/maven/plugins/deploy/DeployMojo.java | 23 ++++++++++++++++++++++
.../maven/plugins/deploy/DeployMojoTest.java | 20 +++++++++++++++++++
2 files changed, 43 insertions(+)
diff --git a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
index b87a1bf..66784d5 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
@@ -112,6 +112,10 @@ public class DeployMojo extends AbstractDeployMojo {
* When the id matches a <code>settings.xml</code> server entry and the URL differs from every URL this build
* associates with that id, the deployment is refused unless
* <code>-Dmaven.deploy.allowCredentialReuse=true</code> is given on the command line.
+ * <p>
+ * <b>Policy note:</b> unlike a repository declared in <code>distributionManagement</code>, an alternative
+ * repository is built from just <code>id::url</code> and therefore carries API-default release/snapshot
+ * policies and serves both artifact kinds; release/snapshot acceptance is enforced by the server only.
*/
@Parameter(property = "altDeploymentRepository")
private String altDeploymentRepository;
@@ -494,6 +498,7 @@ public class DeployMojo extends AbstractDeployMojo {
validateTransportSecurity(
dm.getSnapshotRepository().getId(),
dm.getSnapshotRepository().getUrl());
+ warnIfPolicyMismatch(dm.getSnapshotRepository(), isSnapshot);
repo = session.createRemoteRepository(dm.getSnapshotRepository());
} else if (dm.getRepository() != null
&& isNotEmpty(dm.getRepository().getId())
@@ -510,6 +515,7 @@ public class DeployMojo extends AbstractDeployMojo {
}
validateTransportSecurity(
dm.getRepository().getId(), dm.getRepository().getUrl());
+ warnIfPolicyMismatch(dm.getRepository(), isSnapshot);
repo = session.createRemoteRepository(dm.getRepository());
}
}
@@ -587,6 +593,23 @@ public class DeployMojo extends AbstractDeployMojo {
return urls;
}
+ /**
+ * Client-side release/snapshot policy sanity check: warns when the artifact kind being deployed
+ * is explicitly disabled on the selected repository's declared policy. Enforcement stays
+ * server-side (the resolver does not consult target policies when deploying); this only makes
+ * the mismatch visible before the upload starts.
+ */
+ private void warnIfPolicyMismatch(org.apache.maven.api.model.DeploymentRepository repository, boolean isSnapshot) {
+ org.apache.maven.api.model.RepositoryPolicy policy =
+ isSnapshot ? repository.getSnapshots() : repository.getReleases();
+ if (policy != null && !policy.isEnabled()) {
+ getLog().warn("Deployment repository '" + repository.getId() + "' declares <"
+ + (isSnapshot ? "snapshots" : "releases") + "><enabled>false</enabled>, but a "
+ + (isSnapshot ? "snapshot" : "release")
+ + " artifact is being deployed to it; the server is expected to reject this upload");
+ }
+ }
+
private boolean isValidPath(Artifact a) {
return getArtifactManager().getPath(a).filter(Files::isRegularFile).isPresent();
}
diff --git a/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java b/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
index cda3394..3b2b6ec 100644
--- a/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
@@ -530,6 +530,26 @@ class DeployMojoTest {
assertEquals("releases", repository.getId());
}
+ @Test
+ @InjectMojo(goal = "deploy")
+ void policyMismatchWarnsButDoesNotFail(DeployMojo mojo) throws Exception {
+ ProjectStub project = (ProjectStub) getVariableValueFromObject(mojo, "project");
+ project.setModel(project.getModel()
+ .withDistributionManagement(org.apache.maven.api.model.DistributionManagement.newBuilder()
+ .repository(org.apache.maven.api.model.DeploymentRepository.newBuilder()
+ .id("releases")
+ .url("https://releases.example/repo")
+ .releases(org.apache.maven.api.model.RepositoryPolicy.newBuilder()
+ .enabled("false")
+ .build())
+ .build())
+ .build()));
+
+ // enforcement stays server-side: the mismatch is warned about, not refused
+ RemoteRepository repository = mojo.getDeploymentRepository(false);
+ assertEquals("releases", repository.getId());
+ }
+
private ArtifactDeployerRequest execute(DeployMojo mojo) {
ArgumentCaptor<ArtifactDeployerRequest> requestCaptor = ArgumentCaptor.forClass(ArtifactDeployerRequest.class);
doNothing().when(artifactDeployer).deploy(requestCaptor.capture());
(maven-deploy-plugin) 01/06: fix: f007 (LOW) — Shared fail-closed parser AbstractDeployMojo.parseSkipMode(value, parame
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch security/audit-low-robustness
in repository https://gitbox.apache.org/repos/asf/maven-deploy-plugin.git
commit f4f47f04d855f025432d15d41236d7c22f844304
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Mon Aug 31 13:57:51 2026 +0200
fix: f007 (LOW) — Shared fail-closed parser AbstractDeployMojo.parseSkipMode(value, parame
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../maven/plugins/deploy/AbstractDeployMojo.java | 34 +++++++++++++++++++
.../maven/plugins/deploy/DeployFileMojo.java | 20 ++++++++---
.../apache/maven/plugins/deploy/DeployMojo.java | 10 +++---
.../maven/plugins/deploy/DeployMojoTest.java | 39 ++++++++++++++++++++++
4 files changed, 95 insertions(+), 8 deletions(-)
diff --git a/src/main/java/org/apache/maven/plugins/deploy/AbstractDeployMojo.java b/src/main/java/org/apache/maven/plugins/deploy/AbstractDeployMojo.java
index e9039cb..93716e8 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/AbstractDeployMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/AbstractDeployMojo.java
@@ -107,6 +107,40 @@ public abstract class AbstractDeployMojo implements Mojo {
}
}
+ /**
+ * Recognized values of the {@code skip} tri-state shared by both mojos.
+ */
+ enum SkipMode {
+ NONE,
+ ALL,
+ RELEASES,
+ SNAPSHOTS
+ }
+
+ /**
+ * Parses the {@code skip} tri-state <em>fail-closed</em>: {@code skip} is a publish-suppression
+ * control, so an unrecognized value (a typo such as {@code ture} or {@code release}) must fail
+ * the build instead of silently publishing. Values are matched case-insensitively.
+ */
+ static SkipMode parseSkipMode(String value, String parameterName) throws MojoException {
+ if (value == null || value.isEmpty() || "false".equalsIgnoreCase(value)) {
+ return SkipMode.NONE;
+ }
+ if ("true".equalsIgnoreCase(value)) {
+ return SkipMode.ALL;
+ }
+ if ("releases".equalsIgnoreCase(value)) {
+ return SkipMode.RELEASES;
+ }
+ if ("snapshots".equalsIgnoreCase(value)) {
+ return SkipMode.SNAPSHOTS;
+ }
+ throw new MojoException("Unrecognized value '" + value + "' for " + parameterName
+ + ": supported values are true, false, releases and snapshots."
+ + " Refusing to deploy on an unrecognized value: a typo in a publish-suppression"
+ + " control must not silently publish.");
+ }
+
/**
* Creates resolver {@link RemoteRepository} equipped with needed whistles and bells.
*/
diff --git a/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java b/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java
index 0ea359b..cfa0850 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java
@@ -183,8 +183,12 @@ public class DeployFileMojo extends AbstractDeployMojo {
* <li><code>true</code>: will skip as usual</li>
* <li><code>releases</code>: will skip if current version of the project is a release</li>
* <li><code>snapshots</code>: will skip if current version of the project is a snapshot</li>
- * <li>any other values will be considered as <code>false</code></li>
+ * <li>values are matched case-insensitively; any other value fails the build (fail-closed:
+ * a typo in a publish-suppression control must not silently publish)</li>
* </ul>
+ * The <code>releases</code>/<code>snapshots</code> variants are evaluated after the artifact
+ * coordinates are known, so a version supplied only via <code>pomFile</code> (or the jar's
+ * embedded POM) is classified correctly.
* @since 3.1.0
*/
@Parameter(property = "maven.deploy.file.skip", defaultValue = "false")
@@ -243,9 +247,8 @@ public class DeployFileMojo extends AbstractDeployMojo {
@SuppressWarnings("checkstyle:MethodLength")
public void execute() throws MojoException {
- if (Boolean.parseBoolean(skip)
- || ("releases".equals(skip) && !session.isVersionSnapshot(version))
- || ("snapshots".equals(skip) && session.isVersionSnapshot(version))) {
+ SkipMode skipMode = parseSkipMode(skip, "maven.deploy.file.skip");
+ if (skipMode == SkipMode.ALL) {
getLog().info("Skipping artifact deployment");
return;
}
@@ -279,6 +282,15 @@ public class DeployFileMojo extends AbstractDeployMojo {
+ "'version' and 'packaging' are required.");
}
+ // the releases/snapshots skip variants classify the version, so they are evaluated only
+ // after the version is known - including a version supplied via pomFile or the jar's
+ // embedded POM (previously they classified a possibly-null version)
+ if ((skipMode == SkipMode.RELEASES && !session.isVersionSnapshot(version))
+ || (skipMode == SkipMode.SNAPSHOTS && session.isVersionSnapshot(version))) {
+ getLog().info("Skipping artifact deployment");
+ return;
+ }
+
if (!isValidId(groupId) || !isValidId(artifactId) || !isValidVersion(version)) {
throw new MojoException("The artifact information is not valid: uses invalid characters.");
}
diff --git a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
index 3e7e977..67e4f2d 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
@@ -147,7 +147,8 @@ public class DeployMojo extends AbstractDeployMojo {
* <li><code>true</code>: will skip as usual</li>
* <li><code>releases</code>: will skip if current version of the project is a release</li>
* <li><code>snapshots</code>: will skip if current version of the project is a snapshot</li>
- * <li>any other values will be considered as <code>false</code></li>
+ * <li>values are matched case-insensitively; any other value fails the build (fail-closed:
+ * a typo in a publish-suppression control must not silently publish)</li>
* </ul>
* @since 2.4
*/
@@ -220,9 +221,10 @@ public class DeployMojo extends AbstractDeployMojo {
return;
}
}
- if (Boolean.parseBoolean(skip)
- || ("releases".equals(skip) && !session.isVersionSnapshot(project.getVersion()))
- || ("snapshots".equals(skip) && session.isVersionSnapshot(project.getVersion()))) {
+ SkipMode skipMode = parseSkipMode(skip, "maven.deploy.skip");
+ if (skipMode == SkipMode.ALL
+ || (skipMode == SkipMode.RELEASES && !session.isVersionSnapshot(project.getVersion()))
+ || (skipMode == SkipMode.SNAPSHOTS && session.isVersionSnapshot(project.getVersion()))) {
getLog().info("Skipping artifact deployment");
synchronized (DEPLOY_AT_END_LOCK) {
putState(State.SKIPPED);
diff --git a/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java b/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
index 8f3aeae..c9c5003 100644
--- a/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
@@ -471,6 +471,45 @@ class DeployMojoTest {
assertEquals(1, captor.getAllValues().size(), "re-entry must not re-deploy the batch");
}
+ @Test
+ @InjectMojo(goal = "deploy")
+ @MojoParameter(name = "deployAtEnd", value = "false")
+ void skipTypoFailsClosed(DeployMojo mojo) throws Exception {
+ setVariableValueToObject(mojo, "skip", "ture");
+
+ MojoException e = assertThrows(MojoException.class, mojo::execute);
+ assertTrue(e.getMessage().contains("Unrecognized value 'ture'"), e.getMessage());
+ }
+
+ @Test
+ @InjectMojo(goal = "deploy")
+ @MojoParameter(name = "deployAtEnd", value = "false")
+ void skipValuesAreCaseInsensitive(DeployMojo mojo) throws Exception {
+ Project project = (Project) getVariableValueFromObject(mojo, "project");
+ artifactManager.setPath(
+ project.getMainArtifact().get(),
+ Paths.get(getBasedir(), "target/test-classes/unit/maven-deploy-test-1.0-SNAPSHOT.jar"));
+ // project version is 1.0-SNAPSHOT: skip=SNAPSHOTS (any case) must skip
+ when(session.isVersionSnapshot("1.0-SNAPSHOT")).thenReturn(true);
+ setVariableValueToObject(mojo, "skip", "SNAPSHOTS");
+
+ assertNull(execute(mojo));
+ }
+
+ @Test
+ @InjectMojo(goal = "deploy")
+ @MojoParameter(name = "deployAtEnd", value = "false")
+ void skipReleasesDoesNotSkipSnapshotProject(DeployMojo mojo) throws Exception {
+ Project project = (Project) getVariableValueFromObject(mojo, "project");
+ artifactManager.setPath(
+ project.getMainArtifact().get(),
+ Paths.get(getBasedir(), "target/test-classes/unit/maven-deploy-test-1.0-SNAPSHOT.jar"));
+ when(session.isVersionSnapshot("1.0-SNAPSHOT")).thenReturn(true);
+ setVariableValueToObject(mojo, "skip", "Releases");
+
+ assertNotNull(execute(mojo));
+ }
+
private ArtifactDeployerRequest execute(DeployMojo mojo) {
ArgumentCaptor<ArtifactDeployerRequest> requestCaptor = ArgumentCaptor.forClass(ArtifactDeployerRequest.class);
doNothing().when(artifactDeployer).deploy(requestCaptor.capture());
(maven-deploy-plugin) 02/06: fix: f008 (LOW) — The dm selection now distinguishes 'no snapshotRepository declared' (doc
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch security/audit-low-robustness
in repository https://gitbox.apache.org/repos/asf/maven-deploy-plugin.git
commit 0885374887aaf0125dbc1f8ef1c7b20dd297bd10
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Mon Aug 31 13:57:51 2026 +0200
fix: f008 (LOW) — The dm selection now distinguishes 'no snapshotRepository declared' (doc
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../org/apache/maven/plugins/deploy/DeployMojo.java | 16 +++++++++++++---
.../apache/maven/plugins/deploy/DeployMojoTest.java | 20 ++++++++++++++++++++
2 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
index 67e4f2d..b87a1bf 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
@@ -487,10 +487,10 @@ public class DeployMojo extends AbstractDeployMojo {
if (repo == null) {
DistributionManagement dm = project.getModel().getDistributionManagement();
if (dm != null) {
- if (isSnapshot
- && dm.getSnapshotRepository() != null
+ boolean snapshotRepositoryUsable = dm.getSnapshotRepository() != null
&& isNotEmpty(dm.getSnapshotRepository().getId())
- && isNotEmpty(dm.getSnapshotRepository().getUrl())) {
+ && isNotEmpty(dm.getSnapshotRepository().getUrl());
+ if (isSnapshot && snapshotRepositoryUsable) {
validateTransportSecurity(
dm.getSnapshotRepository().getId(),
dm.getSnapshotRepository().getUrl());
@@ -498,6 +498,16 @@ public class DeployMojo extends AbstractDeployMojo {
} else if (dm.getRepository() != null
&& isNotEmpty(dm.getRepository().getId())
&& isNotEmpty(dm.getRepository().getUrl())) {
+ if (isSnapshot && dm.getSnapshotRepository() != null) {
+ // a declared-but-unusable snapshotRepository is a config error; falling
+ // back silently would route snapshots to a repository with a different
+ // audience, retention policy and credentials
+ getLog().warn("distributionManagement declares a <snapshotRepository> whose id or url is"
+ + " empty; falling back to the release <repository> '"
+ + dm.getRepository().getId() + "' ("
+ + dm.getRepository().getUrl()
+ + ") for this snapshot deployment");
+ }
validateTransportSecurity(
dm.getRepository().getId(), dm.getRepository().getUrl());
repo = session.createRemoteRepository(dm.getRepository());
diff --git a/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java b/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
index c9c5003..cda3394 100644
--- a/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
@@ -510,6 +510,26 @@ class DeployMojoTest {
assertNotNull(execute(mojo));
}
+ @Test
+ @InjectMojo(goal = "deploy")
+ void snapshotDeployFallsBackToReleaseRepositoryWhenSnapshotRepositoryUnusable(DeployMojo mojo) throws Exception {
+ ProjectStub project = (ProjectStub) getVariableValueFromObject(mojo, "project");
+ project.setModel(project.getModel()
+ .withDistributionManagement(org.apache.maven.api.model.DistributionManagement.newBuilder()
+ .snapshotRepository(org.apache.maven.api.model.DeploymentRepository.newBuilder()
+ .id("snapshots")
+ .url("") // declared but unusable, e.g. a property interpolating empty
+ .build())
+ .repository(org.apache.maven.api.model.DeploymentRepository.newBuilder()
+ .id("releases")
+ .url("https://releases.example/repo")
+ .build())
+ .build()));
+
+ RemoteRepository repository = mojo.getDeploymentRepository(true);
+ assertEquals("releases", repository.getId());
+ }
+
private ArtifactDeployerRequest execute(DeployMojo mojo) {
ArgumentCaptor<ArtifactDeployerRequest> requestCaptor = ArgumentCaptor.forClass(ArtifactDeployerRequest.class);
doNothing().when(artifactDeployer).deploy(requestCaptor.capture());
(maven-deploy-plugin) 04/06: fix: f010 (LOW) — New AbstractDeployMojo.redactUrlUserInfo() masks scheme://userinfo@ as s
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch security/audit-low-robustness
in repository https://gitbox.apache.org/repos/asf/maven-deploy-plugin.git
commit 749f9dd8c4535388bfb90ba796ec292f66250582
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Mon Aug 31 13:57:52 2026 +0200
fix: f010 (LOW) — New AbstractDeployMojo.redactUrlUserInfo() masks scheme://userinfo@ as s
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../org/apache/maven/plugins/deploy/AbstractDeployMojo.java | 13 +++++++++++++
.../org/apache/maven/plugins/deploy/DeployFileMojo.java | 4 +++-
.../java/org/apache/maven/plugins/deploy/DeployMojo.java | 5 +++--
.../apache/maven/plugins/deploy/DeployFileMojoUnitTest.java | 13 +++++++++++++
4 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/maven/plugins/deploy/AbstractDeployMojo.java b/src/main/java/org/apache/maven/plugins/deploy/AbstractDeployMojo.java
index 93716e8..6e155db 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/AbstractDeployMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/AbstractDeployMojo.java
@@ -454,6 +454,19 @@ public abstract class AbstractDeployMojo implements Mojo {
return userProperties != null && Boolean.parseBoolean(userProperties.get(ALLOW_CREDENTIAL_REUSE_PROPERTY));
}
+ /**
+ * Masks URL-embedded userinfo ({@code scheme://user:token@host/...}) before a repository
+ * string is logged: build logs are routinely archived and shared, and the deploy log line
+ * must be safe to keep while remaining useful as the audit signal for where artifacts and
+ * credentials were sent.
+ */
+ static String redactUrlUserInfo(String value) {
+ if (value == null) {
+ return null;
+ }
+ return value.replaceAll("://[^/@\\s]+@", "://***@");
+ }
+
static String normalizeRepositoryUrl(String url) {
String normalized = url.trim();
while (normalized.endsWith("/")) {
diff --git a/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java b/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java
index cfa0850..f9aa67a 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java
@@ -438,7 +438,9 @@ public class DeployFileMojo extends AbstractDeployMojo {
.retryFailedDeploymentCount(Math.max(1, Math.min(10, getRetryFailedDeploymentCount())))
.build();
- getLog().info("Deploying artifacts " + deployables + " to repository " + deploymentRepository);
+ getLog().info("Deploying artifacts " + deployables + " to repository "
+ + deploymentRepository.getId() + " ("
+ + redactUrlUserInfo(deploymentRepository.getUrl()) + ")");
ArtifactDeployer artifactDeployer = session.getService(ArtifactDeployer.class);
artifactDeployer.deploy(deployRequest);
} catch (ArtifactDeployerException e) {
diff --git a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
index 66784d5..4938c1b 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
@@ -365,7 +365,8 @@ public class DeployMojo extends AbstractDeployMojo {
private void deploy(ArtifactDeployerRequest request) {
try {
getLog().info("Deploying artifacts " + request.getArtifacts().toString() + " to repository "
- + request.getRepository());
+ + request.getRepository().getId() + " ("
+ + redactUrlUserInfo(request.getRepository().getUrl()) + ")");
getArtifactDeployer().deploy(request);
} catch (MojoException e) {
throw e;
@@ -451,7 +452,7 @@ public class DeployMojo extends AbstractDeployMojo {
}
if (altDeploymentRepo != null) {
- getLog().info("Using alternate deployment repository " + altDeploymentRepo);
+ getLog().info("Using alternate deployment repository " + redactUrlUserInfo(altDeploymentRepo));
Matcher matcher = ALT_LEGACY_REPO_SYNTAX_PATTERN.matcher(altDeploymentRepo);
diff --git a/src/test/java/org/apache/maven/plugins/deploy/DeployFileMojoUnitTest.java b/src/test/java/org/apache/maven/plugins/deploy/DeployFileMojoUnitTest.java
index 21f4175..6fa96a7 100644
--- a/src/test/java/org/apache/maven/plugins/deploy/DeployFileMojoUnitTest.java
+++ b/src/test/java/org/apache/maven/plugins/deploy/DeployFileMojoUnitTest.java
@@ -155,6 +155,19 @@ class DeployFileMojoUnitTest {
assertTrue(AbstractDeployMojo.isValidTypeOrExtension("maven-plugin"));
}
+ @Test
+ void urlUserInfoIsRedactedForLogging() {
+ assertEquals(
+ "https://***@repo.example/releases",
+ AbstractDeployMojo.redactUrlUserInfo("https://user:s3cr3t@repo.example/releases"));
+ assertEquals(
+ "my-repo::https://***@repo.example/releases",
+ AbstractDeployMojo.redactUrlUserInfo("my-repo::https://ci-bot:tok3n@repo.example/releases"));
+ assertEquals(
+ "https://repo.example/releases", AbstractDeployMojo.redactUrlUserInfo("https://repo.example/releases"));
+ assertEquals("file:///tmp/repo", AbstractDeployMojo.redactUrlUserInfo("file:///tmp/repo"));
+ }
+
private void setMojoModel(
MockDeployFileMojo mojo, String group, String artifact, String version, String packaging, Parent parent) {
mojo.model = Model.newBuilder()
(maven-deploy-plugin) 05/06: fix: f011 (LOW) — Opt-in containment knob, per the triage recommendation (pipeline variabl
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch security/audit-low-robustness
in repository https://gitbox.apache.org/repos/asf/maven-deploy-plugin.git
commit 5b6da352a4711986d466d15acccdb7ad10305188
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Mon Aug 31 13:57:52 2026 +0200
fix: f011 (LOW) — Opt-in containment knob, per the triage recommendation (pipeline variabl
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../maven/plugins/deploy/DeployFileMojo.java | 54 ++++++++++++++++++++++
.../plugins/deploy/DeployFileMojoUnitTest.java | 13 ++++++
2 files changed, 67 insertions(+)
diff --git a/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java b/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java
index f9aa67a..aa67ddb 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/DeployFileMojo.java
@@ -194,6 +194,21 @@ public class DeployFileMojo extends AbstractDeployMojo {
@Parameter(property = "maven.deploy.file.skip", defaultValue = "false")
private String skip = Boolean.FALSE.toString();
+ /**
+ * Optional containment directory for all artifact-content paths ({@code file}, {@code files},
+ * {@code sources}, {@code javadoc}, {@code pomFile}): when set, each of those paths must resolve
+ * inside this directory (symlinks are resolved before the comparison) or the build fails.
+ * <p>
+ * Intended for CI/automation that fills deploy-file parameters from pipeline variables: without
+ * containment, any file readable by the build (for example {@code ~/.m2/settings.xml} passed as a
+ * side artifact) can be published to the deployment URL in one invocation. Disabled (no
+ * containment) by default; point it at the project or workspace directory in automated pipelines.
+ *
+ * @since 4.0.0
+ */
+ @Parameter(property = "maven.deploy.file.containedIn")
+ private Path containedIn;
+
void initProperties() throws MojoException {
Path deployedPom;
if (pomFile != null) {
@@ -259,6 +274,13 @@ public class DeployFileMojo extends AbstractDeployMojo {
throw new MojoException(message);
}
+ // containment applies to the operator-supplied paths, before any of them is read;
+ // the temporary POM later extracted from the (already contained) jar is exempt
+ checkContained(file, "file");
+ checkContained(pomFile, "pomFile");
+ checkContained(sources, "sources");
+ checkContained(javadoc, "javadoc");
+
initProperties();
validateCredentialBinding(repositoryId, url.replace(File.separator, "/"));
@@ -392,6 +414,7 @@ public class DeployFileMojo extends AbstractDeployMojo {
file = Paths.get(files.substring(fi, nfi));
}
if (Files.isRegularFile(file)) {
+ checkContained(file, "files");
String extension = getExtension(file);
String type = types.substring(ti, nti).trim();
String classifierEntry = classifiers.substring(ci, nci).trim();
@@ -459,6 +482,37 @@ public class DeployFileMojo extends AbstractDeployMojo {
}
}
+ /**
+ * Enforces the optional {@link #containedIn} containment directory for an artifact-content path.
+ */
+ private void checkContained(Path path, String parameterName) throws MojoException {
+ if (containedIn == null || path == null) {
+ return;
+ }
+ if (!isContainedIn(path, containedIn)) {
+ throw new MojoException("Parameter '" + parameterName + "' resolves to "
+ + path.toAbsolutePath().normalize() + ", which is outside the containment directory "
+ + containedIn.toAbsolutePath().normalize() + " configured with maven.deploy.file.containedIn");
+ }
+ }
+
+ /**
+ * Returns {@code true} when {@code path} resolves inside {@code root}, resolving symlinks where
+ * the paths exist so a link pointing outside the containment directory does not pass.
+ */
+ static boolean isContainedIn(Path path, Path root) {
+ return realOrNormalized(path).startsWith(realOrNormalized(root));
+ }
+
+ private static Path realOrNormalized(Path path) {
+ Path absolute = path.toAbsolutePath().normalize();
+ try {
+ return absolute.toRealPath();
+ } catch (IOException e) {
+ return absolute;
+ }
+ }
+
/**
* Gets the path of the specified artifact within the local repository. Note that the returned path need not exist
* (yet).
diff --git a/src/test/java/org/apache/maven/plugins/deploy/DeployFileMojoUnitTest.java b/src/test/java/org/apache/maven/plugins/deploy/DeployFileMojoUnitTest.java
index 6fa96a7..3c95dd1 100644
--- a/src/test/java/org/apache/maven/plugins/deploy/DeployFileMojoUnitTest.java
+++ b/src/test/java/org/apache/maven/plugins/deploy/DeployFileMojoUnitTest.java
@@ -18,6 +18,8 @@
*/
package org.apache.maven.plugins.deploy;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -168,6 +170,17 @@ class DeployFileMojoUnitTest {
assertEquals("file:///tmp/repo", AbstractDeployMojo.redactUrlUserInfo("file:///tmp/repo"));
}
+ @Test
+ void containmentDirectoryIsEnforced() throws IOException {
+ Path root = Files.createTempDirectory("deploy-file-containment");
+ Path inside = Files.createFile(root.resolve("artifact.jar"));
+
+ assertTrue(DeployFileMojo.isContainedIn(inside, root));
+ assertTrue(DeployFileMojo.isContainedIn(root.resolve("sub/other.jar"), root));
+ assertFalse(DeployFileMojo.isContainedIn(root.resolve("../escaped.jar"), root));
+ assertFalse(DeployFileMojo.isContainedIn(Paths.get("/etc/passwd"), root));
+ }
+
private void setMojoModel(
MockDeployFileMojo mojo, String group, String artifact, String version, String packaging, Parent parent) {
mojo.model = Model.newBuilder()
(maven-deploy-plugin) 06/06: fix: f012 (LOW) — Two targeted rejections rather than a pattern-order swap: reordering the
Posted by gn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch security/audit-low-robustness
in repository https://gitbox.apache.org/repos/asf/maven-deploy-plugin.git
commit 01b6a8655fd7e912b6d45b8cecfbfc60660b747e
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Mon Aug 31 13:57:52 2026 +0200
fix: f012 (LOW) — Two targeted rejections rather than a pattern-order swap: reordering the
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../apache/maven/plugins/deploy/DeployMojo.java | 26 ++++++++++++++++++++++
.../maven/plugins/deploy/DeployMojoTest.java | 19 ++++++++++++++++
2 files changed, 45 insertions(+)
diff --git a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
index 4938c1b..8c442ca 100644
--- a/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
+++ b/src/main/java/org/apache/maven/plugins/deploy/DeployMojo.java
@@ -462,8 +462,19 @@ public class DeployMojo extends AbstractDeployMojo {
String url = matcher.group(3).trim();
if ("default".equals(layout)) {
+ if (url.contains("::")) {
+ // "a::default::b::c" would otherwise be accepted with url "b::c": refuse
+ // instead of guessing which of the two possible parses was intended
+ throw new MojoException(
+ altDeploymentRepo,
+ "Ambiguous syntax for alternative repository.",
+ "Ambiguous alternative repository: the value parses as legacy \"" + id + "::" + layout
+ + "::" + url + "\" but its URL part still contains \"::\"."
+ + " Use \"id::url\" with a URL that does not contain \"::\".");
+ }
getLog().warn("Using legacy syntax for alternative repository. " + "Use \"" + id + "::" + url
+ "\" instead.");
+ requireNonEmptyIdAndUrl(altDeploymentRepo, id, url);
repo = createAltDeploymentRepository(id, url);
} else {
throw new MojoException(
@@ -484,6 +495,7 @@ public class DeployMojo extends AbstractDeployMojo {
String id = matcher.group(1).trim();
String url = matcher.group(2).trim();
+ requireNonEmptyIdAndUrl(altDeploymentRepo, id, url);
repo = createAltDeploymentRepository(id, url);
}
}
@@ -532,6 +544,20 @@ public class DeployMojo extends AbstractDeployMojo {
return repo;
}
+ /**
+ * An alternative repository whose id or url trims to empty cannot bind credentials or be
+ * deployed to meaningfully; refuse instead of continuing with a blank id (whose credential
+ * lookup would fail server-side) or a blank URL.
+ */
+ private static void requireNonEmptyIdAndUrl(String altDeploymentRepo, String id, String url) {
+ if (id.isEmpty() || url.isEmpty()) {
+ throw new MojoException(
+ altDeploymentRepo,
+ "Invalid syntax for repository.",
+ "Invalid syntax for alternative repository: id and url must be non-empty. Use \"id::url\".");
+ }
+ }
+
/**
* Creates the repository for an alternative deployment target: warns when it overrides the
* project's declared {@code distributionManagement} (naming the server id whose settings.xml
diff --git a/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java b/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
index 3b2b6ec..58175e6 100644
--- a/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/deploy/DeployMojoTest.java
@@ -550,6 +550,25 @@ class DeployMojoTest {
assertEquals("releases", repository.getId());
}
+ @Test
+ @InjectMojo(goal = "deploy")
+ void emptyIdAltDeploymentRepositoryRefused(DeployMojo mojo) throws Exception {
+ setVariableValueToObject(mojo, "altDeploymentRepository", " ::https://repo.example/releases");
+
+ MojoException e = assertThrows(MojoException.class, () -> mojo.getDeploymentRepository(false));
+ assertEquals("Invalid syntax for repository.", e.getMessage());
+ assertTrue(e.getLongMessage().contains("non-empty"), e.getLongMessage());
+ }
+
+ @Test
+ @InjectMojo(goal = "deploy")
+ void ambiguousLegacyAltDeploymentRepositoryRefused(DeployMojo mojo) throws Exception {
+ setVariableValueToObject(mojo, "altDeploymentRepository", "a::default::b::c");
+
+ MojoException e = assertThrows(MojoException.class, () -> mojo.getDeploymentRepository(false));
+ assertEquals("Ambiguous syntax for alternative repository.", e.getMessage());
+ }
+
private ArtifactDeployerRequest execute(DeployMojo mojo) {
ArgumentCaptor<ArtifactDeployerRequest> requestCaptor = ArgumentCaptor.forClass(ArtifactDeployerRequest.class);
doNothing().when(artifactDeployer).deploy(requestCaptor.capture());