[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).
Summary
The hosted Maven rewriter (scan --mode hosted) finds the patched dependency with find_maven_dependency_matches, which compares the raw text of the first <groupId> / <artifactId> inside each <dependency> block. Two ordinary pom shapes defeat that match:
- A property coordinate:
<groupId>${ct.group}</groupId> (or <artifactId>${ct.artifact}</artifactId>) with the property defined in the same pom's <properties>.
<exclusions> written before the dependency's own <groupId>. Maven accepts any child order, and the first <groupId> in the block is then the exclusion's.
In both cases the direct <dependency> with its literal <version>1.10.0</version> isn't recognised. versioned is empty, so the rewriter takes the transitive branch and adds a <dependencyManagement> entry pinning 1.10.0-socket.<hex8>. Maven applies the managed version only when a direct dependency has none, so the direct literal 1.10.0 wins and Maven resolves Central's unpatched jar. The fail-closed suffix never takes effect.
socket-patch still reports success:
scan --mode hosted --json: exit 0, redirect.redirected: 1, with only the redirect_maven_dep_management_added warning, whose detail ("has no literal <version> in pom.xml") is false.
- The in-run
--vex writes a not_affected statement ("Patched via Socket patch … (redirected)").
- For the
${property} variants, a later standalone socket-patch vex on a fresh checkout also attests not_affected (exit 0). The VEX pom parser (formats/maven) resolves ${prop} only in <version>, not in groupId/artifactId, so it files the direct declaration under the GA ${ct.group}:commons-text and treats the managed suffixed pin as effective. For the exclusions-first variant the VEX parser handles the order correctly and refuses (patched_ref_invalid: "a direct <version> overrides <dependencyManagement>"), so only the scan envelope and the in-run VEX are wrong there.
Impact
The project builds the vulnerable upstream jar while the scan envelope, the in-run VEX and (for property coordinates) the standalone VEX all claim the CVE is mitigated. This silently defeats the "fail-closed" guarantee in docs/ecosystems.md.
Repro
Built from the real-Maven capstone crates/socket-patch-cli/tests/e2e_redirect_maven_build.rs, unchanged except that the warmed project's pom.xml is rewritten before scan --mode hosted (local, uncommitted variant). The checks: a fresh checkout with the fixture purged from the local repo, then maven-dependency-plugin:copy-dependencies through the stock settings.xml mirror for socket-patch-<uuid>, with the resolved jar's META-INF/NOTICE.txt checked for the patch marker.
Variant A (property groupId):
<properties>
<ct.group>org.apache.commons</ct.group>
</properties>
<dependencies>
<dependency>
<groupId>${ct.group}</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
</dependencies>
Variant B: the same with <artifactId>${ct.artifact}</artifactId>.
Variant C (exclusions first):
<dependency>
<exclusions>
<exclusion><groupId>org.example.none</groupId><artifactId>none</artifactId></exclusion>
</exclusions>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
The resulting pom after scan --mode hosted (variant A):
<dependencies>
<dependency>
<groupId>${ct.group}</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version> <!-- untouched -->
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0-socket.4d5e6f70</version> <!-- overridden by the direct literal -->
</dependency>
</dependencies>
</dependencyManagement>
Then mvn dependency:copy-dependencies resolves commons-text-1.10.0.jar from Central, without the patch marker.
Expected vs actual
- Expected (docs/ecosystems.md, "Fail-closed by version suffixing (hosted Maven)"; CLI_CONTRACT.md, the maven paragraph of the hosted rewriter): the rewriter "rewrites the literal
<version>", and adds a <dependencyManagement> entry only "for a transitive / managed dependency with no literal version in your pom". Either the direct literal is suffixed, or the dependency is refused or warned (like redirect_maven_dep_unpinned for a ${property} version) with redirected: 0 and no VEX statement.
- Actual: the direct literal is left alone, a shadowed dM pin is added, and the run reports
redirected: 1 plus a not_affected VEX statement while Maven builds the unpatched jar.
Matrix (Linux, JDK 21, main 045d7ec)
| Maven |
A: ${prop} groupId |
B: ${prop} artifactId |
C: exclusions before groupId |
literal control |
| 3.6.3 |
fail (scan + in-run VEX + standalone VEX attest) |
fail (same) |
fail (scan + in-run VEX attest; standalone VEX refuses) |
— |
| 3.9.11 |
fail ×2 |
fail |
fail |
pass (suffixed patched jar resolved) |
| 3.9.16 |
fail |
fail |
fail |
— |
| 4.0.0-rc-7 |
fail |
fail |
fail |
— |
On every failing cell the fresh resolve linked commons-text-1.10.0.jar (unpatched). macOS and Windows weren't probed: the defect is pure pom-text matching with no OS-dependent path. It wasn't bisected; the matching code is unchanged in the v4.0.0 release.
Suspect code
crates/socket-patch-core/src/patch/redirect/mod.rs:6078: find_maven_dependency_matches takes the first <groupId> / <artifactId> in the block (exclusions included) and compares raw text without resolving ${…}.
crates/socket-patch-core/src/patch/redirect/mod.rs:6346: if versioned.is_empty() assumes "no match with a literal version" means transitive or managed. A direct dependency the matcher can't identify falls into this branch.
crates/socket-patch-core/src/formats/maven/mod.rs:92-98 and crates/socket-patch-core/src/vex/discover/maven.rs:591: VEX resolves properties for <version> only, so the property-coordinate variants are attested by standalone vex too.
Related, not duplicates: #655 is the same symptom in the vendored reactor planner (maven_reactor.rs, keyed_declarations), a separate code path. #513 covers an unresolved ${prop} version in the reactor. #259 covers comment/profile/plugin markup in the hosted rewriter.
[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).
Summary
The hosted Maven rewriter (
scan --mode hosted) finds the patched dependency withfind_maven_dependency_matches, which compares the raw text of the first<groupId>/<artifactId>inside each<dependency>block. Two ordinary pom shapes defeat that match:<groupId>${ct.group}</groupId>(or<artifactId>${ct.artifact}</artifactId>) with the property defined in the same pom's<properties>.<exclusions>written before the dependency's own<groupId>. Maven accepts any child order, and the first<groupId>in the block is then the exclusion's.In both cases the direct
<dependency>with its literal<version>1.10.0</version>isn't recognised.versionedis empty, so the rewriter takes the transitive branch and adds a<dependencyManagement>entry pinning1.10.0-socket.<hex8>. Maven applies the managed version only when a direct dependency has none, so the direct literal1.10.0wins and Maven resolves Central's unpatched jar. The fail-closed suffix never takes effect.socket-patch still reports success:
scan --mode hosted --json: exit 0,redirect.redirected: 1, with only theredirect_maven_dep_management_addedwarning, whose detail ("has no literal<version>in pom.xml") is false.--vexwrites anot_affectedstatement ("Patched via Socket patch … (redirected)").${property}variants, a later standalonesocket-patch vexon a fresh checkout also attestsnot_affected(exit 0). The VEX pom parser (formats/maven) resolves${prop}only in<version>, not in groupId/artifactId, so it files the direct declaration under the GA${ct.group}:commons-textand treats the managed suffixed pin as effective. For the exclusions-first variant the VEX parser handles the order correctly and refuses (patched_ref_invalid: "a direct<version>overrides<dependencyManagement>"), so only the scan envelope and the in-run VEX are wrong there.Impact
The project builds the vulnerable upstream jar while the scan envelope, the in-run VEX and (for property coordinates) the standalone VEX all claim the CVE is mitigated. This silently defeats the "fail-closed" guarantee in docs/ecosystems.md.
Repro
Built from the real-Maven capstone
crates/socket-patch-cli/tests/e2e_redirect_maven_build.rs, unchanged except that the warmed project'spom.xmlis rewritten beforescan --mode hosted(local, uncommitted variant). The checks: a fresh checkout with the fixture purged from the local repo, thenmaven-dependency-plugin:copy-dependenciesthrough the stocksettings.xmlmirror forsocket-patch-<uuid>, with the resolved jar'sMETA-INF/NOTICE.txtchecked for the patch marker.Variant A (property groupId):
Variant B: the same with
<artifactId>${ct.artifact}</artifactId>.Variant C (exclusions first):
The resulting pom after
scan --mode hosted(variant A):Then
mvn dependency:copy-dependenciesresolvescommons-text-1.10.0.jarfrom Central, without the patch marker.Expected vs actual
<version>", and adds a<dependencyManagement>entry only "for a transitive / managed dependency with no literal version in your pom". Either the direct literal is suffixed, or the dependency is refused or warned (likeredirect_maven_dep_unpinnedfor a${property}version) withredirected: 0and no VEX statement.redirected: 1plus anot_affectedVEX statement while Maven builds the unpatched jar.Matrix (Linux, JDK 21, main
045d7ec)${prop}groupId${prop}artifactIdOn every failing cell the fresh resolve linked
commons-text-1.10.0.jar(unpatched). macOS and Windows weren't probed: the defect is pure pom-text matching with no OS-dependent path. It wasn't bisected; the matching code is unchanged in the v4.0.0 release.Suspect code
crates/socket-patch-core/src/patch/redirect/mod.rs:6078:find_maven_dependency_matchestakes the first<groupId>/<artifactId>in the block (exclusions included) and compares raw text without resolving${…}.crates/socket-patch-core/src/patch/redirect/mod.rs:6346:if versioned.is_empty()assumes "no match with a literal version" means transitive or managed. A direct dependency the matcher can't identify falls into this branch.crates/socket-patch-core/src/formats/maven/mod.rs:92-98andcrates/socket-patch-core/src/vex/discover/maven.rs:591: VEX resolves properties for<version>only, so the property-coordinate variants are attested by standalonevextoo.Related, not duplicates: #655 is the same symptom in the vendored reactor planner (
maven_reactor.rs,keyed_declarations), a separate code path. #513 covers an unresolved${prop}version in the reactor. #259 covers comment/profile/plugin markup in the hosted rewriter.