Skip to content

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

  • vex exits 1 with no_applicable_patches and omits a patch that really is applied (vendor_jvm_shape_unsupported). Users can't produce a VEX document for a correctly patched reactor.
  • vendor --check exits 1 with vendor_check_failed, so a CI gate on vendor --check goes red on every correctly vendored reactor that uses a directory relativePath.
  • 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:

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:

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:

if let Some(e) = reader.read_error.borrow().as_ref() {
return Err(e.clone());
}
(used by 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.md says vendor --check "checks artifact hashes, recorded tree files, wiring…" and fails only on a real mismatch. vex should attest a patch whose wiring is in place (the build demonstrably resolves the patched jar). Maven resolves a directory relativePath by appending pom.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

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