Skip to content

Fix prefix filter verification for denied artifact paths - #2188

Open
harshab054 wants to merge 3 commits into
apache:masterfrom
harshab054:master
Open

harshab054 wants to merge 3 commits into
apache:masterfrom
harshab054:master

Conversation

@harshab054

Copy link
Copy Markdown

Description

Fixes the Maven Resolver prefix filter behavior when a repository serves an artifact path that is missing from its "prefixes.txt".

A repository such as an Artifactory/Nexus virtual repository may expose a prefixes file generated from a public repository index while also serving private/internal artifacts. When the requested artifact path is absent from that prefixes file, the prefix filter can reject an artifact that the repository actually serves.

This change verifies a denied path against the repository before permanently rejecting it.

Implementation

  • Successfully served denied paths are cached and allowed on subsequent requests.
  • Definitively absent paths remain blocked and are negative-cached to avoid repeated remote probes.
  • Transient/unknown transport failures are not negative-cached, allowing later requests to retry.
  • Verification is performed per exact artifact path.
  • Multiple independently denied paths can be verified without disabling the repository's prefix filter globally.
  • Existing "verifyDeniedDropsTree=true" legacy behavior remains unchanged.

Security

The implementation preserves the security invariant introduced by Resolver PR #2082.

A successful verification of one denied path does NOT disable the prefix filter for the entire repository or authorize unrelated artifact paths.

Only the exact remotely verified path is allowed.

Tests

  • "PrefixesRemoteRepositoryFilterSourceVerifyDeniedTest": 14/14 passed
  • "PrefixesRemoteRepositoryFilterSourceTest": 6/6 passed
  • Checkstyle: passed with 0 violations
  • Spotless: passed
  • "git diff --check": clean

Related Maven Issue

This addresses the underlying Maven Resolver behavior reported in Apache Maven issue #13385:

apache/maven#13385

The original issue is reported against Maven because Maven users experience the resolution failure, while the prefix filtering implementation itself lives in Maven Resolver.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for tackling the per-path caching — the direction is right. A few observations:

This review was generated by an AI agent, Hermès on behalf of @gnodet.

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

Thanks for this — the per-path verification caching and the VerificationOutcome enum to distinguish transient from definitive failures are the right improvements here.

One thing that's missing and would make a real difference for users: an actionable error message when a path is rejected.

Right now, when a denied path makes it through the pre-checks and ends up getting a hard rejection (e.g. verifyDenied is disabled, or the path is in verifiedAbsentPaths), the reasoning() string in the result is just:

Path /com/example/internal/1.0/internal-1.0.jar NOT allowed from central

That's what users see in ArtifactFilteredOutException, and it gives them no hint of what's happening or how to work around it.

Since we're already in PrefixesRemoteRepositoryFilterSource and have access to cachedPrefixes.autoDiscovered(), repository.getId(), and the config property name constants, this is the ideal place to enrich the message. When the tree is auto-discovered and the path is rejected, the reasoning could be something like:

Path /com/example/internal/1.0/internal-1.0.jar NOT allowed from central
(auto-discovered prefix file appears incomplete). If this repository serves a
virtual aggregate with an incomplete prefix file, set
-Daether.remoteRepositoryFilter.prefixes.verifyDenied.central=true
or report this to the repository administrator.
See https://maven.apache.org/resolver/remote-repository-filtering.html

For user-provided prefix files (not auto-discovered), keep the bare message — those denials are deliberate.

Would you be willing to add this here? It's a small addition but would save a lot of debugging time for users hitting this in the wild.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The latest commits address all three previous observations — good follow-up:

  • ✅ Javadoc updated — CachedPrefixes class doc now correctly describes per-path caching of both served and absent paths.
  • ✅ synchronized block comment added — the intent (preventing duplicate verification of the same path, not global serialization) is now documented inline.
  • ✅ Wasted allocations resolved — DISABLED_PREFIXES / NO_INPUT_PREFIXES now use Collections.emptySet() when autoDiscovered=false; the empty sets allocated for static singletons are gone.

The test additions are solid: transportFailureDoesNotNegativeCachePath directly validates the UNKNOWN/retry semantics, unservedDeniedPathRemainsBlockedAndDoesNotDisableFilter covers negative caching, and the reasoning-string assertions in deniedPathAbsentFromRemoteStaysDenied pin the actionable error message content.

One informational note: remoteRepositoryServesPath uses a manual try/finally instead of try-with-resources. This is justified — transporter.classify(e) must be called inside the catch block where transporter needs to be in scope; try-with-resources would scope the variable to the try block only. No change needed.

No blocking issues.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet
gnodet requested a review from cstamas October 9, 2026 14:03
@cstamas

cstamas commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

I am very uneasy on changes like this (and previous #2082) and here is why: prefixes are usually (and are meant) to be resolved from remote, but, there are (may be) cases, when user wants to provide them manually, check out the RRF demo: https://github.com/cstamas/rrf-demo/tree/master/.mvn/rrf

So in latter case, prefixes now become "advice" only? In other word, in latter case they are "user provided configuration", that Maven may opt to simply ignore/override? Like, I configured it intentionally like this, but will Maven obey or not, is up to some heuristics.

We may then want to distinguish, between "user provided" (when content comes from a configured file) and "remotely resolved" (when it comes from remote)?

And this is the reason why I was pushing for "black or white": when present, no magic but fully obeyed, or when absent (404 or user configures it disabled) just work without it.

Essentially, it boils down to one thing: "fix your MRM, it is broken". No magic or heuristic helps here, when software is broken.

@harshab054

Copy link
Copy Markdown
Author

Thanks for raising this concern. I agree that explicitly user-provided prefix filters should be enforced strictly and should not be silently overridden.

My intention with this change was to address incomplete, automatically discovered prefix files while preserving strict filtering for user-provided configurations.

Would you recommend limiting the verification behavior strictly to automatically discovered prefixes, while keeping user-provided prefix files fully enforced? Or would you prefer a strict, black-or-white policy for both cases?

I'd be happy to adjust the implementation based on the intended Resolver behavior.

@cstamas

cstamas commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Yes, that would make sense: limit this behaviour for automatically discovered prefix files only.

And many-many thanks for the work so far!

@harshab054

Copy link
Copy Markdown
Author

Hi @cstamas, I've updated the PR to restrict denied-path verification exclusively to automatically discovered prefix files, while keeping user-provided prefix files strictly enforced. I've also addressed the singleton allocation and synchronization documentation feedback. The targeted tests and all 19 reported CI matrix jobs are passing. Whenever you have time, could you please take another look? Thank you for the feedback!

@cstamas
cstamas requested a review from ascheman October 9, 2026 18:17
@harshab054
harshab054 requested a review from gnodet-bot October 12, 2026 13:44
@cstamas cstamas added the enhancement New feature or request label Oct 12, 2026
@cstamas cstamas added this to the 2.0.25 milestone Oct 12, 2026
@cstamas
cstamas requested a balanced review from Copilot and removed request for gnodet-bot October 12, 2026 17:14

Copilot AI 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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

3 open findings
What changed in this PR

Fixes Maven Resolver’s prefix filter behavior when a repository serves artifact paths that are missing from an auto-discovered prefixes.txt, by verifying denied paths against the remote repository and caching the outcomes.

Changes:

  • Add per-artifact-path verification for denied paths when prefixes are auto-discovered (served paths become allowed; definite 404s become negative-cached; transient failures are retried).
  • Extend reasoning/messages for denied artifacts to include repository-specific verification guidance and documentation links.
  • Expand tests to cover multiple independent denied paths, per-path caching (positive/negative), and transient transport failures.
File Description
maven-resolver-impl/​src/​main/​java/​org/​eclipse/​aether/​internal/​impl/​filter/​PrefixesRemoteRepositoryFilterSource.java Implements per-path verification outcomes and caching; updates denial reasoning and verification behavior.
maven-resolver-impl/​src/​test/​java/​org/​eclipse/​aether/​internal/​impl/​filter/​PrefixesRemoteRepositoryFilterSourceVerifyDeniedTest.java Adds/updates test coverage for per-path verification, caching semantics, and reasoning content.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +674 to 694
private VerificationOutcome remoteRepositoryServesPath(RemoteRepository repository, String path) {
Transporter transporter = null;
try {
transporter = transporterProvider.newTransporter(session, repository);
transporter.peek(new PeekTask(new URI(null, null, path, null)));
return true;
return VerificationOutcome.SERVED;
} catch (URISyntaxException e) {
logger.debug("Cannot construct URI for denied path {} of {}", path, repository, e);
return false;
return VerificationOutcome.ABSENT;
} catch (Exception e) {
logger.debug("Verification of denied path {} against {} failed", path, repository, e);
return false;
if (transporter != null && transporter.classify(e) == Transporter.ERROR_NOT_FOUND) {
return VerificationOutcome.ABSENT;
}
return VerificationOutcome.UNKNOWN;
} finally {
if (transporter != null) {
transporter.close();
}
}
}
Comment on lines +384 to 392
private final Set<String> verifiedServedPaths;
private final Set<String> verifiedAbsentPaths;

private CachedPrefixes(PrefixTree prefixTree, boolean autoDiscovered) {
this.prefixTree = prefixTree;
this.autoDiscovered = autoDiscovered;
this.verifiedServedPaths = autoDiscovered ? ConcurrentHashMap.newKeySet() : Collections.emptySet();
this.verifiedAbsentPaths = autoDiscovered ? ConcurrentHashMap.newKeySet() : Collections.emptySet();
}
Comment on lines +631 to +635
reasoning += " (auto-discovered prefix file appears incomplete). If this repository serves a "
+ "virtual aggregate with an incomplete prefix file, set -D"
+ CONFIG_PROP_VERIFY_DENIED + "." + repository.getId()
+ "=true or report this to the repository administrator. "
+ "See https://maven.apache.org/resolver/remote-repository-filtering.html";

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Following up on my earlier review — all previous observations have been addressed, and the new commit is clean.

The delta (fixing verifiedAbsentResult on the post-lock path, extracting inconclusiveVerificationResult, correcting URISyntaxException → UNKNOWN to prevent false negative caching, leading-slash stripping, and the offline-aware reasoning branch in notAllowedResult) is correct throughout. All new tests exercise the right code paths.

LGTM.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants