Skip to content

Resolver 2.0.23-SNAPSHOT and validation cleanup - #13078

Draft
cstamas wants to merge 1 commit into
apache:maven-3.10.xfrom
cstamas:maven-3.10.x-resolver-2023
Draft

Resolver 2.0.23-SNAPSHOT and validation cleanup#13078
cstamas wants to merge 1 commit into
apache:maven-3.10.xfrom
cstamas:maven-3.10.x-resolver-2023

Conversation

@cstamas

@cstamas cstamas commented Sep 8, 2026

Copy link
Copy Markdown
Member

We had overlapping changes in Resolver and Maven, align them.

We had overlapping changes in Resolver and Maven,
align them.
@cstamas cstamas added this to the 3.10.0 milestone Sep 8, 2026
@cstamas cstamas self-assigned this Sep 8, 2026
@cstamas cstamas added the dependencies Pull requests that update a dependency file label Sep 8, 2026
@ascheman

ascheman commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Sure about the SNAPSHOT-dependency, @cstamas ? Or will you resolve this before you make the draft final?

@cstamas

cstamas commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

We usually run "main" branches (in quotes, as we have several "main" branches, the master, the maven-4.0.x, the maven-3.10.x, etc) just before the release with SNAPSHOT versions, as we do produce and deploy them, is fine to get another layer of confirmation. OTOH, you cannot release anyway with a SNAPSHOT, hence, this is completely okay, and we did it before as well.

@cstamas

cstamas commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

So, all of ITs explode, reason is f013 in this commit:
apache/maven-resolver@339161b

@gnodet ping

@gnodet

gnodet commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

@cstamas The root cause is the tracking key function change from nid to nid_hurl in commit 339161b (f013). All existing _remote.repositories entries use ID-only keys (e.g. artifact>central=), but the new resolver looks for URL-qualified keys (artifact>central-<sha1>=). No match → every cached artifact appears "present but unavailable" → re-download from the IT's fake repos → 💥

This affects all 9 IT jobs (4229 test failures, 507 unique test classes on ubuntu/JDK26 alone). The existenceCheckRelabel change (f005) is secondary — even with the old existence-check behavior, the tracking key mismatch would cause failures because the fake repos can't serve the artifacts either way.

Fix PR: apache/maven-resolver#2133 — adds a backward-compatible fallback in applyTracking(): when the nid_hurl lookup misses, tries the system-wide key function (nid) as fallback. If the legacy key matches, the artifact is accepted with a debug log. The entry gets upgraded to nid_hurl on next download. Security intent of f013 is preserved for new entries.

cstamas pushed a commit to apache/maven-resolver that referenced this pull request Sep 8, 2026
…allback (#2133)

## Summary

When the tracking key function is URL-qualified (`nid_hurl`, the default since 2.0.23 / commit 339161b), tracking entries written by an older resolver using ID-only keys (`nid` format, e.g. `artifact>central=`) are invisible to the new lookup which expects `artifact>central-<sha1>=`.

This causes **all** artifacts cached in the local repository before the upgrade to appear as "present but unavailable", triggering full re-downloads from remote repositories. In CI environments and integration tests that use fake/file-based repositories (like maven-integration-testing), this breaks resolution entirely — see [apache/maven#13078 (comment)](apache/maven#13078 (comment)).

## Root Cause

The tracking key change from `nid` to `nid_hurl` (finding f013 in the security audit) makes existing `_remote.repositories` entries invisible:

- **Old entry**: `maven-core-3.8.6.pom>central=`
- **New lookup**: `maven-core-3.8.6.pom>central-<sha1 of repo URL>=` → no match

The `isTracked()` check finds the old `central=` entry, so the artifact is treated as "tracked but not for the current repo" rather than "untracked" — the untracked inter-op fallback does not apply, and the artifact is rejected.

## Fix

Add a backward-compatible fallback in `applyTracking()`: when the URL-qualified (`nid_hurl`) lookup misses, try the system-wide key function (`nid` by default) as a fallback. If that matches, accept the artifact and log a debug message. The legacy entry will be upgraded to the new key format on the next download.

This preserves the f013 security improvement (URL-qualified tracking prevents same-id-different-URL repository poisoning for *new* entries) while providing a smooth upgrade path from older resolvers.

## Tests

- Updated `testUrlQualifiedTrackingTreatsLegacyIdOnlyEntriesAsStale` → `testUrlQualifiedTrackingAcceptsLegacyIdOnlyEntriesViaFallback`: legacy entries from a matching repo are now accepted
- Added `testUrlQualifiedTrackingRejectsLegacyIdOnlyEntriesFromDifferentRepo`: legacy entries from a *different* repo are still rejected
- `testUrlQualifiedTrackingDistinguishesSameIdDifferentUrl`: unchanged — URL-qualified entries still prevent same-id-different-URL poisoning
@gnodet

gnodet commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Updated the resolver fix PR based on @cstamas's analysis. The first fix only handled legacy nidnid_hurl migration, but the real problem is different:

The ITs override central to file:target/null to prevent remote access. With nid_hurl, the tracking key includes a hash of the repo URL: central-<sha1(https://repo.maven.apache.org/maven2)>central-<sha1(file:target/null)>. Same repo ID, different URL hash → tracking key mismatch → artifact treated as "present but unavailable" → tries to download from file:target/null → 💥

The fix now has a two-stage fallback:

  1. Stage 1: ID-only match (handles legacy nid entries from older resolvers)
  2. Stage 2: repo-ID prefix match (handles same-id-different-URL nid_hurl entries — the IT scenario)

This effectively relaxes f013 to recording-only: entries are written with URL-qualified keys, but lookups fall back to repo-ID matching. All 585 resolver tests pass.

Fix PR: apache/maven-resolver#2133 (force-pushed with the updated fix)

@ascheman

ascheman commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

@cstamas — the SNAPSHOT and IT-explosion questions are resolved on my side (thanks, and to @gnodet for #2133). One code point from the validation consolidation itself, independent of the IT fix:

The removed DefaultMetadataReader.validateMetadata() validated both plugin.getArtifactId() and plugin.getPrefix(), but the consolidated ValidatingMetadataXpp3Reader.validate() adds only plugin.getArtifactId()getPrefix() is no longer validated. Since the plugin prefix is carried forward as a coordinate/path component (the old reader's comment flagged prefixes as such), is dropping its validation intentional, or should it get a PathUtils.validatePathComponent(plugin.getPrefix(), "plugin/prefix") alongside the artifactId?

Two minors: validate() went publicprivate (fine — internal package, no external caller left), and there's no test yet exercising the plugin-token validation path.

@slachiewicz
slachiewicz removed their request for review September 9, 2026 14:53

@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.

Review: validation consolidation looks correct, one gap to fix

The consolidation from two separate validation sites (DefaultMetadataReader.validateMetadata() and DefaultRepositoryMetadataManager.validateVersioning()) into the single ValidatingMetadataXpp3Reader.validate() is clean — it eliminates the duplicate validateVersioning() call and the now-redundant per-reader validation logic.

The new validate() method correctly covers versioning tokens (latest, release, versions[], snapshotVersions[].version, snapshot.timestamp) and plugin.getArtifactId(). The snapshot timestamp validation and metadata.getVersion() validation are new additions not present in either old reader — good.

However, as @ascheman noted: plugin.getPrefix() validation was dropped. The removed DefaultMetadataReader.validateMetadata() explicitly validated both plugin.getArtifactId() and plugin.getPrefix(), with its Javadoc stating these "get carried forward by callers as if they were already-validated path and coordinate components." The consolidated reader only validates plugin.getArtifactId().

Fix: add PathUtils.validatePathComponent(plugin.getPrefix(), "plugin/prefix"); alongside the artifactId validation in ValidatingMetadataXpp3Reader.validate().

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of @gnodet

PathUtils.validatePathComponent(metadata.getVersion(), "version");
for (Plugin plugin : metadata.getPlugins()) {
PathUtils.validatePathComponent(plugin.getArtifactId(), "plugin/artifactId");
}

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.

@ascheman's observation is correct — plugin.getPrefix() validation was present in the removed DefaultMetadataReader.validateMetadata() but was not carried over here. Since prefixes are used as coordinate/path components downstream, this should be:

Suggested change
}
PathUtils.validatePathComponent(plugin.getArtifactId(), "plugin/artifactId");
PathUtils.validatePathComponent(plugin.getPrefix(), "plugin/prefix");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants