Repository navigation
Agent-mode Go vex omits an applied patch for a +incompatible module (not_applied, exit 1) because it looks up the manifest with a literal "+" while the API key spells it %2B #484
Description
[agent] Found by the scheduled Go modules bug-hunt routine (ledger #317).
Summary
The patch API sends +incompatible Go versions percent-encoded, as pkg:golang/M@v2.0.0%2Bincompatible. This is stated in crates/socket-patch-core/src/utils/purl.rs:284 and golang_local.rs:1898. Agent-mode apply handles that key correctly: it writes replace M v2.0.0+incompatible => ./.socket/go-patches/M@v2.0.0+incompatible, and go run prints PATCHED. apply --check reports the redirect in sync.
socket-patch vex then gets it wrong. It rebuilds the PURL from the go.mod replace with a literal + and looks it up in the manifest by exact key. That lookup misses the %2B key, so vex never finds the go-patches redirect. It falls back to verifying the pristine module cache, which still holds the original bytes, so it reports not_applied and exits 1 with no_applicable_patches.
Impact
Every agent-mode patch for a +incompatible Go module, a common shape for pre-modules v2+ repos, can't be attested. CI that runs socket-patch vex fails, and if the project has other patches, this one is silently left out of the OpenVEX document even though it is applied and built. This fails closed, so there's no false attestation, but the patch is invisible to scanners.
Repro (Linux, go 1.24.7, hermetic file GOPROXY)
The fixture follows tests/e2e_golang_build.rs. A legacy module example.com/legacy at v2.0.0+incompatible has no go.mod in its zip (go synthesizes one). There's a hand-staged .socket/manifest.json keyed pkg:golang/example.com/legacy@v2.0.0%2Bincompatible plus a blob that patches lib.go, 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/legacy v2.0.0+incompatible
socket-patch apply # exit 0: "Patched packages: pkg:golang/example.com/legacy@v2.0.0+incompatible"
grep replace go.mod # replace example.com/legacy v2.0.0+incompatible => ./.socket/go-patches/example.com/legacy@v2.0.0+incompatible
go run . # OUT: PATCHED
go mod verify # all modules verified
socket-patch apply --check # exit 0: "Patch redirects are in sync (1 redirect checked)."
socket-patch vex --product pkg:golang/example.com/consumer --output v.json
# Warning: omitting pkg:golang/example.com/legacy@v2.0.0%2Bincompatible from VEX: the patched files still hold the original content (not_applied)
# Error: No applied patches with vulnerability metadata to attest. (exit 1)--json gives events[0].errorCode: "not_applied" and error.code: "no_applicable_patches".
There are two controls in the same fixture. If the manifest key is changed to a literal + (@v2.0.0+incompatible), vex exits 0 with not_affected. A no-go.mod module at plain v1.0.0 also exits 0 with not_affected. So only the %2B spelling, which is the one the API serves, fails. rollback with the %2B key works: it drops the replace and the build goes back to PRISTINE.
It reproduced twice on main 6e7ef74 in fresh fixtures, once in human and once in --json output.
Expected vs actual
- Expected: README "socket-patch vex": the attestation covers "patches that are actually applied". CLI_CONTRACT.md property 7 (on-disk verification). The go-patches copy is applied and linked, as
go runandapply --checkboth show, so vex should verify the copy dir and emitnot_affected. - Actual: exit 1,
not_applied, and the patch is omitted.
OS × version
| OS | go | socket-patch | result |
|---|---|---|---|
| Linux | 1.24.7 | main 6e7ef74 |
reproduces (2×) |
| Linux | 1.24.7 | 4.0.0 release | n/a: apply itself exits 1 ("No packages found that match available patches") on the %2B key, so vex never gets this far |
This isn't a regression. #252 (872b591) fixed the crawler and the redirect for %2B, but the vex synthesis path wasn't updated. The bug is in pure manifest-key logic, so it doesn't depend on the OS.
Suspect code
crates/socket-patch-cli/src/commands/vex.rs:1182-1183 (synthesize_go_patches):
let purl = build_golang_purl(&entry.module, version); // "...@v2.0.0+incompatible"
if !manifest.patches.contains_key(&purl) { // manifest key is "...@v2.0.0%2Bincompatible"
continue;
}The lookup should compare decoded coordinates, the way golang_local.rs does since #252, or try both spellings. The lookup_entry(entries, &purl) check on the next line probably has the same exposure for a vendor takeover of a +incompatible redirect.
Activity
go: run 5 ledger (filed #484)
mikolalysenko commentedon Oct 1, 2026
[agent] Triaged as priority:p2 (Go). Confirmed on main:
synthesize_go_patchesincrates/socket-patch-cli/src/commands/vex.rsbuilds the PURL withbuild_golang_purl(literal+), then looks it up with an exactmanifest.patches.contains_key(&purl). That misses the%2Bkey the API serves. The lookup needs the same percent-decoded comparison the crawler and redirect got in #252. No open or merged PR covers this.Generated by Claude Code
mikolalysenko commentedon Oct 1, 2026
[agent] Scope narrowed: this is agent-mode only. Checked 2× on main
61cfb9b(Linux, go 1.24.7) with a local mock vendoring service (thevendor/golang.rsmount_go_grantedshape: a granted tarball withsha512integrity). Agent mode still reproduces on61cfb9b.vendor --vendor-source=service,%2Bincompatiblemanifest key, no-go.mod module: exit 0. It writesreplace example.com/legacy v2.0.0+incompatible => ./.socket/vendor/golang/<uuid>/example.com/legacy@v2.0.0+incompatible,go runprints PATCHED,go mod verifyOK, andvexexits 0 withnot_affected. The literal-+key gives the same result.applywith the%2Bkey, thenvendor --vendor-source=service): the takeover warning is printed, the build is PATCHED, andvexexits 0 withnot_affected.So the
lookup_entry(entries, &purl)exposure I suspected in the report doesn't show up in practice. The fix only needs themanifest.patches.contains_key(&purl)lookup atvex.rs:1182-1183.Generated by Claude Code
mikolalysenko commentedon Oct 12, 2026
[agent] Claiming this issue (shared root cause: Go vex compares the literal-
+purl from go.mod against the%2Bmanifest key by exact string). Branch: agent/fix-go-vex-incompatible-purl-key. Claim-ID: 2026-10-12T09:20:41Z-3ee419Generated by Claude Code
mikolalysenko commentedon Oct 12, 2026
[agent] Fix in progress: #1427
Generated by Claude Code
Fix vex of +incompatible Go module patches