Go apply, apply --check and vex treat a module that is only another module's replace target as built, so the patch is reported applied and attested while go builds the unpatched code

Cerrado
#1,823 3 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@mikolalysenko ya está trabajando en esto.

Desde el 29/10/2026.

  • #1824 de @mikolalysenko — abierto

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
48/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
go, rust
Área
backend, devtools

Línea de trabajo

Start with crates/socket-patch-core/src/vendor/go_build_list.rs, especially the selection logic around lines 90–99, then trace the callers in patch/redirect/golang_local.rs and vex/discover/golang.rs. Reproduce the reported case with a module named only as a replace target and compare apply, apply --check, and VEX results with workspace mode. Done means those paths refuse or otherwise avoid claiming the patch is active when Go builds the unpatched target.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

agent:claimed agent:triaged bug bughunt pm:go priority:p2

[agent] Found by the scheduled Go modules bug-hunt routine (ledger #317).

Summary

A common Go fork idiom is replace other.com/fork v1.0.0 => example.com/upstream v1.0.0 (or the versionless replace other.com/fork => example.com/upstream v1.0.0). go downloads example.com/[email protected] into the module cache and records its zip and /go.mod lines in go.sum under the target's path, but example.com/upstream is not a module of the build: go list -m all shows only other.com/fork v1.0.0 => example.com/upstream v1.0.0. go doesn't chain replaces, so a replace of example.com/upstream is ignored.

In module mode (no go.work), with a patch for pkg:golang/example.com/[email protected], socket-patch:

  • apply writes replace example.com/upstream v1.0.0 => ./.socket/go-patches/example.com/[email protected] and exits 0 ("1 of 1 targeted patch applied"),
  • apply --check says "Patches are in sync" (exit 0),
  • vex emits not_affected / inline_mitigations_already_exist (exit 0),

while go run / go build -mod=readonly link the unpatched code.

Impact

A false VEX attestation for a vulnerable dependency, and a green apply --check in CI. Replacing a module with a fork is common (abandoned upstreams, security forks), and the fork's code is exactly what Socket would patch.

Repro (Linux, go 1.24.7, hermetic file GOPROXY serving example.com/upstream v1.0.0 whose lib.go returns "PRISTINE"; hand-staged .socket/manifest.json + blob that makes it return "PATCHED", as in e2e_golang_build.rs)

mkdir app && cd app
cat > go.mod <<'EOF'
module example.com/app

go 1.21

require other.com/fork v1.0.0

replace other.com/fork v1.0.0 => example.com/upstream v1.0.0
EOF
cat > main.go <<'EOF'
package main

import (
	"fmt"
	u "other.com/fork"
)

func main() { fmt.Println("app:", u.Greeting()) }
EOF
go mod tidy                      # go.sum: example.com/upstream v1.0.0 h1:… and /go.mod h1:…
# stage .socket/manifest.json + blob for pkg:golang/example.com/[email protected]
socket-patch apply --offline --ecosystems golang   # exit 0, 1 of 1 applied; go.mod gains the replace
go list -m all                   # example.com/app / other.com/fork v1.0.0 => example.com/upstream v1.0.0
GOFLAGS=-mod=readonly go run .   # app: PRISTINE
socket-patch apply --check --ecosystems golang     # exit 0, "Patches are in sync"
socket-patch vex --product pkg:golang/example.com/app   # status not_affected, exit 0

Expected vs actual

docs/ecosystems.md, "Go: directory replaces and go.sum": "A replace names an exact module version, so it only works while the build selects that version. socket-patch reads which version that is from go.mod and go.sum … the require line in a go 1.17+ module … apply and scan --mode hosted refuse to write a replace for any other version." Here no require names example.com/upstream at all. Expected: apply refuses (as it does with "is not in this project's build graph … nothing written" when there's no zip line), apply --check fails, and vex leaves the patch out. Alternatively, patch it through the replace that actually selects the code (other.com/fork v1.0.0 => ./.socket/go-patches/…). Actual: all three report success while the build is unpatched. CLI_CONTRACT: vex must not attest a patch that isn't in effect.

Workspace mode is correct: the same module as a go.work member (apply from the member or the root) refuses with "not in this project's build graph", because GoWorkspace::selection walks the module graph.

OS × version

OS go go directive replace form main ecd4924 v5.0.1
Linux 1.21.13 1.21 versioned fail —
Linux 1.24.7 1.16 versioned / versionless fail / fail —
Linux 1.24.7 1.21 versioned / versionless fail / fail fail
Linux 1.26.8 1.21 versioned fail —
Linux 1.24.7 go.work member versioned refuses (correct) —
macOS / Windows — — — untested (no probe branch this run) —

Untested: vendored (vendor uses the same selection_refusal) and hosted (patch/redirect/mod.rs:9633 calls the same go_build_list::select), which probably behave the same.

First bad release

Not a recent regression: v5.0.1 shows it too.

Suspect code

  • crates/socket-patch-core/src/vendor/go_build_list.rs:90: in a pruned (go 1.17+) graph, None if zips.contains(&version) => Selection::Selected. And :94-99 for go < 1.17: a go.sum zip line counts as evidence that the module is built. A zip line also appears for a version that is only a replace target, under the target's path. The selection should ignore go.sum lines for a module/version that go.mod names only on the right-hand side of a replace (or, as workspace mode does, walk the graph).
  • Callers: patch/redirect/golang_local.rs:669 (selection_refusal, used by apply and vendor), vex/discover/golang.rs:300, patch/redirect/mod.rs:9633 (hosted).
  • Open PR #1698 mentions "unused replacements" in its audits; I couldn't tell whether it covers this shape.
Lenguaje dominante
Rust
Estrellas
8
Forks
2
Merge medio
19 h 19 min
PR fusionados (30 d)
697

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de SocketDev/socket-patch

Todos los issues de SocketDev/socket-patch

Issues similares

Más issues de Rust

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.