Repository navigation
vex and vendor --check refuse a correctly vendored Maven reactor whose <parent><relativePath> names a directory (../corp-parent or ..), with "is not a regular file" / "unsafe path" #534
Description
[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).
Summary
vendor patches a Maven reactor correctly, and the build resolves the vendored 1.10.0-socket.* jar. But when any module's <parent><relativePath> names a directory rather than a pom.xml file, socket-patch vex and socket-patch vendor --check then refuse it:
<relativePath>../corp-parent</relativePath>→cannot establish JVM wiring: corp-parent: <proj>/corp-parent is not a regular file<relativePath>..</relativePath>→cannot establish JVM wiring: unsafe path ""
Maven accepts both forms; it appends pom.xml to a directory. The repo's own reactor capstone uses the first one (B_POM in crates/socket-patch-cli/tests/e2e_vendor_jvm_build.rs), but the capstone never runs vex or vendor --check.
Impact
vexexits 1 withno_applicable_patchesand omits a patch that really is applied (vendor_jvm_shape_unsupported). Users can't produce a VEX document for a correctly patched reactor.vendor --checkexits 1 withvendor_check_failed, so a CI gate onvendor --checkgoes red on every correctly vendored reactor that uses a directoryrelativePath.- It fails closed (no false attestation), but it hits a common, valid layout.
<relativePath>..</relativePath>and<relativePath>../parent</relativePath>are widespread.
Root cause (suspect)
local_parent_of probes the bare relativePath before <path>/pom.xml:
socket-patch/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs
Lines 1164 to 1174 in 61cfb9b
| let candidates = [ | |
| path.clone(), | |
| if path.is_empty() { | |
| "pom.xml".to_string() | |
| } else { | |
| format!("{path}/pom.xml") | |
| }, | |
| ]; | |
| let Some(parent_rel) = candidates | |
| .into_iter() | |
| .find(|c| poms.contains_key(c) || read(c).is_some()) |
During vendor, a directory read just counts as "missing", and the pom.xml candidate wins. During vex / vendor --check, the read goes through ProjectReader::read, which records any error other than NotFound (InvalidInput for a directory, unsafe path "" for the root) in read_error:
socket-patch/crates/socket-patch-core/src/vendor/jvm/apply.rs
Lines 164 to 181 in 61cfb9b
| pub fn read(&self, rel: &str) -> Option<Vec<u8>> { | |
| let path = match self.resolve(rel) { | |
| Ok(path) => path, | |
| Err(e) => { | |
| self.read_error.borrow_mut().get_or_insert(e); | |
| return None; | |
| } | |
| }; | |
| match group_commit::read(&path).unwrap_or_else(|| read_regular_to_bytes_sync(&path)) { | |
| Ok(bytes) => Some(bytes), | |
| Err(e) if e.kind() == std::io::ErrorKind::NotFound => None, | |
| Err(e) => { | |
| self.read_error | |
| .borrow_mut() | |
| .get_or_insert(format!("{rel}: {e}")); | |
| None | |
| } | |
| } |
entry_wired_checked then turns any recorded read error into a hard failure, even though the planner went on to find the parent through the next candidate:
socket-patch/crates/socket-patch-core/src/vendor/jvm/apply.rs
Lines 700 to 702 in 61cfb9b
| if let Some(e) = reader.read_error.borrow().as_ref() { | |
| return Err(e.clone()); | |
| } |
vex at crates/socket-patch-cli/src/commands/vex_sources.rs:347 and by check_entry at apply.rs:770).
Repro
The stock reactor fixture from e2e_vendor_jvm_build::maven_reactor (aggregator root, corp-parent/, module a with commons-text:1.10.0 at <relativePath>../corp-parent/pom.xml</relativePath>, module b with <relativePath>../corp-parent</relativePath>), plus a staged patch manifest, the same as the capstone:
socket-patch vendor --json --offline # applied: 1
mvn -B package org.apache.maven.plugins:maven-dependency-plugin:3.5.0:build-classpath -Dmdep.outputFile=target/cp.txt
# a/target/cp.txt and b/target/cp.txt → .socket/vendor/maven2/.../commons-text-1.10.0-socket.1d3c1fd2.jar (patched)
socket-patch vex --json --offline --output vex.json
# exit 1, error.code no_applicable_patches, warning vendor_jvm_shape_unsupported:
# "cannot establish JVM wiring: corp-parent: <proj>/corp-parent is not a regular file"
socket-patch vendor --check --json --offline
# exit 1, event failed / vendor_check_failed: "corp-parent: <proj>/corp-parent is not a regular file"Control: change only b's <relativePath> to ../corp-parent/pom.xml. Then vex exits 0 with not_affected, and vendor --check exits 0.
.. variant: module a inherits the root aggregator with <relativePath>..</relativePath> (b uses the pom.xml form). Then both vex and vendor --check fail with unsafe path "".
Expected vs actual
- Expected:
docs/design/maven-vendoring.mdsaysvendor --check"checks artifact hashes, recorded tree files, wiring…" and fails only on a real mismatch.vexshould attest a patch whose wiring is in place (the build demonstrably resolves the patched jar). Maven resolves a directoryrelativePathby appendingpom.xml, as the planner itself does when vendoring. - Actual: both commands fail with a filesystem error on the probe of the bare directory, which the planner then resolves anyway.
Matrix (Linux, JDK 21, main 61cfb9b)
| Maven | ../corp-parent (dir) |
.. |
../corp-parent/pom.xml (control) |
|---|---|---|---|
| 3.6.3 | fail (vex 1, check 1) | untested | — |
| 3.8.8 | fail (vex 1, check 1) | untested | — |
| 3.9.11 | fail (reproduced 2×) | fail (unsafe path "") |
pass (vex not_affected, check 0) |
| 4.0.0-rc-7 | fail (vex 1, check 1) | untested | — |
The defect is in path probing, not in Maven itself: the build resolves the patched jar on every line, so the Maven version only matters for the build oracle. macOS and Windows were not probed. The code path is OS-independent apart from the error kind for a directory read, which on Windows is PermissionDenied, not NotFound, so it's expected to fail the same way.
First bad commit
2463257 (#277, the v5 consolidation, which introduced ProjectReader::read_error and the JVM entry_wired_checked). It isn't in any release yet (latest release 4.0.0).
Activity
maven: run 7 ledger (#534, #535)
mikolalysenko commentedon Oct 2, 2026
[agent] Triaged as
priority:p3(Maven). Not a duplicate, and no PR fixes it yet.Suspect boundary: the jvm vendor file reader in
vendor/jvm/apply.rs(:162–:192,:316) accepts regular files only. Maven also accepts a<relativePath>that names a directory, which it resolves to<dir>/pom.xml, so that path needs normalizing before the read.Generated by Claude Code