Repository navigation
Agent-mode Go vex attests not_affected after go get upgrades the patched module, although the go-patches replace no longer applies and the build links the unpatched code #391
Description
[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 --checkalready 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.
Backlog review — 2026-10-08
Priority: P2 → P1. After a Go dependency upgrade the patch no longer applies, yet VEX still attests safety. Retain urgently as an attestation validity defect.
Activity
mikolalysenko commentedon Sep 30, 2026
[agent] Triaged as priority:p2 (Go). Shares root cause with #392: agent-mode Go never checks that its version-pinned go.mod
replacematches the version the build graph selects (verify_go_redirect_stateis only run byapply --check, and skips the cross-check for pre-1.17 go.mod;vexnever runs it). Will be fixed together. See also #393 (a go.work replace overriding it).Generated by Claude Code
mikolalysenko commentedon Oct 1, 2026
[agent] Re-triage on main
2463257(v5 consolidation, #277): still reproduces. Afterapply, I rango get example.com/upstream@v1.0.1.go run .then printsPRISTINE101, andapply --checkexits 1 ("go would link the UNPATCHED module"). Butvex --product …still exits 0 withnot_affected.The new Go notes in docs/ecosystems.md say "Updating the
requirecan leave it unused; re-scan and regenerate VEX after dependency updates." That assumes a regenerated VEX is correct, and it isn't: a VEX generated after the upgrade is exactly what's wrong here.Generated by Claude Code
mikolalysenko commentedon Oct 1, 2026
[agent] Shares root cause with #509 (and #391/#392): no Go mode determines the build-list-selected version before treating a version-pinned
replaceas live; #509 is the hosted gate's variant (GoSum::has_module_versioncounts a/go.mod-only go.sum line). Will be fixed together.Generated by Claude Code
mikolalysenko commentedon Oct 3, 2026
[agent] Re-triage from the Go modules bug-hunt routine (ledger #317): this still reproduces on main
045d7ec, and it now also reproduces on go 1.21.13 and 1.26.8 as well as 1.24.7. On every toolchain,go get example.com/upstream@v1.0.1afterapplybuilds PRISTINE,apply --checkexits 1 (drift detected), andvexstill exits 0 and writes"status": "not_affected".Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 triage: P2, not a release blocker. Retain Go agent attestation after a later dependency upgrade at P2; do not require a full agent-state lifecycle expansion to ship the lockfile workflow.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commentedon Oct 11, 2026
[agent] Claiming this issue (with #392, #509; shared root cause: no Go path determines the build-list-selected version before treating a version-pinned
replaceas live; each uses its own go.mod/go.sum heuristic). Branch: agent/fix-go-selected-version-gate. Claim-ID: 2026-10-11T22:21:19Z-e670efGenerated by Claude Code
mikolalysenko commentedon Oct 11, 2026
[agent] Draft PR: #1403
Generated by Claude Code
Gate Go replaces on the build-selected version