Skip to content

Vendored Maven reactor reports applied and VEX attests not_affected when a ${property} version is left unresolved, so the build keeps Central's unpatched jar (also triggered by Maven 4 <parent/> inference) #513

Description

[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).

Summary

When a reactor module declares the patched GA as <version>${prop}</version> and the v5 reactor planner (vendor/jvm/maven_reactor.rs) can't resolve ${prop}, it emits vendor_jvm_degraded / property_unresolved ("left as is, probably unpatched"). It leaves the declaration alone but still writes the module's <dependencyManagement> pin and the socket-patch-vendor repository. Maven applies the module's explicit ${prop} version over its own management, so the build keeps Central's unpatched 1.10.0 jar. All the same:

  • vendor exits 0 with status: success and summary.applied: 1.
  • vex exits 0 and emits not_affected for pkg:maven/org.apache.commons/commons-text@1.10.0. Its only warning is vendored_tree_out_of_sync ("the attestation is based on the committed .socket/vendor artifact (the lockfile consumes it)"), but the build doesn't consume that artifact.

There are two common ways to hit this:

  1. Maven 3 and 4: the property comes from a parent outside the checkout. For example, a corp parent or spring-boot-starter-parent (<relativePath/>) defines <ct.version>1.10.0</ct.version> and a module uses ${ct.version}. The warning is accurate here, but applied: 1 and the VEX attestation are not.
  2. Maven 4 (model 4.1.0): parent inference. A module with <parent/> (no coordinates; Maven 4 infers the parent from ../pom.xml) uses ${ct.version}, which is defined in the root pom in the checkout. local_parent_of compares the parent's groupId/artifactId (both None) with the root's, finds no match, and treats the parent as remote. The module then becomes its own local root, and the planner reports the property as "not defined in the checkout", which is false. The build is unpatched in the same way, and VEX still attests.

Impact

VEX says not_affected for a vulnerability the shipped artifact still has, and vendor counts the patch as applied. The degraded warning is easy to miss among the always-present maven_f_outside_root / maven_mirror_of_all degraded events (every run in this report emitted all three). Property-managed versions inherited from an external parent are a very common Maven layout. docs/design/maven-vendoring.md says unresolved properties "produce specific warnings; the backend does not silently claim those unsupported declarations are patched", but the VEX attestation is exactly such a claim.

Repro

I used the repo's own harness: a local, uncommitted copy of e2e_vendor_jvm_build::maven_reactor (its warm_fixture, stage_manifest and prebuilt_common::prepare_command fixture server, plus the commons-text 1.10.0 NOTICE marker patch, uuid 1d3c1fd2-…), with only the fixture POMs swapped. The classpath oracle is mvn package dependency:build-classpath from a fresh checkout, with 1.10.0 and the suffixed version purged from the local repository.

Case 1, external parent property (Maven 3.6.3 → 4.0.0-rc-7):

<!-- installed in the local repo: com.corp:corp-parent:3 -->
<project><modelVersion>4.0.0</modelVersion><groupId>com.corp</groupId><artifactId>corp-parent</artifactId><version>3</version><packaging>pom</packaging>
  <properties><ct.version>1.10.0</ct.version></properties></project>

<!-- pom.xml (root) -->
<project><modelVersion>4.0.0</modelVersion>
  <parent><groupId>com.corp</groupId><artifactId>corp-parent</artifactId><version>3</version><relativePath/></parent>
  <groupId>com.example</groupId><artifactId>root</artifactId><version>1.0.0</version><packaging>pom</packaging>
  <modules><module>a</module></modules></project>

<!-- a/pom.xml -->
<project><modelVersion>4.0.0</modelVersion>
  <parent><groupId>com.example</groupId><artifactId>root</artifactId><version>1.0.0</version></parent>
  <artifactId>a</artifactId>
  <dependencies><dependency><groupId>org.apache.commons</groupId><artifactId>commons-text</artifactId><version>${ct.version}</version></dependency></dependencies></project>

Case 2, Maven 4 parent inference (4.0.0-rc-7; nothing outside the checkout):

<!-- pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.1.0"><modelVersion>4.1.0</modelVersion>
  <groupId>com.example</groupId><artifactId>root</artifactId><version>1.0.0</version><packaging>pom</packaging>
  <subprojects><subproject>a</subproject></subprojects>
  <properties><ct.version>1.10.0</ct.version></properties></project>

<!-- a/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.1.0"><modelVersion>4.1.0</modelVersion>
  <parent/>
  <artifactId>a</artifactId>
  <dependencies><dependency><groupId>org.apache.commons</groupId><artifactId>commons-text</artifactId><version>${ct.version}</version></dependency></dependencies></project>

Then, with the commons-text@1.10.0 patch staged in .socket/manifest.json:

socket-patch vendor --json --offline
#   exit 0, status success, summary.applied 1
#   events: … vendor_jvm_degraded "reason: property_unresolved: a/pom.xml:6: org.apache.commons:commons-text
#           version ${ct.version} is not defined in the checkout; left as is, probably unpatched"
#   a/pom.xml gains <dependencyManagement> commons-text 1.10.0-socket.1d3c1fd2 + the socket-patch-vendor <repository>
#   (case 2: the module is wired as its own root; the root pom is untouched)
socket-patch vex --json --offline --output vex.json
#   exit 0, events[0] {action: verified, status: not_affected}, warning vendored_tree_out_of_sync
# fresh checkout, commons-text purged from the local repo:
mvn package dependency:build-classpath -Dmdep.outputFile=target/cp.txt
#   a/target/cp.txt -> …/m2/org/apache/commons/commons-text/1.10.0/commons-text-1.10.0.jar   (Central's, unpatched)

Expected vs actual

Matrix (Linux, JDK 21, main 61cfb9b)

Maven Case 1: external-parent ${prop} Case 2: 4.1.0 <parent/> + root ${prop} Controls: 4.1.0 <parent/> with a literal 1.10.0, or versionless + root dM
3.6.3 unpatched, VEX attests n/a (no 4.1.0 model) n/a
3.8.8 unpatched, VEX attests n/a n/a
3.9.11 unpatched, VEX attests (2/2) n/a n/a
4.0.0-rc-7 unpatched, VEX attests unpatched, VEX attests (2/2) patched (pass)

This is planner/VEX logic with no OS dependence, so I ran no macOS/Windows probe. Not bisected: the reactor backend is new in v5 (#277).

Suspect code

  • crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs:1001-1013: property_unresolved pushes a degraded warning and continues. The root is not added to unpinned (compare the conflicting_literal_version branches, which do unpinned.insert(root)), so the pin, the repository and the "applied" outcome all stand.
  • crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs:1142-1196 (local_parent_of): a <parent/> with no groupId/artifactId never matches candidate.effective_group() / candidate.artifact, so Maven 4 inferred parents count as remote.
  • crates/socket-patch-cli/src/commands/vex_sources.rs (liveness, vendor_unwired): liveness is satisfied by the module's own pin, although the module's explicit version overrides it.

Related, but a different root cause: #488 (an external BOM/parent version is overridden by the pin, a downgrade), #459 (profile properties).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions