[agent] Found by the scheduled Go modules bug-hunt routine (ledger #317).
Summary
After an agent-mode apply of a Go patch for M@v1.0.0, go.mod gets replace M v1.0.0 => ./.socket/go-patches/M@v1.0.0. If a developer or Dependabot then runs go get M@v1.0.1 (or any other version), go.mod requires M v1.0.1. The version-pinned replace then no longer matches, and go build links the unpatched upstream module.
apply --check catches this correctly. It exits 1 with ResolvedVersionMismatch: "patched version v1.0.0 is not the required version (go.mod requires v1.0.1) — go would link the UNPATCHED module". But socket-patch vex still exits 0 and writes a not_affected statement for the patch's CVE.
Impact
The OpenVEX document tells scanners the product is not affected by a CVE whose vulnerable code is compiled into the binary. This is the one failure VEX must never have. Upgrade drift is routine: Dependabot and Renovate bump go.mod versions all the time.
Repro (Linux, go 1.24.7, hermetic file GOPROXY)
The fixture has the same shape as crates/socket-patch-cli/tests/e2e_golang_build.rs: module example.com/upstream with versions v1.0.0 and v1.0.1, both containing Greeting() = "PRISTINE", plus a hand-staged .socket/manifest.json and blob that patch lib.go in v1.0.0 to "PATCHED", with setup.manual: ["golang"].
export GOPROXY=file://$T/proxy GOMODCACHE=$T/modcache GOSUMDB=off GOFLAGS=-mod=mod GOTOOLCHAIN=local
cd consumer # go.mod: require example.com/upstream v1.0.0
socket-patch apply # exit 0; replace example.com/upstream v1.0.0 => ./.socket/go-patches/...
go run . # OUT: PATCHED
go get example.com/upstream@v1.0.1
go run . # OUT: PRISTINE <- unpatched code linked
socket-patch apply --check # exit 1: "patched version v1.0.0 is not the required version (go.mod requires v1.0.1)"
socket-patch vex --output v.json # exit 0, 1 statement
grep status v.json # "status": "not_affected"
It reproduced 3 times on main in fresh fixtures: with go get @v1.0.1, go get @v1.1.0, and with go build as the oracle instead of go run.
Expected vs actual
- Expected: README ("socket-patch vex", step 2): "re-checks each patch's bytes so the attestation only covers patches that are actually applied". CLI_CONTRACT.md property 7 also requires on-disk verification. A go-patches replace whose LHS version no longer matches the required version is inert, so vex should omit the patch with a warning, the same way
apply --check already reports the drift.
- Actual: vex exit 0,
not_affected.
For comparison, vendored mode handles the same drift correctly: after vendor plus go get M@v1.0.1, vex omits the patch with vendor_unwired.
OS × version
| OS |
go |
socket-patch |
reproduces |
| Linux |
1.24.7 |
main f6b7fb9 |
yes (3×) |
| Linux |
1.24.7 |
4.0.0 release |
yes (needs --product, since 4.0.0 doesn't auto-detect it here) |
The defect is in pure go.mod parsing logic, so it doesn't depend on the OS. It isn't a regression: 4.0.0 behaves the same way, and 3.3.0 doesn't accept the hand-staged manifest shape.
Suspect code
crates/socket-patch-cli/src/commands/vex.rs:1333 synthesize_go_patches: for each socket-owned .socket/go-patches replace, it builds the PURL from the replace's LHS version (line 1351) and records the copy dir for dir-hash verification. It never compares that version with the go.mod require (or the selected build-list version). The copy dir still hashes as patched, so verification passes. The drift check already exists as Drift::ResolvedVersionMismatch in crates/socket-patch-core/src/patch/redirect/golang_local.rs:115, but vex doesn't use it.
[agent] Found by the scheduled Go modules bug-hunt routine (ledger #317).
Summary
After an agent-mode
applyof a Go patch forM@v1.0.0, go.mod getsreplace M v1.0.0 => ./.socket/go-patches/M@v1.0.0. If a developer or Dependabot then runsgo get M@v1.0.1(or any other version), go.mod requiresM v1.0.1. The version-pinned replace then no longer matches, andgo buildlinks the unpatched upstream module.apply --checkcatches this correctly. It exits 1 withResolvedVersionMismatch: "patched version v1.0.0 is not the required version (go.mod requires v1.0.1) — go would link the UNPATCHED module". Butsocket-patch vexstill exits 0 and writes anot_affectedstatement for the patch's CVE.Impact
The OpenVEX document tells scanners the product is not affected by a CVE whose vulnerable code is compiled into the binary. This is the one failure VEX must never have. Upgrade drift is routine: Dependabot and Renovate bump go.mod versions all the time.
Repro (Linux, go 1.24.7, hermetic file GOPROXY)
The fixture has the same shape as
crates/socket-patch-cli/tests/e2e_golang_build.rs: moduleexample.com/upstreamwith versions v1.0.0 and v1.0.1, both containingGreeting() = "PRISTINE", plus a hand-staged.socket/manifest.jsonand blob that patchlib.goin v1.0.0 to"PATCHED", withsetup.manual: ["golang"].It reproduced 3 times on main in fresh fixtures: with
go get @v1.0.1,go get @v1.1.0, and withgo buildas the oracle instead ofgo run.Expected vs actual
apply --checkalready reports the drift.not_affected.For comparison, vendored mode handles the same drift correctly: after
vendorplusgo get M@v1.0.1, vex omits the patch withvendor_unwired.OS × version
f6b7fb9--product, since 4.0.0 doesn't auto-detect it here)The defect is in pure go.mod parsing logic, so it doesn't depend on the OS. It isn't a regression: 4.0.0 behaves the same way, and 3.3.0 doesn't accept the hand-staged manifest shape.
Suspect code
crates/socket-patch-cli/src/commands/vex.rs:1333synthesize_go_patches: for each socket-owned.socket/go-patchesreplace, it builds the PURL from the replace's LHS version (line 1351) and records the copy dir for dir-hash verification. It never compares that version with the go.modrequire(or the selected build-list version). The copy dir still hashes as patched, so verification passes. The drift check already exists asDrift::ResolvedVersionMismatchincrates/socket-patch-core/src/patch/redirect/golang_local.rs:115, but vex doesn't use it.