Skip to content

Vendored Maven silently builds the unpatched jar when the project path contains a %XX sequence (e.g. a Jenkins "feature%2Fx" workspace), because file://${project.basedir} is not URI-encoded #350

Description

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

Summary

vendor wires the vendored repository as
<url>file://${project.basedir}/.socket/vendor/maven/<uuid></url>. ${project.basedir} is a raw filesystem path, but Maven treats the <url> as a URI and percent-decodes it. When the checkout path contains a %XX sequence, the decoded path doesn't exist, the vendored repository serves nothing, and Maven falls through to Central for the same GAV. The build succeeds with the pristine jar.

%2F in directory names is common on CI: Jenkins multibranch jobs and other tools encode branch names like feature/cve-fix as feature%2Fcve-fix in workspace paths.

Impact

  • Fail-open with no signal: vendor exits 0 with applied: 1, Maven exits 0, and the resolved commons-text-1.10.0.jar is Central's.
  • vex --offline attests not_affected, because the vendored tree, jar and .sha1 are all intact.
  • It reproduces identically on Linux, macOS and Windows, and on Maven 3.6.3, 3.8.8, 3.9.11 and 4.0.0-rc-7.

Repro

Setup: socket-patch 4.0.0 built from main f6b7fb9. Use the fixture shape from crates/socket-patch-cli/tests/e2e_vendor_maven_build.rs: commons-text 1.10.0 with a marker appended to META-INF/NOTICE.txt, and a hand-staged .socket/manifest.json + blob.

mkdir -p '/tmp/ws/app_feature%2Fcve-fix/proj' && cd '/tmp/ws/app_feature%2Fcve-fix/proj'
cat > pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId><artifactId>app</artifactId><version>1.0.0</version>
<dependencies><dependency><groupId>org.apache.commons</groupId><artifactId>commons-text</artifactId><version>1.10.0</version></dependency></dependencies>
</project>
EOF
# stage .socket/manifest.json + blob for pkg:maven/org.apache.commons/commons-text@1.10.0
socket-patch vendor --json --offline            # exit 0, applied 1
rm -rf .socket/manifest.json .socket/blobs      # fresh checkout
mvn -B -s settings-with-fresh-localRepository.xml \
  org.apache.maven.plugins:maven-dependency-plugin:3.1.2:copy-dependencies
# [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-text/1.10.0/commons-text-1.10.0.jar
# BUILD SUCCESS; target/dependency/commons-text-1.10.0.jar has NO marker (pristine)
socket-patch vex --offline --product pkg:maven/com.example/app@1.0.0 -O vex.json
# exit 0, not_affected

The same project in /tmp/ws/plain/proj (control) resolves Downloaded from socket-patch-vendor-<uuid>: file:///… and the jar carries the marker. A path with a space, or with unicode (under a UTF-8 locale), also works. Only the % sequence breaks.

Fix check: replacing the url with ${project.baseUri}.socket/vendor/maven/<uuid> (project.baseUri is the URI-encoded project directory, ending in /) makes the same %2F checkout resolve the PATCHED jar from the vendored repository (file:///tmp/…/app_feature%252Fcve-fix/proj/.socket/…), on Maven 3.9.11.

Expected vs actual

  • Expected (docs/ecosystems.md, Maven row: "committed maven2 file:// repository"; vendor/maven_repo.rs module doc: "${project.basedir} interpolates to the pom's own dir, so the file:// url resolves relative to the committed tree on any checkout"): the vendored repository resolves from any checkout path.
  • Actual: any checkout path containing %XX makes the vendored repository miss, and the build silently uses Central's jar while VEX attests.

Matrix (vendored; fresh local repository per cell)

OS Maven 3.6.3 3.8.8 3.9.11 4.0.0-rc-7
Linux (sandbox, JDK 21) PRISTINE, exit 0 PRISTINE, exit 0 PRISTINE, exit 0 PRISTINE, exit 0
Linux (ubuntu-latest probe) PRISTINE — PRISTINE PRISTINE
macOS (macos-latest probe) PRISTINE — PRISTINE PRISTINE
Windows (windows-latest probe) PRISTINE — PRISTINE PRISTINE
control (plain / space path), all of the above PATCHED PATCHED PATCHED PATCHED

Probe runs: https://github.com/SocketDev/socket-patch/actions/runs/36752468964 (all three OSes) and https://github.com/SocketDev/socket-patch/actions/runs/36750538747 (Linux + macOS). Side note from the Windows probe: file://D:\a\_temp\…\proj/.socket/… is what Maven resolves there today. It works, but ${project.baseUri} would also give a well-formed file:///D:/… URI.

Suspect code (main f6b7fb9)

  • crates/socket-patch-core/src/vendor/maven_repo.rs:104: VENDOR_REPO_URL_PREFIX = "file://${project.basedir}/"
  • crates/socket-patch-core/src/vex/discover/maven.rs:527: VEX discovery requires exactly that prefix, so the two would need to change together (and accept the old form for already-vendored trees).

Related, but a different trigger: #263 (an earlier repository or a mirrorOf * mirror shadows the vendored repo) and #272 (lowercased GAV path). All three share the "vendored repo misses, so Central wins silently" failure mode.

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