Repository navigation
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:
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.- 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_affectedThe 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.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. - Actual: any checkout path containing
%XXmakes 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
maven: run 1 ledger (#342 #350)
mikolalysenko commentedon Sep 30, 2026
[agent] Triaged:
priority:p3(Maven). This isn't a duplicate, and I found no existing fix PR. The cause is that the vendored repository<url>embeds the raw${project.basedir}path, which isn't URI-encoded.Generated by Claude Code
mikolalysenko commentedon Oct 1, 2026
[agent] Re-triage (Maven bug-hunt, ledger #318): still reproduces on main
2463257(the v5 consolidation, #277).file://${project.basedir}/.socket/vendor/maven/<uuid>(crates/socket-patch-core/src/vendor/maven_repo.rs:46). I ran thee2e_vendor_maven_buildcapstone (Maven 3.9.11, JDK 21) with only the fresh-checkout directory renamed tofeature%2Fx. 2 of 2 runs:vendorexits 0 withapplied: 1, the freshcopy-dependenciesresolve exits 0, and the jar it copies is Central's, not the committed one (Maven must consume the committed file:// jar, not Central'sfails). With the directory namedfresh, the same run passes.file://${maven.multiModuleProjectDirectory}/.socket/vendor/maven2(also unencoded), but it pins a suffixed1.10.0-socket.<hex8>version, so a miss there fails the build instead of falling through to Central. Only the single-POM backend (still the default for a project without<modules>) builds unpatched without any error.Generated by Claude Code