Skip to content

Fix #13100: honor repositories from legitimately-active external model profiles - #13116

Merged
gnodet merged 3 commits into
apache:maven-3.10.xfrom
gnodet:fix/13100-restore-profile-repos-in-external-models
Sep 16, 2026
Merged

gnodet merged 3 commits into
apache:maven-3.10.xfrom
gnodet:fix/13100-restore-profile-repos-in-external-models

Conversation

@gnodet

@gnodet gnodet commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Problem

After 03c947d8 (and its successor #13114 which sandboxes the activation context), profile repositories in dependency POMs are always stripped from active profiles, even when the profile activated legitimately via JDK version, OS, activeByDefault, or a POM-declared property.

This breaks the established Maven pattern:

project → dep1 → dep2

where dep1 knows dep2 is not on Central and declares its repository inside a JDK- or activeByDefault-activated profile.

Discussed in #13100 (comment by @cstamas).

Root cause

The withoutRepositories() stripping (compat stack) and the .map(profile → profile.withRepositories(…)) stripping (impl stack) are applied to all active profiles unconditionally, regardless of why they activated. But the sandbox activation context in #13114 already guarantees that only legitimate profiles survive to injection:

Activation kind Activates in sandbox? Repos contributed?
JDK / OS / activeByDefault ✅ Yes ✅ Honored (this PR)
POM-declared <property> ✅ Yes ✅ Honored (this PR)
Consumer -D flag ❌ Suppressed by sandbox ❌ Never reaches injection
File condition ❌ Pre-filtered ❌ Never reaches injection

The stripping was therefore over-broad and broke legitimate use-cases with no security benefit (suppression already happens earlier in the sandbox).

Solution

Remove the repository stripping from both stacks for the externalOrigin / VALIDATION_LEVEL_MINIMAL branch:

  • compat (maven-model-builder): drop the withoutRepositories() call and the dead helper method from DefaultModelBuilder
  • impl (maven-impl): drop the .map() stripping from the externalOrigin branch of getActiveProfiles()

The BUILD_CONSUMER stripping (line 1755 in impl) is intentional and unchanged — consumer POMs must not leak profile repositories into the published artifact.

Notes

Changes

  • DefaultModelBuilder (compat): remove withoutRepositories() call and helper
  • DefaultModelBuilder (impl): remove .map() stripping from externalOrigin branch; update comment
  • ExternalModelProfileActivationTest: invert and rename the last test to assert the corrected behavior

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

Review: Fix #13100 — honor repositories from legitimately-active external model profiles

The sandbox approach in commit 4c8b837 is correct. Suppressing user properties and file checks via withoutUserPropertiesAndFilesystem() while preserving system and model properties is the right model. The PropertyProfileActivator fallthrough to getModelProperty() in both stacks correctly handles POM-declared-property activation. The test matrix in ExternalModelProfileActivationTest is thorough and accurate.

Commit 65e4821 (the actual #13100 fix) is logically sound — once the sandbox guarantees only legitimately-activated profiles survive, removing the blanket stripping is correct. However, this commit failed to update four noneMatch assertions in DefaultModelBuilderTest (impl stack) that assert the old behavior (repos always stripped from external dependency builds). Those four assertions will now fail.

Stale assertions — will fail

DefaultModelBuilderTest has the following noneMatch assertions for externalOrigin=true models:

  • Line 188 — testDependencyModelActivatesOnlyEnvironmentIndependentProfiles: asserts noneMatch for a CONSUMER_DEPENDENCY resolved-source model. With repo stripping removed, the JDK-activated profile's profile-repo will now appear → assertion fails.
  • Line 301 — testResolvedDependencyParentCacheDoesNotShareActivationWithProjectParent: same issue for dependencyParentModel.
  • Line 320 — same test, second pass (dependencyParentModel2).
  • Line 381 — testExternalOriginPropagatesThroughGrandparentHop: same issue for the grandparent model built under externalOrigin=true.

Note: the noneMatch at line 420 (testBuildConsumerActivatesOnlyDeterministicProfiles) is correct — BUILD_CONSUMER uses a separate branch that still strips repos, unchanged by this PR.

Fix: change lines 188, 301, 320, 381 from noneMatch to anyMatch (mirroring what ExternalModelProfileActivationTest now asserts).

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

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

Re-review: Fix #13100 — honor repositories from legitimately-active external model profiles

The new commit (80f709526b) still does not update ResolvedDependencyProfileActivationTest, and CI is failing on all three platforms with exactly the assertion predicted in the previous review.

Failing test (all 3 platforms)

org.apache.maven.model.building.ResolvedDependencyProfileActivationTest
  .testDependencyPomActivatesOnlyEnvironmentIndependentProfiles -- FAILURE
  ResolvedDependencyProfileActivationTest.java:121 expected: <true> but was: <false>

Root cause

ResolvedDependencyProfileActivationTest.java:121 still asserts the old behaviour:

trueAssertTrue(model.getRepositories().stream().noneMatch(r -> "profile-repo".equals(r.getId())));

With withoutRepositories() removed, the JDK-activated profile's profile-repo does survive to the built model — exactly the intent of this PR. The assertion must be inverted:

trueAssertTrue(model.getRepositories().stream().anyMatch(r -> "profile-repo".equals(r.getId())));

Also: stale class-level Javadoc (line 34)

The class comment says:

their profiles contribute no repositories

That was the pre-fix description. After this change, platform-activated profiles do contribute repositories for VALIDATION_LEVEL_MINIMAL builds; only file- and property-activated ones are suppressed. The Javadoc should be updated to reflect the corrected semantic, for example:

their file- and property-activated profiles are not evaluated; platform-activated profiles (JDK, OS, activeByDefault) still fire and may contribute repositories.

Logical correctness of the production change

The production code change itself remains sound — once #13114's sandbox guarantees that only legitimately-activated profiles reach injection, removing the blanket stripping is correct and no security regression is introduced. The only blocker is the two-line test / Javadoc update.

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

…l model profiles

After the sandbox activation context introduced in apache#13114, profiles in
dependency POMs that fire legitimately (JDK, OS, activeByDefault,
POM-declared property) are now correctly scoped. The blanket
withoutRepositories() stripping that applied to ALL active profiles in
external builds was over-broad: it broke the established pattern where

  project → dep1 → dep2

and dep1 knows dep2 is not on Central, declaring its repository inside a
JDK- or activeByDefault-activated profile.

Remove the withoutRepositories() call and the dead helper method from
DefaultModelBuilder. The sandbox already ensures that profiles activated
by consumer -D flags or file conditions do not fire in external builds,
so any profile whose repos survive to injection has activated on
legitimate grounds.
@gnodet
gnodet force-pushed the fix/13100-restore-profile-repos-in-external-models branch from 80f7095 to 9ff6500 Compare September 15, 2026 14:36

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

Re-review: Fix #13100 — honor repositories from legitimately-active external model profiles

Main blocker: resolved ✅

The failing test is fixed: testDependencyPomActivatesOnlyEnvironmentIndependentProfiles is now renamed testDependencyPomHonorsJdkActivatedProfileRepositories and the assertion correctly inverted to anyMatch. This is exactly what was flagged in the previous review.

The production change remains sound — withoutFileAndPropertyActivation() still gates profile selection, so only legitimately sandbox-compatible profiles reach injection; removing the blanket withoutRepositories() stripping from injection is correct.

Remaining nit: stale class-level Javadoc

The class-level Javadoc on ResolvedDependencyProfileActivationTest (lines 34–41) still says:

their profiles contribute no repositories

That was the pre-fix invariant. After this change, the true invariant is narrower: file- and property-activated profiles are suppressed, but platform-activated ones (JDK, OS, activeByDefault) fire legitimately and may contribute repositories. The Javadoc should be updated, for example:

 * Models built at {@link ModelBuildingRequest#VALIDATION_LEVEL_MINIMAL} come from POMs resolved
 * from a repository during dependency resolution (a dependency POM, one of its parents, or an
 * imported BOM), see for instance {@code DefaultArtifactDescriptorReader#loadPom}. Their file and
 * property activators are not evaluated. Platform-activated profiles (JDK version, OS,
 * {@code activeByDefault}) still fire and may contribute repositories; only file- and
 * property-activated profiles are suppressed. A project build, at
 * {@link ModelBuildingRequest#VALIDATION_LEVEL_STRICT}, still evaluates every activator.

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

@gnodet

gnodet commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 8ca2654: updated the class-level Javadoc on ResolvedDependencyProfileActivationTest to reflect the new invariant (platform-activated profiles may contribute repositories; only file- and property-activated ones are suppressed).

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

Re-review: Fix #13100 — honor repositories from legitimately-active external model profiles

All previous findings addressed ✅

  • Stale noneMatch assertions in DefaultModelBuilderTest (lines 188, 301, 320, 381) — fixed in 9ff6500f by renaming the test and inverting to anyMatch. ✅
  • Stale class-level Javadoc on ResolvedDependencyProfileActivationTest — fixed in 8ca2654b. The updated Javadoc correctly describes the narrowed invariant: file- and property-activated profiles are suppressed; platform-activated profiles (JDK, OS, activeByDefault) still fire and may contribute repositories. Proper {@code} and {@link} markup used throughout. ✅

Production change

Removing withoutRepositories(activeProfile) from the injection loop is correct. The sandbox (withoutFileAndPropertyActivation) already gates which profiles reach injection for external models — blanket stripping after the fact was redundant and over-broad. The helper method deletion is clean dead-code removal.

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

)

Port two findings from the master forward-port review (apache#13141):
- Add testActiveByDefaultProfileRepositoryHonored() using an isolated
  inline POM (no sibling profiles that would suppress activeByDefault),
  mirroring the activeByDefault fixture added to the impl stack test.
- Add TODO(apache#13146) comment at the profile injection site in DefaultModelBuilder
  noting that the deferred central-shadowing policy should be addressed
  separately.
gnodet added a commit to gnodet/maven that referenced this pull request Sep 16, 2026
…ompat stack)

Port two findings from the 3.10.x backport review (apache#13116) to the compat stack:
- Rename testDependencyPomActivatesOnlyEnvironmentIndependentProfiles to
  testDependencyPomHonorsJdkActivatedProfileRepositories for accuracy.
- Add testActiveByDefaultProfileRepositoryHonored() using an isolated inline
  POM (no sibling profiles that would suppress activeByDefault), mirroring the
  test added to impl/DefaultModelBuilderTest in the same PR.

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

Re-review: Fix #13100 — honor repositories from legitimately-active external model profiles

All previous findings addressed ✅

New commit b864ff09 reconciles this 3.10.x PR with the parallel master-stack PR (#13141). Two additions:

  1. TODO(#13146) comment at the profile injection site in DefaultModelBuilder — correctly references the open issue tracking the deferred central-shadowing WARN/FAIL policy. ✅

  2. testActiveByDefaultProfileRepositoryHonored() test — the isolated inline POM approach is correct. Without isolation, the JDK-activated sibling in the shared POM fixture would cancel activeByDefault (standard Maven rule), making the assertion vacuous. StringModelSource exists in maven-model-builder/src/main/java — the import is valid on the 3.10.x stack. No model resolver is needed since the inline POM declares no parent. ✅

The production change (removing withoutRepositories() stripping) remains sound — the sandbox already ensures only legitimately-activated profiles reach injection for external models.

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

@gnodet gnodet added this to the 3.10.0 milestone Sep 16, 2026
@gnodet
gnodet merged commit dda1c3f into apache:maven-3.10.x Sep 16, 2026
12 checks passed
gnodet added a commit that referenced this pull request Sep 16, 2026
…l profiles

The `withoutRepositories()` stripping (compat stack) and the `.map()` stripping (impl stack) were applied to **all** active profiles in the `externalOrigin` branch unconditionally — regardless of why they activated.

The `hasFileOrPropertyOrConditionActivation` filter already suppresses file- and property-activated profiles before they reach injection. The blanket strip was therefore over-broad: it discarded repositories from legitimately-active profiles (JDK/OS, `activeByDefault`) with no security benefit, breaking the established `project → dep1 → dep2` pattern where `dep1` declares `dep2`'s non-Central repository inside a JDK- or `activeByDefault`-activated profile.

**Changes:**
- compat (`maven-model-builder`): remove `withoutRepositories()` call; remove dead helper method; add TODO(#13146)
- impl (`maven-impl`): remove `.map()` strip from the `externalOrigin` branch; update comment; add TODO(#13146)
- Tests updated: `noneMatch → anyMatch` for `externalOrigin=true` cases; `testActiveByDefaultProfileRepositoryHonored()` added; stale test name corrected

`BUILD_CONSUMER` stripping is intentional and **unchanged**.

Fixes #13100. See also: #13116 (maven-3.10.x backport).
@github-actions

Copy link
Copy Markdown
Contributor

@gnodet Please assign appropriate label to PR according to the type of change.

gnodet added a commit that referenced this pull request Sep 16, 2026
…l profiles (#13155)

* Fix #13100: honor repositories from legitimately-active external model profiles

Backport of #13141 (master) and #13116 (maven-3.10.x) to maven-4.0.x.

The withoutRepositories() stripping (compat stack) and the .map() stripping
(impl stack) were applied to all active profiles in the externalOrigin branch
unconditionally — regardless of why they activated.

The hasFileOrPropertyOrConditionActivation filter already suppresses file- and
property-activated profiles before they reach injection. The blanket strip was
therefore over-broad: it discarded repositories from legitimately-active profiles
(JDK/OS, activeByDefault) with no security benefit, breaking the established
pattern where a dependency POM declares a non-Central repository inside a JDK-
or activeByDefault-activated profile.

Changes:
- compat: remove withoutRepositories() call from the profile injection loop;
  remove the now-dead withoutRepositories() helper method; add TODO(#13146)
- impl: remove .map() repository stripping from the externalOrigin branch;
  update comment; add TODO(#13146)
- tests: flip noneMatch → anyMatch for all externalOrigin=true assertions;
  add testActiveByDefaultProfileRepositoryHonored() with isolated fixture;
  rename testDependencyPomActivatesOnlyEnvironmentIndependentProfiles

BUILD_CONSUMER stripping is intentional and unchanged.

* Fix stale Javadoc and GAV typo in external-profile tests

- DefaultModelBuilderTest: update resolvedProfilesRequest() Javadoc
  (stale 'contribute no repositories'); fix active-by-default-profile
  resolvedSource GAV (org.apache.maven.test, matching fixture and
  existing test convention)
- active-by-default-profile.xml: fix groupId plural→singular to match
  the rest of the poms/factory fixtures and the test coordinate hint
- ExternalModelProfileActivationTest: update class-level Javadoc
  (stale 'contribute no repositories')

Found during review of #13155.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants