Repository navigation
Stop vendored Maven liveness attesting unpatched builds - #2006
Draft
Mikola Lysenko (mikolalysenko) wants to merge 2 commits into
Draft
Mikola Lysenko (mikolalysenko) wants to merge 2 commits into
Mikola Lysenko (mikolalysenko) wants to merge 2 commits into
Conversation
Three cases where vendored JVM (Maven reactor) liveness proved an entry "wired" and let vex attest while the build may still use the unpatched jar. Hosted mode and VEX discovery already refuse all three. J2, executable classifier copies: when the project also declares an executable classifier copy of the patched GA (tests, a native build), the reactor planner only warned `classifier_declared` and moved on, so `attestable_checked` passed. The classifier-less pin doesn't retarget the copy and the vendored tree serves only the main jar, so Maven fetches the public release and its code runs. A used (non-managed) executable classifier declaration whose version isn't the suffixed one now marks the reactor uncertain, so the entry isn't attestable. `sources` and `javadoc` still only warn. The executable-classifier rule now lives in `formats::maven::is_executable_classifier`, shared with the hosted rewriter and discovery. J8, unscoped "wired" check: `maven_reactor::wired_checked` was a substring match for the suffixed version over every reactor pom's comment-masked text. A suffixed version left only in a build plugin's dependencies, a profile, an unused property, another artifact's or a classifier copy's declaration kept the entry live. It now requires a `<dependencies>`/`<dependencyManagement>` declaration outside `<profiles>` (Doc::declarations already excludes `<build>`/`<reporting>`) naming the GA's main jar at the suffixed version, literally or through a property the checkout defines. That covers `vex`/`attestable_checked`, `vendor --check` (`check_entry`) and the plan's `refuse_unwired`. The old substring check is kept as `references_checked` and used only by the `scan --prune` GC (`entry_references`): the maven.config tail also serves plugin resolution, so a tree a `<build>` plugin or a profile still names must not be reverted out from under the build. J15, fail-open on read errors: `entry_wired` was `entry_wired_checked(..).unwrap_or(true)`, so an unreadable or unparseable pom made `Discovery::vendor_entry_live` (vendor's kept-patch rule, scan's takeover classifier and vex's ledger liveness) report a JVM entry as live. It now fails closed (`false`). The GC doesn't use it: it asks `entry_references`, which still answers "keep" on a read error, so nothing is deleted on an unknown answer. Tests (each fails on origin/main): - maven_reactor: an_executable_classifier_copy_is_not_attestable - maven_reactor: a_suffixed_version_outside_the_declarations_is_not_wired - apply: an_unreadable_pom_is_not_live_but_is_kept (also pins that entry_references / vendor_entry_in_use still keep the tree) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Nov 4, 2026
Review findings on the profile / user-property attestation fix:
- A profile <dependencyManagement> entry was flagged even when the live
pin is a direct literal <version>, which beats management; likewise a
profile-managed classifier entry no declaration takes. Profile
management now withholds the pin only when management governs it (no
live direct version). The old test case managed(Some("1.7.36")) beside
a direct pin asserted the wrong outcome and now expects an attested
pin; the governing case (managed live pin) expects the opposite.
- A version-less profile classifier copy was always flagged. One shared
classifier check now covers live and profile copies and resolves a
version-less copy from the classifier-keyed management (live, with a
profile's entry overriding it; none in this pom fails closed). Profile
classifier copies report MavenClassifierUnpatched, like live ones.
- The same check now runs for the pre-v5 vendored wiring too, where any
executable classifier copy comes from the next repository (the vendored
tree holds only the main jar). formats::maven gains
NON_EXECUTABLE_CLASSIFIERS / is_executable_classifier, worded as in
#2006 so the two merge cleanly.
- A pom-typed (or java-source / javadoc) profile declaration carries no
code and no longer counts against the pin. Other types (test-jar, ejb)
still do: they put a jar on a classpath.
- PomDep now keeps both the version as the pom spells it (`version`, what
hosted writes and rolls back) and as the build resolves it
(`build_version`, -D user properties first). Pins are found from the
former, so a pin .mvn/maven.config moves stays a ref (list / rollback /
remove and liveness keep the wiring) but is marked unattested with the
new UnattestedKind::MavenConfigOverride (CLI note
vex_maven_config_override). A Socket version only maven.config
supplies is diagnosed (DIAG_REF_UNATTRIBUTABLE naming
.mvn/maven.config), never a pom.xml ref, so nothing edits pom.xml for
it.
- The build view leaves project.* / pom.* to the model: -Dproject.version
no longer resolves a ${project.version} dependency version.
- Cleanup: main_jar_declarations / classifier_declarations take a scope
predicate and back ga_versions, the classifier check and the profile
check; group_by_ga is gone (live_gas lists the pin candidates);
trusted_checksums takes the maven.config text extract already read.
Tests: version_property_reads_maven_config_overrides,
profile_declaration_off_the_pin_is_not_attested and
vendored_ref_bypassed_by_a_profile_or_classifier_is_not_attested
(renamed from vendored_ref_under_a_profile_version_is_not_attested) were
extended with a case per finding; each new case failed on the previous
commit. formats::maven gains model_reference_version_ignores_user_properties.
The golden corpus is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes all nine /code-review high findings on #2006. 1. A typed copy of the GA (`<type>test-jar</type>`, which is the `tests` jar, or `ejb`, `ejb-client`, `maven-plugin`, an unknown type) was skipped with a bare `continue`, so it still attested. A new shared rule, `formats::maven::is_executable_variant(type, classifier)`, maps types to the classifier they imply and treats every type except `pom`, `java-source` and `javadoc` as executable. The reactor now warns `classifier_declared` for such a copy and marks it uncertain unless every reader resolves it to the suffixed version. 2. With `entry_wired` failing closed, `scan`'s overlap takeover classifier read an unreadable pom, or vendored wiring left only in a plugin or profile, as a dead vendored claim. It then emitted `redirect_supersedes_vendored` ("remove <purl>") while the GC still kept the entry. New tri-state: `apply::entry_liveness`, `Discovery::vendor_entry_liveness` and `LedgerLiveness:: vendor_entry_state`. It returns `None` when the claim is undecided (read error, or still referenced without attestable wiring), and the classifier stays silent on `None`. 3. A classifier copy's version now goes through the same override check as the main jar's (`version_moved`, generalized from `version_override`). An inheriting pom or a profile that moves the property off the suffixed version makes it uncertain. 4. A version-less declaration now reads its version from the nearest local `<dependencyManagement>` on its chain, interpolated as the declaring pom reads it. Anything other than the suffixed version, including no local management (a local root left unpinned), is uncertain. One pinned root no longer proves an unpinned sibling root. 5. `vendor --check` now agrees with `vex`. `check_entry` returns `Ok(Some(why))` when the entry is intact and wired but not attestable. `vendor --check` keeps the entry `verified` and adds the new run warning `vendor_jvm_unattested` naming the declarations (documented in CLI_CONTRACT.md). It is a warning rather than a failure because the contract's unattested-references rule says liveness gates keep such wiring live, and re-vendoring cannot clear it. 6. `Reactor::wires` now interpolates each declaration in the declaring pom and in every pom that inherits it, as Maven interpolates after inheritance. A parent's managed `${ct.version}` that each module defines as the pin now counts as wired again, so `refuse_unwired` no longer refuses that plan. 7. The classifier version check uses `Doc::coordinate`. 8. The main-jar predicate is `formats::maven::is_main_jar`, used through `Doc::is_main_jar` by `wires`, `rewrite_declarations`, `warn_unresolved_coordinates` and `jar_declaration`. 9. Removed the uncalled `maven_reactor::wired`. Tests (each fails with its fix neutralized): - plugin_dependencies_exclusions_and_other_types_are_untouched: updated. It pinned the silent `test-jar` pass-through; executable types now warn and don't attest, while `pom` and `java-source` still pass silently. - a_classifier_copy_a_reader_moves_back_to_the_base_is_not_attestable - a_version_less_declaration_under_an_unpinned_root_is_not_attestable - a_parent_declaration_its_modules_resolve_to_the_pin_is_wired - vendor_check_reports_an_unattestable_entry - maven_takeover_stays_silent_on_an_undecided_vendored_claim (CLI) - an_unreadable_pom_is_not_live_but_is_kept also covers the tri-state Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Vendored JVM (Maven reactor) liveness attested in cases where hosted mode and VEX discovery don't. The owner rule says hosted's stricter behavior is ground truth, so this PR brings vendored mode in line.
What attested wrongly, and the fix
J2: executable classifier and type copies. The project could also declare an executable copy of the patched GA:
<classifier>tests</classifier>, a native classifier, or<type>test-jar</type>. The planner only warned (or, for typed copies, skipped silently), so the entry still attested. The classifier-less pin doesn't retarget the copy, and the vendored tree serves only the main jar, so Maven fetches the public release and its code runs.dependencyManagement) executable copy isuncertain, and so not attestable, unless every pom that reads it resolves the suffixed version. That check uses the same inheritor/profile override check as the main jar.formats::maven:is_main_jar: untyped orjar, no classifier.is_executable_variant: a type counts as the classifier it implies (test-jar→tests,ejb-client→client,java-source/javadoc→ non-executable). Every other type exceptpomis executable.is_executable_classifier: everything exceptsources/javadoc. Hosted mode and discovery use it too.<profile>also blocks attestation, because the profile may be active.J8: unscoped "wired" check.
maven_reactor::wired_checkedsearched every reactor pom's text for the suffixed version. A suffixed version that appeared only in a build plugin's dependencies, a<profile>, an unused property, another artifact's declaration or a classifier copy kept the entry wired.<dependencies>or<dependencyManagement>declaration outside<profiles>that names the main jar at the suffixed version.Doc::declarationsalready excludes<build>and<reporting>.${...}is interpolated in the declaring pom and in every pom that inherits it, as Maven interpolates after inheritance.<dependencyManagement>on its chain. One pinned local root no longer proves an unpinned sibling root.vex,vendor --checkand the planner'srefuse_unwireduse this check.J15: fail-open on read errors.
entry_wiredwasunwrap_or(true), so an unreadable or unparseable pom madeDiscovery::vendor_entry_livereport a JVM entry as live. It now fails closed.GC, the takeover classifier and
vendor --checkscan --pruneGC: unchanged on purpose. It usesentry_references, which still answers "keep" on a read error. The old text search stays asmaven_reactor::references_checkedfor the GC only, because the.mvn/maven.configtail repository also serves plugin resolution: a tree a<build>plugin or a profile still names must not be reverted out from under the build.scan's overlap takeover classifier: now uses a tri-state:apply::entry_liveness,Discovery::vendor_entry_livenessandLedgerLiveness::vendor_entry_state. "Unknown" is not "dead": an unreadable pom, or a tree still referenced without attestable wiring, givesNone. The classifier stays silent then, instead of emittingredirect_supersedes_vendored("remove ") for an entry the GC keeps.vendor --check: now agrees withvex. An intact, wired entry that isn't attestable staysverified, and the run gets the new warningvendor_jvm_unattested, which names the declarations. The new code is documented inCLI_CONTRACT.md. It's a warning, not a failure, because of the contract's unattested-references rule: liveness gates keep such wiring live, and re-vendoring cannot clear it.Other behavior changes
vendor's kept-older-patch rule: an unreadable kept entry now means the unserved upgrade fails asvendor_prebuilt_requiredinstead of being a benign skip.refuse_unwired: a plan whose only wiring is a rewritten profile declaration, with no root pin, is refused asnothing_wired.classifier_declareddegraded warning to the plan.Review findings (/code-review high)
All nine are confirmed and fixed. Each behavior test fails when its fix is neutralized.
test-jarskipped before the classifier check. Fixed with the shared type rule.plugin_dependencies_exclusions_and_other_types_are_untouchedpinned the old silent pass-through; it now expects executable types to warn and not attest, whilepomandjava-sourcestill pass silently.maven_takeover_stays_silent_on_an_undecided_vendored_claim(CLI), with a superseded control case.a_classifier_copy_a_reader_moves_back_to_the_base_is_not_attestable.a_version_less_declaration_under_an_unpinned_root_is_not_attestable.vendor --checkignored J2. It now runsunattestable_checkedand warnsvendor_jvm_unattested. Test:vendor_check_reports_an_unattestable_entry.a_parent_declaration_its_modules_resolve_to_the_pin_is_wired.attestable_checkedstill treats a parent declaration whose${...}the parent itself can't resolve as uncertain (property_unresolved); that rule predates this PR and I left it strict.Doc::coordinate.Doc::is_main_jar/formats::maven::is_main_jar.maven_reactor::wired. Removed.Tests
cargo test -p socket-patch-core --lib -- vendor::jvm:: vex::discover:: formats::maven: 710 passedcommitted_fixture_corpus_matches_golden: passes, output unchanged-- vendor:: patch::redirect:: utils::purl_key: 3756 passed-- takeover commands::vendor::: 76 passed--test vendor_jvm_cli: 54 passed--test maven_sidecar_cli: 28 passed--test e2e_vendor_jvm_build: 35 passed, 6 ignored--test e2e_vendor_maven_build: 4 passed, 1 ignoredcargo clippy --locked --workspace --all-features -- -D warningsandcargo fmt --all -- --checkare clean.🤖 Generated with Claude Code