fix(langmgr,provenance): tolerate broken upstream go.mod with 'go mod tidy -e' - #1602
Merged
omercnet merged 3 commits intoMay 22, 2026
Merged
Conversation
… mod tidy -e'
When patching CVEs in Go binaries, copa runs `go mod tidy` after applying
`go get` version bumps to clean up the module graph. For real-world
upstream projects whose `go.mod` is brittle under a modern Go toolchain,
strict tidy fails on transitive imports unrelated to the security patch
and aborts the entire patch even though the CVE-relevant `go get` calls
succeeded.
Concrete error from patching
quay.io/prometheus-operator/prometheus-config-reloader:v0.81.0:
go: github.com/go-openapi/testify/v2/assert/yaml:
module github.com/go-openapi/testify/v2@latest found (v2.5.0),
but does not contain package
github.com/go-openapi/testify/v2/assert/yaml
process "/usr/local/go/bin/go mod tidy" did not complete
successfully: exit code: 1
The same failure has been observed against prometheus-config-reloader,
grafana/promtail, cilium/cilium-envoy, kyverno/* (background-controller,
cleanup-controller, kyvernopre, reports-controller, readiness-checker),
and rabbitmqoperator/messaging-topology-operator in production patching
workflows.
Switch all three call sites to `go mod tidy -e`:
- pkg/langmgr/golang.go (in-image path, factored into a small
buildGoUpdateCmd helper for testability)
- pkg/langmgr/golang.go (tooling-container fallback path)
- pkg/provenance/rebuilder.go (binary-rebuild path -- the call site
that produces the literal error in the failure log above)
From `go help mod tidy`:
The -e flag causes tidy to attempt to proceed despite errors
encountered while loading packages.
`tidy -e` will still update go.mod/go.sum based on what it can resolve,
honor the `go get` version pins (the actual CVE fix), and produce a
working module graph for the subsequent `go build`. It does not mask
errors in patched package versions (`go get` runs before tidy and still
fails loudly), and it does not hide CVE regressions (the post-patch
Trivy rescan is a second backstop). Healthy upstream go.mod files see
no behavior change -- `tidy -e` is a strict superset of `tidy`'s
success path.
Refines project-copacetic#1089 (which introduced the strict tidy step).
Smoke-tested against prometheus-config-reloader:v0.81.0:
47 fixable vulns -> 8 remaining (1 CRITICAL + 9 HIGH stdlib CVEs fixed
via Go 1.23.7 -> 1.26.3 toolchain bump), 0 regressions, patched binary
runs and reports --version cleanly.
Fixes project-copacetic#1601
Signed-off-by: Omer <omer@descope.com>
omercnet
requested review from
ashnamehrotra,
jeremyrickard,
robert-cronin and
sozercan
as code owners
May 9, 2026 19:06
Contributor
There was a problem hiding this comment.
Pull request overview
This PR makes Go app-level patching more resilient by switching the post-go get cleanup step from go mod tidy to go mod tidy -e, so upstream module-graph hygiene issues don’t block otherwise-successful CVE patching flows.
Changes:
- Replace strict
go mod tidywith tolerantgo mod tidy -ein both Go lang manager update paths and the provenance rebuilder binary rebuild path. - Factor the Go lang manager update command into a small helper (
buildGoUpdateCmd) and add regression tests to enforce-eusage. - Document the new behavior in the app-level patching docs under Go limitations.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| website/docs/app-level-patching.md | Documents that Go binary patching uses go mod tidy -e to tolerate broken upstream module graphs. |
| pkg/provenance/rebuilder.go | Updates the rebuild path to run go mod tidy -e and logs the new command. |
| pkg/provenance/rebuilder_test.go | Adds a regression test guarding against reintroducing bare go mod tidy. |
| pkg/langmgr/golang.go | Uses a shared helper to build the go get … && go mod tidy -e update command for both update sites. |
| pkg/langmgr/golang_test.go | Adds a table-driven test validating the helper emits go mod tidy -e. |
| // step still fails loudly if the patched module graph cannot produce a | ||
| // working binary. | ||
| func buildGoUpdateCmd(modPath, allGetCmd string) string { | ||
| return fmt.Sprintf(`sh -c 'cd %s && %s && go mod tidy -e'`, modPath, allGetCmd) |
omercnet
enabled auto-merge (squash)
May 22, 2026 04:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1601.
Prior art: refines #1089, which introduced the post-
go getgo mod tidycleanup step.Problem
Copa's app-level Go patcher runs
go get <patched-versions>followed bygo mod tidyto clean up the module graph before rebuilding. Strictgo mod tidyis too aggressive a cleanup gate — for real-world upstream Go projects whosego.modis brittle under a modern Go toolchain, a fresh tidy discovers transitive imports that no longer resolve and exits non-zero, killing the entire patch even though the CVE-relevantgo getcalls succeeded.Concrete failure
Patching
quay.io/prometheus-operator/prometheus-config-reloader:v0.81.0:The
go.modproblem is upstream-project hygiene (github.com/go-openapi/testify/v2/assert/yamldoes not exist in the resolved version oftestify/v2); it has nothing to do with the CVE being patched.Affected images
The same failure mode has been observed in production patching workflows against:
prometheus-operator/prometheus-config-reloadergrafana/promtailcilium/cilium-envoy(partially Go-related)kyverno/background-controller,kyverno/cleanup-controller,kyverno/kyvernopre,kyverno/reports-controller,kyverno/readiness-checkerrabbitmqoperator/messaging-topology-operatorFix
Switch all three
go mod tidyinvocations togo mod tidy -e:pkg/langmgr/golang.go— primary in-image path (factored into a smallbuildGoUpdateCmdhelper for testability)pkg/langmgr/golang.go— tooling-container fallback path (uses the same helper)pkg/provenance/rebuilder.go— binary-rebuild path. This is the call site that produces the literal error in the log above —go version -mruns the rebuilder first and its barego mod tidyis what exits 1.From
go help mod tidy:tidy -ewill still:go.mod/go.sumbased on what it can resolvego getversion pins applied just before tidy (the actual CVE fix)go buildIt will not:
go getruns before tidy and still fails loudly)go.modwas already healthy (tidy -eis a strict superset oftidy's success path)Smoke test
Built copa from this branch and ran end-to-end against the canonical reproducer.
Result:
Patched binary
--version(Original image was on
go1.23.7; the rebuilt binary now ships ongo1.26.3— the actual fix carrier for the stdlib CVEs.)Trivy before/after delta
All 19 stdlib CVEs (1 CRITICAL —
CVE-2025-68121; 9 HIGH includingCVE-2025-47907,CVE-2025-58183,CVE-2025-61726…) resolved by the toolchain upgrade. The 8 remaining are minor-version bumps ofprometheus/prometheus,golang.org/x/crypto,golang.org/x/net,golang.org/x/oauth2filtered out by the default--library-patch-level patchpolicy — unrelated to this PR.What this PR does
go mod tidyinvocations withgo mod tidy -e(pkg/langmgr/golang.go×2,pkg/provenance/rebuilder.go×1).TestBuildGoUpdateCmd(pkg/langmgr/golang_test.go) — table-driven, asserts both langmgr call sites.TestRebuilderUsesGoModTidyDashE(pkg/provenance/rebuilder_test.go) — regression guard for the rebuilder path.website/docs/app-level-patching.mdwith a "Tolerance for broken upstream go.mod" subsection under the Go limitations.make testgreen locally;golangci-lint run ./pkg/langmgr/... ./pkg/provenance/...clean for changed code.Out of scope (intentionally)
replacedirective injection (per-image overrides are a separate, larger proposal)go.modfiles