Skip to content

Output, transport and consumer POM robustness fixes - #12946

Open
slachiewicz wants to merge 8 commits into
apache:maven-4.0.xfrom
slachiewicz:pr/cli-and-transport-4.0.x
Open

Output, transport and consumer POM robustness fixes#12946
slachiewicz wants to merge 8 commits into
apache:maven-4.0.xfrom
slachiewicz:pr/cli-and-transport-4.0.x

Conversation

@slachiewicz

@slachiewicz slachiewicz commented Aug 30, 2026

Copy link
Copy Markdown
Member

Robustness fixes across the CLI, transport and consumer POM.

  • Transfer listener output. Control characters in transfer messages are escaped so they render literally rather than being interpreted by the terminal. Tab and newline are preserved.
  • Transport base URI. Relative locations resolve against a slash-terminated base, so a relative reference cannot resolve outside it.
  • Mirror matching. isExternalRepo/isExternalHttpRepo parsed with java.net.URL, which throws on dav:, dav:http: and dav+http: — leaving those branches unreachable — and returned false on any parse failure, so an unparseable URL matched no external:* mirror. Parsing is now textual and an unparseable URL is treated as external.
  • Settings decryption messages. Decryption failure messages identify the server or profile property concerned rather than including the encrypted value.
  • Consumer POM repositories. The consumer POM now publishes only repositories the project's own POM declares; those present only via a parent POM or an active settings.xml profile are dropped, with a warning naming each. maven.consumer.pom.sanitizeRepositories=false restores the previous behaviour. This changes a published artifact format — see the note below.
  • mvnup resolution. mvnup reused hardcoded repositories and forced user.home, so a real settings.xml was never read. It now uses the settings the CLI already loaded and the configured local repository. Where the standalone resolver cannot honour the configuration — offline mode, a redirecting mirror, an active proxy — it is given no remote repositories and the affected strategies skip their remote work with a warning.

Release note for the consumer POM change: a downstream consumer relying on inheriting a repository declaration that existed only in a parent POM or the publisher's settings will need to declare that repository itself.

Each change is a separate commit.


Draft while related code paths are reviewed — the same guard may be needed in sibling implementations of these interfaces, and I would rather establish that before asking for review time.

@slachiewicz slachiewicz added this to the 4.0.0-rc-7 milestone Aug 30, 2026
@slachiewicz
slachiewicz marked this pull request as draft August 30, 2026 19:05
@slachiewicz
slachiewicz force-pushed the pr/cli-and-transport-4.0.x branch from 5279b64 to c43dbd8 Compare August 30, 2026 21:23
@slachiewicz
slachiewicz marked this pull request as ready for review August 30, 2026 22:21

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well-structured multi-topic robustness PR with genuine security and correctness improvements. All changes are well-tested with both unit and integration tests. A few minor observations:

Minor observations:

  1. Style (MavenITgh10210SettingsXmlDecryptTest.java:77): assertTrue(!log.contains(...))assertFalse(log.contains(...)) for readability and better failure messages.

  2. Logging level (DefaultConsumerPomBuilder.java:602): LOGGER.warn() for every retained repository will fire in every build that declares non-central repositories, even when the user is doing everything correctly. Consider using INFO or DEBUG for retained repositories and reserving WARN for dropped repositories.

  3. Duplication (DefaultMirrorSelector.java / MavenRepositorySystem.java): The regex pattern URL_PROTOCOL_AND_HOST and the isExternalRepo/isExternalHttpRepo/isLocal methods are duplicated verbatim between the compat and core modules. A shared utility could reduce maintenance burden, though the duplication is understandable given the compat module's deprecated status.

  4. Duplication (AbstractMavenTransferListener): The sanitize() method is duplicated between the impl and compat versions of this class. Consider extracting to a shared utility.

Technical notes (for other reviewers):

  • The fail-closed change (return true on URL parse failure) in isExternalRepo/isExternalHttpRepo is the right security posture — unparseable URLs will now be subject to external:* and external:http:* mirror rules (including the default HTTP blocker).
  • The transport URI slash normalization fixes a subtle bug where URI.resolve("foo") against http://example.com/repo resolves to http://example.com/foo instead of http://example.com/repo/foo, and prevents prefix-match false positives (e.g. repo-other matching repo).
  • The DecryptingSettingsTransformer refactoring with withReference properly tracks context through nested calls and restores state in finally blocks. The reference equality check newVal != entry.getValue() is intentional and correct.
  • The consumer POM repository sanitization correctly collects declared repository IDs from both model-level and profile-level, with a proper opt-out via maven.consumer.pom.sanitizeRepositories=false.
  • The mvnup remote-resolution gate correctly identifies offline mode, redirecting mirrors, and active proxies as scenarios where standalone resolution cannot honor operator settings.
  • The possessive quantifier {2,}+ in the URL regex prevents ReDoS.

🤖 This review was generated by ForgeBot.

Repository-supplied strings (checksum mismatch messages, resource
names, repository ids/urls) were written straight to the console or
logger without any filtering of control characters. Add a small
sanitize() helper to AbstractMavenTransferListener (impl/maven-cli
and the deprecated compat/maven-embedder copy) that replaces C0
controls other than tab/newline, DEL, and C1 controls with visible
\uXXXX escapes, and apply it everywhere these listeners print
remote-derived text, so terminal output always reflects what was
actually printed.
Normalize the repository base URI to end with a slash before resolving a
relative get/put location, and centralize the containment check in a
shared helper. This keeps URI.resolve() on a whole path segment boundary
and makes get/put behave consistently regardless of whether the
configured repository URL carries a trailing slash.
isExternalRepo/isExternalHttpRepo parsed the repository URL with
java.net.URL, which has no stream handler for the dav/dav:http/dav+http
schemes wagon-webdav speaks and throws for any other unparseable URL;
both methods caught the exception and returned false, so such a
repository was classified as neither external nor external-http and
silently skipped the external:*/external:http:* mirror rules,
including the shipped external:http:* blocker. Parse the scheme and
host textually instead, the way the resolver's own mirror matcher
does, so these URLs are classified and matched like any other.
@slachiewicz
slachiewicz force-pushed the pr/cli-and-transport-4.0.x branch from c43dbd8 to 37f8cb4 Compare August 31, 2026 14:10
The consumer POM's <repositories> section previously published every
effective-model repository except 'central', including repositories that
only reached the effective model through parent-POM inheritance or an
active settings.xml profile. Restrict publication to repository ids
declared in the project's own raw POM (model or profiles); everything
else is dropped, with a WARN naming each dropped and each retained
repository so operators can see what a deploy will publish.

The new user property maven.consumer.pom.sanitizeRepositories (default
true) restores the previous behavior when set to false.
The existing regression tests for consumer-POM repository scoping only
checked the intermediate Model object returned by transformNonPom().
Add a test that builds a bom-packaged project through the real public
PomBuilder entry point and serializes the result with the same
MavenStaxWriter the production ConsumerPomArtifactTransformer uses, so
the assertion is against the actual generated POM XML.
mvnup's compatibility and plugin-upgrade strategies built effective
models with a standalone session that always remapped user.home to a
test directory and hardcoded Maven Central plus an Apache snapshots
repository, ignoring the operator's real settings.xml (mirrors, proxies,
offline mode, blocked repositories) entirely.

ApiRunner.createSession() gains an isolateUserHome flag (existing
callers keep isolation; production callers can opt out). mvnup now
builds its session against the operator's real settings and local
repository when settings were loaded, drops the hardcoded snapshots
repository, and skips remote-model-dependent analysis with a warning
when the effective settings declare a mirror, proxy or offline mode the
standalone resolver cannot honor, instead of resolving around it.
isExternalRepo/isExternalHttpRepo in the legacy DefaultMirrorSelector parsed
the repository URL with java.net.URL, which has no stream handler for the
dav/dav:http/dav+http schemes wagon-webdav speaks and throws for any other
unparseable URL, silently skipping the external:*/external:http:* mirror
rules for such repositories. Parse the scheme and host textually instead,
matching the fix already applied to MavenRepositorySystem.
@slachiewicz
slachiewicz force-pushed the pr/cli-and-transport-4.0.x branch from 37f8cb4 to 9038987 Compare August 31, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants