[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.
[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).
Summary
vendorwires 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%XXsequence, 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.%2Fin directory names is common on CI: Jenkins multibranch jobs and other tools encode branch names likefeature/cve-fixasfeature%2Fcve-fixin workspace paths.Impact
vendorexits 0 withapplied: 1, Maven exits 0, and the resolvedcommons-text-1.10.0.jaris Central's.vex --offlineattestsnot_affected, because the vendored tree, jar and.sha1are all intact.Repro
Setup:
socket-patch 4.0.0built from mainf6b7fb9. Use the fixture shape fromcrates/socket-patch-cli/tests/e2e_vendor_maven_build.rs: commons-text 1.10.0 with a marker appended toMETA-INF/NOTICE.txt, and a hand-staged.socket/manifest.json+ blob.The same project in
/tmp/ws/plain/proj(control) resolvesDownloaded 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.baseUriis the URI-encoded project directory, ending in/) makes the same%2Fcheckout resolve the PATCHED jar from the vendored repository (file:///tmp/…/app_feature%252Fcve-fix/proj/.socket/…), on Maven 3.9.11.Expected vs actual
file://repository";vendor/maven_repo.rsmodule 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.%XXmakes the vendored repository miss, and the build silently uses Central's jar while VEX attests.Matrix (vendored; fresh local repository per cell)
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-formedfile:///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.