Skip to content

Vendored Maven silently resolves the unpatched jar when an earlier <repository> or a mirrorOf * mirror serves the same GAV, and VEX still attests #263

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 mode wires socket-patch-vendor-<uuid> (file://${project.basedir}/.socket/vendor/maven/<uuid>) and appends it last in <repositories>. The vendored GAV is the original upstream GAV, so it has no suffix. As a result, any repository Maven consults first that also has the GAV wins, and the pristine jar is used.

This happens with:

  • An earlier project <repository>, for example the company's Central proxy declared in the pom. Maven tries declared repositories in order.
  • A settings.xml <mirror><mirrorOf>*</mirrorOf>. * also captures file:// repositories, so the vendored repo is rerouted to the mirror. (external:* does not capture them, and the vendored repo is honored.) docs/ecosystems.md documents mirrorOf * only for hosted mode, where it fails loudly. In vendored mode it fails open.

In both cases, vendor succeeds and socket-patch vex --offline attests not_affected, because the vendored tree, the jar and its .sha1 are all intact.

Impact

The project is silently unpatched with a positive VEX. Both shapes are common in enterprise builds.

This is the same failure class as the documented warm-~/.m2 shadow, but there is no warning for it. The always-on vendor_maven_local_cache_shadow covers only the local repository.

Repro

The patch is commons-lang3 3.12.0 with a patched META-INF/NOTICE.txt, set up with a hand-staged .socket/manifest.json + blob and then vendor --json --offline, as in e2e_vendor_maven_build. After that:

  • The manifest and blobs are removed (fresh checkout).
  • Maven runs dependency:3.6.1:copy-dependencies with a fresh local repository.

Pom with a pre-existing repository:

<repositories>
  <repository><id>corp-proxy</id><url>https://repo.maven.apache.org/maven2</url></repository>
</repositories>

After vendor, the repository order is corp-proxy, then socket-patch-vendor-4d5e6f70-….

case Maven 3.9.6 Maven 4.0.0-rc-7
control (no other repos, plain settings) PATCHED (from socket-patch-vendor-…) PATCHED
corp-proxy declared before PRISTINE (Downloaded from corp-proxy) PRISTINE
control + settings <mirrorOf>*</mirrorOf> PRISTINE (Downloaded from corp-mirror) PRISTINE
control + settings <mirrorOf>external:*</mirrorOf> PATCHED PATCHED

vex --offline --product pkg:maven/com.example/app@1.0.0 in the corp-proxy case gives exit 0 and not_affected for pkg:maven/org.apache.commons/commons-lang3@3.12.0.

Expected vs actual

  • Expected:
    • Insert the vendored repository first, as hosted mode does, using the same comment/profile-safe anchor.
    • Warn when the pom or the user's settings.xml has a mirrorOf of *, or a pattern matching socket-patch-vendor-*. Document *,!socket-patch-vendor-*.
    • VEX discovery should refuse to attest a vendored ref that is not the first repository.
  • Actual: appended last, no warning, positive VEX.

CLI revision

3efdc31d

Suggested fix

  • In build_repo_edit, anchor on the first wireable <repositories> open tag and insert right after it.
  • Extend the vendor_maven_local_cache_shadow family with a vendor_maven_repository_shadow warning when other repositories exist.
  • Add a docs note for mirrorOf * under vendored.

File refs (at 3efdc31)

  • crates/socket-patch-core/src/vendor/maven_repo.rs:968-985 (build_repo_edit anchors on </repositories>, i.e. appends)
  • crates/socket-patch-core/src/vendor/maven_repo.rs:1089-1103 (repository_block)
  • docs/ecosystems.md:171-186 (warm-cache note; mirrorOf note is hosted-only)

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