simulate

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package simulate validates a proposed set of Go module bumps by doing what the melange build's bump step will do, ahead of time: clone the upstream source at the exact tag, apply the bumps with the step's own semantics (gobump for `uses: go/bump`, omnibump for `uses: bump` - see Engine), then OSV-rescan the resolved module graph and raise versions until a fixpoint. The deps list that survives is proven to (a) resolve cleanly and (b) leave no known-fixable vulnerability behind; anything unreachable is reported as a residual rather than silently dropped.

Index

Constants

This section is empty.

Variables

View Source
var ErrCompileGate = errors.New("compile gate failed")

ErrCompileGate marks a simulation the compile gate could not finish (baseline unavailable, tool failure, budget exhausted, or a build that fails with every bump relaxed). Callers must fail closed: the candidate set was never proven to compile, so it must not be written.

View Source
var ErrInfrastructure = errors.New("go tool infrastructure failure")

ErrInfrastructure marks a go tool failure caused by the environment - the network, the module proxy or VCS host (connection errors, TLS timeouts, 5xx responses) or a per-command timeout - rather than by the module graph. It is never a verdict on a candidate: the simulation fails (closed) with it instead of shedding or residualising anything.

Functions

func Checkout added in v0.4.0

func Checkout(ctx context.Context, cloner Cloner, repoURL, tag, expectedCommit, tmpDir, purpose string) (dir string, cleanup func(), err error)

Checkout clones repoURL at tag into a fresh directory under tmpDir ("" = the os.MkdirTemp default) named for purpose ("choam-<purpose>-*"). expectedCommit, when non-empty, is verified warn-only (melange enforces it at build time). cleanup removes the directory; call it once err is nil.

func CompileCgoLimited added in v0.4.0

func CompileCgoLimited(arch string, env BuildEnv) bool

CompileCgoLimited reports whether the compile gate must type-check arch with CGO disabled although the build enables it (see targetBuildEnv).

func ModrootDir added in v0.4.0

func ModrootDir(root, modroot string) string

ModrootDir is modroot's directory within a checkout at root ("" and "." are the root itself).

func OmnibumpVersion added in v0.4.0

func OmnibumpVersion() string

OmnibumpVersion reports the omnibump version linked into this binary - the one EngineOmnibump simulates with - or "" when build info is unavailable.

func TargetArches added in v0.4.0

func TargetArches(arches []string) []string

TargetArches normalizes a target-arch list: deduplicated, amd64 then arm64 first and any others after in lexical order (so the compile gate's primary arch never depends on input order); empty means defaultArches.

Types

type BuildEnv added in v0.4.0

type BuildEnv struct {
	// CGO is the spec's CGO_ENABLED: "0" or "1", "" when unset or not
	// understood (the melange default then applies: enabled).
	CGO string
	// GOExperiment is the GOEXPERIMENT the build runs with ("" for none).
	GOExperiment string
}

BuildEnv is the part of the melange build environment that changes which files are built (see targetBuildEnv).

type BuildTarget added in v0.4.0

type BuildTarget struct {
	Patterns []string
	Tags     []string
	Arches   []string
	Env      BuildEnv
}

BuildTarget is what the melange build compiles for one modroot: build package patterns (empty means ./...), tags, the GOARCHes the package is built for (empty means amd64 and arm64; see TargetArches) and the build environment.

type Candidate

type Candidate struct {
	Module  string
	Version string
	FromCVE bool
	VulnIDs []string
	// Severity is the most severe advisory level the candidate addresses
	// (scan.SeverityRank vocabulary; empty when unknown).
	Severity string

	// Rungs is the candidate's fix ladder, highest first: one rung per
	// distinct advisory fix version (plus an existing YAML version), each
	// with the advisories it first fixes. The compile gate relaxes a
	// candidate whose version breaks the build one rung down. Empty means
	// the single rung {Version, VulnIDs, Severity}.
	Rungs []Rung

	// Replace marks a user-authored YAML replace (a replace seed), applied
	// as a go.mod replace directive rather than a `go get`; the loop never
	// creates replace directives of its own. ReplaceOld is the directive's
	// left-hand side; empty means a self-replace (ReplaceOld == Module).
	Replace    bool
	ReplaceOld string
}

Candidate is one proposed module@version bump. FromCVE marks candidates backed by an OSV advisory: they are defended (stepped down their fix ladder, reported as residuals when unreachable), whereas coherence-only candidates (carried-forward pins) are the first to be sacrificed when the module graph won't resolve.

func (Candidate) OldPath

func (c Candidate) OldPath() string

OldPath returns the replace directive's left-hand side, defaulting to the module itself (self-replace).

type Cloner

type Cloner interface {
	CloneAtTag(ctx context.Context, repoURL, tag, destDir string) error
	HeadCommit(ctx context.Context, dir string) (string, error)
}

Cloner is the source-checkout seam; *git.Client satisfies it.

type CompileReport added in v0.4.0

type CompileReport struct {
	// Failed maps each package that failed to compile to its first error
	// lines ("# pkg" header stripped).
	Failed map[string][]string
	// Modules maps every non-standard package in the graph to its module
	// path (replacement applied); "" for main-module packages.
	Modules map[string]string
	// Imports holds the direct imports of each failed package, for repair
	// and relaxation attribution.
	Imports map[string][]string
	// FileModules maps module-cache file paths named in error lines
	// (<GOMODCACHE>/<mod>@<ver>/...) back to "module@version".
	FileModules map[string]string
}

CompileReport is the outcome of one Compiler.Compile.

type Compiler added in v0.4.0

type Compiler interface {
	// Compile compiles (without linking) the transitive non-test import
	// graph of the target for GOOS=linux and GOARCH=arch, reporting
	// per-package failures. An error return means the go tool itself could
	// not run, not that packages failed to compile.
	Compile(ctx context.Context, dir string, target BuildTarget, arch string) (*CompileReport, error)
	// ModuleVersions lists module's released versions, ascending.
	ModuleVersions(ctx context.Context, dir, module string) ([]string, error)
	// ModuleRequires returns the require entries of module@version's own
	// go.mod.
	ModuleRequires(ctx context.Context, dir, module, version string) (map[string]string, error)
}

Compiler is the optional compile-gate seam: a Toolchain that also implements it enables the post-convergence compile gate (see loop.compileGate), unless Options.NoCompile is set. *GoToolchain implements it.

type DroppedCandidate

type DroppedCandidate struct {
	Module  string `json:"module" yaml:"module"`
	Version string `json:"version" yaml:"version"`
	Reason  string `json:"reason" yaml:"reason"`
	// Redundant marks an entry removed because it did no work: it matched or
	// regressed upstream, or the final tidied go.mod is identical without it.
	Redundant bool `json:"redundant,omitempty" yaml:"redundant,omitempty"`
	// Hygiene marks a scanner-hygiene bump that was not proposed because it
	// is not free (see HygieneModule); Reason says why.
	Hygiene bool `json:"hygiene,omitempty" yaml:"hygiene,omitempty"`
}

DroppedCandidate records a candidate removed during simulation and why.

type Engine added in v0.4.0

type Engine int

Engine selects the apply semantics the simulation models: what the build's bump step will actually do with the written deps list.

const (
	// EngineGobump models melange's `uses: go/bump` (gobump): tidy
	// -go=<host>, one `go get` per entry (skipped when go.mod already
	// requires the module above the entry), tidy -go=<host>. The zero value.
	EngineGobump Engine = iota
	// EngineOmnibump models `uses: bump` by running omnibump's own
	// golang.DoUpdate behind the filter its CLI applies first (see rawSkip):
	// the go directive only ever lowered to the build's Go and the toolchain
	// line dropped, initial tidy, replaces, `go get` only for modules absent
	// from go.mod (or non-semver targets), the EXACT version set in place for
	// already-required modules (no MVS ripple), final tidy without -go, and
	// a post-tidy verification that no entry was downgraded.
	EngineOmnibump
)

func (Engine) String added in v0.4.0

func (e Engine) String() string

type GoToolchain

type GoToolchain struct {
	// contains filtered or unexported fields
}

GoToolchain runs the local go binary. The environment passes through the user's proxy/auth configuration (GOPROXY, GOPRIVATE, netrc via HOME) and forces the settings resolution parity depends on: GOTOOLCHAIN=auto so a go.mod demanding a newer toolchain is honored, -mod=mod so a vendor/ directory doesn't mask resolution, and GOWORK=off so a stray go.work can't leak into the analysis.

func NewToolchain

func NewToolchain(ctx context.Context, commandTimeout time.Duration) (*GoToolchain, error)

NewToolchain locates the go binary; the error return lets callers degrade gracefully (skip validation, report it) when no toolchain is available.

func (*GoToolchain) Compile added in v0.4.0

func (t *GoToolchain) Compile(ctx context.Context, dir string, target BuildTarget, arch string) (*CompileReport, error)

Compile type-checks and compiles (without linking) every non-test package in the transitive import graph of the target's patterns, via `go list -e -export -deps -json`: -export forces a real compile of each package to export data, and -e records each package's own compile error on its .Error instead of aborting the walk - structured, per-package, no vet noise, no link step. A package whose dependency failed is not compiled at all (only DepsErrors), so failures surface on the deepest broken package - exactly the module that needs blaming. The build target is targetBuildEnv's for arch (one arch per call). The build cache is the user's GOCACHE, shared and incremental across compiles and runs. An error return means the go tool itself failed (not that packages failed to compile).

func (*GoToolchain) DepGoVersions

func (t *GoToolchain) DepGoVersions(ctx context.Context, dir string) (map[string]string, error)

DepGoVersions returns the go directive of every non-main module in the build list (bare form, e.g. "1.24" or "1.24.5"), keyed by module path, with replace directives applied - mirrors ListModules' decode loop and skip-main/skip-local-replacement semantics exactly, substituting GoVersion for Version throughout.

func (*GoToolchain) Get

func (t *GoToolchain) Get(ctx context.Context, dir, moduleAtVersion string) error

func (*GoToolchain) Linked

func (t *GoToolchain) Linked(ctx context.Context, dir string, target BuildTarget) (Linkage, error)

Linked returns the modules AND packages in the transitive non-test import graph of the target's build patterns - exactly what the linker records in the binary's buildinfo (what APK scanners read), plus the package-level detail needed to check an advisory's vulnerable import paths (a module can be linked via one package while the vulnerable package is not - e.g. x/sys/unix linked, x/sys/windows not). Both sets come from ONE go list walk per target arch, unioned: an import linked on any arch the package is built for ships. Deliberate choices:

  • Test-only imports are excluded BY DESIGN (no -test flag): they never ship in the artifact, which is precisely the narrowing this exists for.
  • -e with per-package error inspection (see parseDeps): an unmatched //go:embed pattern (generated files absent from the checkout) is tolerated and reported in Linkage.Embeds - it cannot change the import graph. Any other package-load error (on any arch) fails the whole call so the caller fails OPEN (no filtering): the listing may then under-report the import graph and wrongly drop real fixes - the one unacceptable failure mode.
  • Replace directives resolve to the replacement path, matching ListModules' identity space (scan findings name the replacement).
  • The build environment and tags are targetBuildEnv's and the target's, shared with Compile, so the linked set and the compile gate judge the same files (CGO aside: see targetBuildEnv).

func (*GoToolchain) LinkedStd

func (t *GoToolchain) LinkedStd(ctx context.Context, dir string, target BuildTarget) (map[string]struct{}, error)

LinkedStd returns the set of standard-library import paths in the transitive non-test import graph of the target's build patterns, with the same arch union, walk and error semantics as Linked (inverted filter: standard library packages instead of non-standard ones; tolerated embed gaps are Linked's to report).

func (*GoToolchain) ListModules

func (t *GoToolchain) ListModules(ctx context.Context, dir string) (map[string]string, error)

func (*GoToolchain) ModTidy

func (t *GoToolchain) ModTidy(ctx context.Context, dir string) error

func (*GoToolchain) ModuleRequires added in v0.4.0

func (t *GoToolchain) ModuleRequires(ctx context.Context, dir, modulePath, version string) (map[string]string, error)

ModuleRequires returns the require entries of module@version's own go.mod (`go list -m -json` fetches only the .mod/.info, never the zip), memoized per toolchain: module versions are immutable.

func (*GoToolchain) ModuleVersions added in v0.4.0

func (t *GoToolchain) ModuleVersions(ctx context.Context, dir, modulePath string) ([]string, error)

ModuleVersions lists the released versions of module (`go list -m -versions`), in ascending semver order.

func (*GoToolchain) Replace

func (t *GoToolchain) Replace(ctx context.Context, dir, oldPath, newPath, version string) error

Replace applies a replace directive with gobump parity: dropreplace first (so a stale directive can't shadow the new one), then the replacement.

func (*GoToolchain) Replaces

func (t *GoToolchain) Replaces(_ context.Context, dir string) (map[string]ReplaceTarget, error)

Replaces parses dir's go.mod replace directives, keyed by the replaced (old) module path. Local filesystem targets carry an empty Version. Note: modfile.ParseLax skips replace directives entirely, so this must use the full parser.

func (*GoToolchain) RequiredBy added in v0.4.0

func (t *GoToolchain) RequiredBy(ctx context.Context, dir string) (map[string]map[string]string, error)

RequiredBy returns, from `go mod graph`, each module's requirers in the build list: required module -> requiring module path -> required version. Only edges from a requirer's selected version (the highest version of it in the graph, as MVS picks) count; the main module's own requirements are excluded.

func (*GoToolchain) Requirements

func (t *GoToolchain) Requirements(_ context.Context, dir string) (map[string]string, error)

Requirements parses dir's go.mod require entries. No go tool invocation - the file as tidied IS the contract gobump checks deps entries against.

func (*GoToolchain) ResolveQuery added in v0.4.0

func (t *GoToolchain) ResolveQuery(ctx context.Context, dir, modulePath, query string) (string, error)

ResolveQuery resolves module@query (e.g. a commit hash) to a concrete version (`go list -m`).

type HygieneModule added in v0.4.0

type HygieneModule struct {
	Module  string   `json:"module" yaml:"module"`
	Version string   `json:"version" yaml:"version"`
	VulnIDs []string `json:"vuln_ids" yaml:"vuln_ids"`
}

HygieneModule is a scanner-hygiene bump: a module linked into the artifact whose advisories' vulnerable packages are not, raised anyway because the bump is free - it changes nothing in the final go.mod but this module's own version, needs no newer Go, compiles and introduces no advisory. Module-level scanners (grype) flag the module version; this is not a security fix and is never counted as one.

type Linkage added in v0.4.0

type Linkage struct {
	Modules, Packages map[string]struct{}
	// Embeds are the packages whose //go:embed patterns matched no files in
	// the checkout (typically files generated at build time - see
	// unmatchedEmbed), sorted and deduplicated across arches. They do not
	// affect the import graph, so the sets above are complete regardless.
	Embeds []UnmatchedEmbed
}

Linkage is one Toolchain.Linked walk: the linked modules and packages, and the go:embed gaps tolerated while computing them.

type ModrootRequest

type ModrootRequest struct {
	Modroot string
	Seeds   []Candidate
	// Baseline maps module path -> effective version in the original
	// manifest (replace directives applied). FinalDeps entries that don't
	// move a module beyond its baseline are dropped as no-ops.
	Baseline map[string]string
	// Packages are the build package patterns (relative to Modroot,
	// go/build's space-separated with.packages grammar) whose non-test
	// import graphs define artifact reachability; empty falls back to ./...
	// (over-approximate, never narrower than the artifact).
	Packages []string
	// Tags are the go/build steps' build tags (toolchaintags plus tags),
	// applied by reachability and the compile gate so they judge the files
	// the build will compile.
	Tags []string
	// Arches are the GOARCHes the package is built for (see BuildTarget):
	// reachability is their union; the compile gate runs on the first and
	// the final set is compiled once more on each of the others.
	Arches []string
	// Env is the build environment the spec sets (see BuildEnv).
	Env BuildEnv
	// VulnImports maps each seed advisory ID to its vulnerable imports (from
	// the analysis scan's OSV metadata; see
	// scan.Vulnerability.VulnerableImports). Seed candidates whose
	// advisories all live in unlinked packages are dropped before the
	// first apply. Absent/empty entries fail open (see scan.ImportsLinked).
	VulnImports map[string][]scan.VulnerableImport
	// BaselineVulnIDs are the advisory IDs the analysis scan found in the
	// pristine graph. Residual advisories with no ID in this set were
	// INTRODUCED by the bump (they apply only to a bumped-to version) and
	// are reported and counted separately. Nil disables introduced-vuln
	// classification (fail open).
	BaselineVulnIDs map[string]struct{}
	// Engine is the bump step's apply semantics (see Engine).
	Engine Engine
	// NoTidy mirrors a `tidy: false` bump step: the engine runs no go mod
	// tidy at all.
	NoTidy bool
	// GoVersion is the build's Go version (bare, e.g. "1.25.9"); omnibump
	// lowers the go directive to it, never raises it. "" means the host go
	// (EngineOmnibump only).
	GoVersion string
	// ProbeTidy asks, for a NoTidy EngineOmnibump request, whether the
	// pristine module would tidy under omnibump anyway (DoUpdate with tidy
	// on and no entries); the answer is ModrootResult.BaselineTidies. It is
	// informational only - the simulation itself still honours NoTidy.
	ProbeTidy bool
}

ModrootRequest describes one module root to simulate.

type ModrootResult

type ModrootResult struct {
	Modroot string `json:"modroot" yaml:"modroot"`

	// FinalDeps are the module@version entries proven to apply cleanly, in
	// stable (indirect -> direct -> new, alphabetical within group) order,
	// filtered to entries that actually move the module graph forward
	// relative to the original manifest.
	FinalDeps []string `json:"final_deps" yaml:"final_deps"`

	// FinalReplaces are the user-authored replace directives ("old=new@version",
	// gobump grammar) proven to apply cleanly, raised where an advisory
	// demanded it. Note a replace pins its module exactly - future graph
	// demands for a higher version are overridden until a later choam run
	// re-raises the directive.
	FinalReplaces []string `json:"final_replaces,omitempty" yaml:"final_replaces,omitempty"`

	// Resolved is the full module graph (module -> version) after the final
	// clean apply.
	Resolved map[string]string `json:"-" yaml:"-"`

	// Linked is the artifact-linked module set of the final clean state
	// (see Toolchain.Linked); nil when reachability was unavailable and
	// the loop failed open (everything treated as linked). Callers use it
	// to tell which analysis-time findings never affected the artifact.
	Linked map[string]struct{} `json:"-" yaml:"-"`

	// LinkedPackages is the package-level companion of Linked: the import
	// paths in the artifact's non-test import graph, for checking an
	// advisory's vulnerable import paths (nil when unavailable - fail open).
	LinkedPackages map[string]struct{} `json:"-" yaml:"-"`

	// LinkageError says why Linked is nil (the last Toolchain.Linked query
	// failed and reachability failed open); empty when it is known.
	LinkageError string `json:"-" yaml:"-"`

	// UnmatchedEmbeds are the go:embed gaps tolerated computing Linked (see
	// Linkage.Embeds).
	UnmatchedEmbeds []UnmatchedEmbed `json:"-" yaml:"-"`

	// GateUnchecked counts the packages the compile gate excluded because
	// they already fail to compile in the pristine checkout (primary arch) -
	// its blind spot; GateUncheckedFirst is the first one's "pkg: error".
	GateUnchecked      int    `json:"-" yaml:"-"`
	GateUncheckedFirst string `json:"-" yaml:"-"`

	// Requires are the final tidied go.mod require entries (module ->
	// version) - the sustainability contract melange's gobump verifies
	// deps entries against. A pin at (or below) its module's Requires
	// version is proven; anything above is not.
	Requires map[string]string `json:"-" yaml:"-"`

	// UnrequiredModules is the degraded module-level reachability signal,
	// populated ONLY when Linked is nil (the go list -deps walk failed open)
	// and the tidied go.mod's go directive is >= 1.17: modules present in
	// Resolved but absent from the tidied require block under any replace
	// identity. Such modules provide no package in the main module's import
	// closure and cannot be linked into any artifact. nil when unavailable or
	// when Linked is authoritative. Module-granular: unlike Linked, membership
	// of the COMPLEMENT does not imply linked (test-only deps are required but
	// never ship).
	UnrequiredModules map[string]struct{} `json:"-" yaml:"-"`

	// MaxDepGoVersion is the highest go directive across the final resolved
	// build list's non-main modules (bare form, e.g. "1.25"); empty when
	// unavailable. The scratch main module's own directive is deliberately
	// not consulted: the engine rewrites it (gobump: -go=<host>; omnibump:
	// lowered to the build's Go).
	MaxDepGoVersion string `json:"max_dep_go_version,omitempty" yaml:"max_dep_go_version,omitempty"`

	// StdPackages is the stdlib slice of the final artifact import graph
	// (LinkedStd over the converged checkout); nil when unknown.
	StdPackages map[string]struct{} `json:"-" yaml:"-"`

	// CVEBackedModules lists the modules in FinalDeps whose entries address
	// at least one advisory (vs coherence-only pins) - the simulation-time
	// equivalent of the checker's SecurityBumpModules.
	CVEBackedModules []string `json:"cve_backed_modules,omitempty" yaml:"cve_backed_modules,omitempty"`

	// HygieneModules are the FinalDeps entries proposed only for scanner
	// hygiene (see HygieneModule), by module path. A module can be in both
	// lists: a security fix raised further to clear an unlinked advisory.
	HygieneModules []HygieneModule `json:"hygiene_modules,omitempty" yaml:"hygiene_modules,omitempty"`

	// HygieneSkipped says why scanner-hygiene bumps were not evaluated
	// although candidates existed (compile gate off, package reachability
	// unknown, or no convergence); empty otherwise.
	HygieneSkipped string `json:"hygiene_skipped,omitempty" yaml:"hygiene_skipped,omitempty"`

	// BaselineTidies answers ModrootRequest.ProbeTidy: the pristine module
	// tidies under omnibump (so a `tidy: false` may no longer be needed).
	// Always false when no probe was requested.
	BaselineTidies bool `json:"baseline_tidies,omitempty" yaml:"baseline_tidies,omitempty"`

	Residuals []Residual         `json:"residuals,omitempty" yaml:"residuals,omitempty"`
	Dropped   []DroppedCandidate `json:"dropped,omitempty" yaml:"dropped,omitempty"`

	// RemainingVulnIDs are the advisory IDs still present in the final
	// resolved graph (the union of all residuals' VulnIDs).
	RemainingVulnIDs []string `json:"remaining_vuln_ids,omitempty" yaml:"remaining_vuln_ids,omitempty"`

	Iterations int  `json:"iterations" yaml:"iterations"`
	Converged  bool `json:"converged" yaml:"converged"`
}

ModrootResult is the outcome of simulating one module root.

func RunLoop

func RunLoop(ctx context.Context, tc Toolchain, sc Scanner, dir string, req ModrootRequest, opts Options) (*ModrootResult, error)

RunLoop applies the seed candidates to the module at dir with the real go toolchain, rescans the resolved graph, raises versions for any residual advisories, and iterates to a fixpoint (see package doc). It returns a ModrootResult even on non-convergence (Converged=false); an error return means the simulation itself could not run (unreadable module, toolchain or scanner failure) and the caller should fall back to unvalidated behavior.

type Options

type Options struct {
	// MaxIterations caps the raise-and-rescan fixpoint loop (default 5).
	MaxIterations int
	// CommandTimeout bounds each go tool invocation (default 2m).
	CommandTimeout time.Duration
	// Budget bounds the whole per-package simulation (default 10m).
	Budget time.Duration
	// NoCompile disables the compile gate (see Compiler).
	NoCompile bool
}

Options tunes the simulation.

func (Options) WithDefaults

func (o Options) WithDefaults() Options

WithDefaults fills unset options.

type ReplaceTarget

type ReplaceTarget struct {
	Path    string
	Version string // empty for local filesystem replacements
}

ReplaceTarget is the right-hand side of a go.mod replace directive.

type Residual

type Residual struct {
	Module          string   `json:"module" yaml:"module"`
	ResolvedVersion string   `json:"resolved_version" yaml:"resolved_version"`
	FixedVersion    string   `json:"fixed_version,omitempty" yaml:"fixed_version,omitempty"` // empty: no released fix
	VulnIDs         []string `json:"vuln_ids,omitempty" yaml:"vuln_ids,omitempty"`
	Reason          string   `json:"reason" yaml:"reason"`

	// Introduced marks an advisory absent from the baseline analysis scan -
	// it applies only to a version the bump itself moved to, not to the
	// original graph. Accounting must not subtract introduced residuals from
	// the baseline "found" count.
	Introduced bool `json:"introduced,omitempty" yaml:"introduced,omitempty"`
}

Residual is a vulnerability the simulation could not eliminate, with the reason zero-vulnerability state is unreachable for it. Not Go-only: gobump also reuses it for Rust/Java bumps it holds back.

type Rung added in v0.4.0

type Rung struct {
	Version  string
	VulnIDs  []string
	Severity string
}

Rung is one step of a candidate's fix ladder: a target version and the advisories (with their most severe level) first fixed at it.

func FixRungs added in v0.4.0

func FixRungs(vulns []scan.Vulnerability, module string, ids []string, also ...string) []Rung

FixRungs builds module's fix ladder from scan findings: one rung per distinct fixed version among module's advisories (restricted to ids when non-empty), plus a bare rung for each of also not already present (existing pins), highest first.

type Scanner

type Scanner interface {
	ScanPackages(ctx context.Context, pkgs []scan.Package) (*scan.ScanResult, error)
}

Scanner is the vulnerability-scanning seam; *scan.VulnerabilityScanner satisfies it.

type Simulator

type Simulator struct {
	// contains filtered or unexported fields
}

Simulator validates per-modroot bump candidate sets against a real checkout of the upstream source (see package doc).

func NewSimulator

func NewSimulator(cloner Cloner, tc Toolchain, sc Scanner, tempDir string, opts Options) *Simulator

NewSimulator wires a Simulator from its dependencies. tempDir may be empty.

func (*Simulator) Simulate

func (s *Simulator) Simulate(ctx context.Context, repoURL, tag, expectedCommit string, reqs []ModrootRequest) (map[string]*ModrootResult, error)

Simulate clones repoURL at tag once and runs the fixpoint loop for each requested modroot against that checkout. A per-modroot failure is recorded on its result slot as an error return; a clone failure fails the whole simulation. expectedCommit, when non-empty, is verified warn-only (melange enforces it at build time).

type Toolchain

type Toolchain interface {
	// ModTidy runs `go mod tidy` in dir.
	ModTidy(ctx context.Context, dir string) error
	// Get runs `go get module@version` in dir.
	Get(ctx context.Context, dir, moduleAtVersion string) error
	// ListModules returns the resolved module graph (module path -> version)
	// from `go list -m all` in dir, with replace directives applied and the
	// main module and local-path replacements omitted.
	ListModules(ctx context.Context, dir string) (map[string]string, error)
	// Requirements returns the require entries (module path -> version) of
	// dir's go.mod as written - the set melange's gobump verifies deps
	// entries against after its final tidy.
	Requirements(ctx context.Context, dir string) (map[string]string, error)
	// DepGoVersions returns the go directive of every non-main module in the
	// build list (bare form, e.g. "1.24" or "1.24.5"), keyed by module path,
	// with replace directives applied (same skip-main semantics as
	// ListModules).
	DepGoVersions(ctx context.Context, dir string) (map[string]string, error)
	// Replace applies a replace directive (gobump parity:
	// `go mod edit -dropreplace=<old>` then `-replace=<old>=<new>@<version>`).
	Replace(ctx context.Context, dir, oldPath, newPath, version string) error
	// Replaces returns dir's go.mod replace directives keyed by Old.Path; a
	// local filesystem target is reported with Version == "".
	Replaces(ctx context.Context, dir string) (map[string]ReplaceTarget, error)
	// Linked returns the module paths AND package import paths in the
	// transitive non-test import graph of the target (see BuildTarget),
	// unioned over its arches - the modules the linker records in the
	// binary's buildinfo, i.e. what actually ships, plus the package-level
	// detail for checking advisories' vulnerable import paths. Errors make
	// reachability filtering fail OPEN (treat everything as linked).
	// Unmatched go:embed patterns are tolerated and reported (see Linkage).
	Linked(ctx context.Context, dir string, target BuildTarget) (Linkage, error)
	// LinkedStd returns the set of standard-library import paths in the
	// transitive non-test import graph of the target (same arch union and
	// walk semantics as Linked, inverted filter).
	LinkedStd(ctx context.Context, dir string, target BuildTarget) (map[string]struct{}, error)
}

Toolchain abstracts the go tool operations the simulation needs. The real implementation shells out to the local go binary (see NewToolchain); tests inject fakes.

type UnmatchedEmbed added in v0.4.0

type UnmatchedEmbed struct {
	Package string
	Pattern string
}

UnmatchedEmbed is a //go:embed pattern of Package that matched no files.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL