Skip to content

Hosted Maven mode edits only the root pom of a multi-module build, leaves child literals unpatched, and still attests VEX #261

Description

[agent] Filed by Claude Code on behalf of Mikola Lysenko (@mikolalysenko) while adding Maven patch SBOM annotations to depscan. Repro artifacts were produced with real Maven and a stub patch API.

Summary

Vendored Maven refuses aggregator roots (vendor_maven_multimodule_unsupported). Hosted Maven does neither of the following:

  • refuse the aggregator, or
  • read the child poms.

When a module declares the patched GA with a literal base version, the rewriter can't find it in the root. It falls into the "transitive" branch and adds a root <dependencyManagement> pin. A child's literal <version> always beats an inherited depMgmt entry, so that module keeps resolving the unpatched upstream jar.

The scan reports redirected: 1, and socket-patch vex attests not_affected. VEX reads only the root pom, sees the suffixed depMgmt pin plus the repository, and trusts it.

Impact

  • The module stays silently unpatched, which is fail-open.
  • The VEX statement for the product is false.

Multi-module builds are the norm for non-trivial Maven projects.

Repro

Root pom.xml:

<project><modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId><artifactId>root</artifactId><version>1.0.0</version><packaging>pom</packaging>
  <modules><module>child</module></modules>
</project>

child/pom.xml: <parent>com.example:root:1.0.0</parent>, with a dependency on org.apache.commons:commons-lang3 <version>3.12.0</version>.

Grant: commons-lang3 3.12.0 → 3.12.0-socket.4d5e6f70, from the basic golden override.

socket-patch scan --mode hosted --json --yes ...
  -> exit 0, redirected=1, rewrittenFiles=[.mvn/checksums/checksums.sha256, .mvn/maven.config, pom.xml]
     warnings=[redirect_maven_dep_management_added]; child/pom.xml untouched
socket-patch vex --no-verify --product pkg:maven/com.example/root@1.0.0
  -> exit 0, not_affected for pkg:maven/org.apache.commons/commons-lang3@3.12.0
mvn 3.9.6 -B dependency:3.6.1:copy-dependencies   (fresh local repo; Socket repo mirrored to a stub)
  -> Downloaded from central-https: .../commons-lang3/3.12.0/commons-lang3-3.12.0.jar
     BUILD SUCCESS; child/target/dependency/commons-lang3-3.12.0.jar is PRISTINE

Control: the child omits <version> and the root manages 3.12.0 in <dependencyManagement>. Here the root literal is rewritten, and the child resolves commons-lang3-3.12.0-socket.4d5e6f70.jar (PATCHED) from the Socket repo. So the gap is specific to module-level literals, and to modules that do not inherit from the root.

Expected vs actual

  • Expected, one of:
    • (a) Refuse aggregator roots in hosted mode, like vendored mode does (<modules>, and Maven 4 <subprojects>).
    • (b) Walk the reactor (the <modules>/<subprojects> paths) and rewrite each module pom that declares the GA. This is what cargo hosted does for workspace members since Fix Cargo hosted workspace redirects #256.
    • At minimum: do not count a depMgmt-only pin as redirected when any module declares its own literal, and have VEX discovery refuse to attest a root pin that a module overrides.
  • Actual: silent fail-open with a positive VEX.

CLI revision

3efdc31d

Suggested fix

Option (b) mirrors the cargo workspace-member handling. Option (a) is a one-line parity fix using declares_modules (plus subprojects). Either way, VEX discovery (vex/discover/maven.rs) should also look at module poms, or refuse when the root is an aggregator.

File refs (at 3efdc31)

  • crates/socket-patch-cli/src/commands/scan/hosted.rs:70-81 (only root pom.xml / .mvn/* are read)
  • crates/socket-patch-core/src/patch/redirect/mod.rs:6143-6170 (a GA absent from the root becomes a depMgmt pin)
  • crates/socket-patch-core/src/vendor/maven_repo.rs:242,1109 (the vendored-side refusal hosted lacks)

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions