dependency

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 34 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyIgnores added in v0.7.0

func ApplyIgnores(deps []supplychain.Dependency, ignores []VulnIgnore, now time.Time) []supplychain.Dependency

ApplyIgnores strips advisories the operator has explicitly accepted (dependency.ignore) from each dependency's Vulnerabilities — UNLESS the ignore has lapsed past its `until` date, in which case the advisory re-surfaces (accepted risk is never permanent). IDs match case-insensitively. A malformed or expired `until` is treated as expired: it must never silently drop a real finding. A dependency whose every advisory is ignored is left with zero vulnerabilities and thus falls out of security-policy scope.

func FetchAdvisories

func FetchAdvisories(ctx context.Context, fc forge.Forge, ref, rootDir string) ([]security.Advisory, error)

FetchAdvisories attempts to download advisories from the latest successful security-scan job via the forge API. Writes the file to the standard location so subsequent LoadAdvisories calls find it. Returns the advisories, or nil+nil if unavailable (not an error).

func GenerateArtifacts

func GenerateArtifacts(ctx context.Context, repoRoot, outputDir string, result *UpdateResult, bundle bool) ([]string, error)

GenerateArtifacts creates output files in the specified directory.

func LoadAdvisories

func LoadAdvisories(rootDir string) ([]security.Advisory, error)

LoadAdvisories reads the security advisory bridge file. Returns empty slice + nil error if file doesn't exist (no advisories = normal).

func NormalizeSkipReason added in v0.3.2

func NormalizeSkipReason(reason string) string

NormalizeSkipReason maps internal parser-detail skip reasons to operator-facing language. Reasons that are already operator-friendly pass through unchanged.

func RemediationSummaryMarkdown added in v0.7.0

func RemediationSummaryMarkdown(evals []RemediationEvaluation) string

RemediationSummaryMarkdown renders the advisory verdicts as a Markdown section for a merge-request body, so a HUMAN reviewer sees remediated / unremediable-under-policy / no-fix at a glance — the human-in-the-loop enabler (the bot opens a self-explaining PR; a person merges). Returns "" when there are no advisories. States are grouped, with "unremediable under declared policy" surfaced as its own prominent section.

func Verify

func Verify(ctx context.Context, moduleDirs []string, repoRoot string, runTests, runVulncheck bool) (string, error)

Verify runs post-update verification (go test + govulncheck) on the given module directories. moduleDirs should be absolute paths — only dirs where updates were actually applied.

Types

type AppliedUpdate

type AppliedUpdate struct {
	Dep         supplychain.Dependency
	OldVer      string
	NewVer      string
	UpdateType  string // "major", "minor", "patch", "tag", "security"
	CVEsFixed   []string
	Remediation string // how a security remediation was achieved (parent-bump vs pinned floor); empty for ordinary updates
}

AppliedUpdate records a single dependency that was successfully updated.

type CandidateSet added in v0.7.0

type CandidateSet struct {
	Dep            supplychain.Dependency
	Eligible       bool         // is this dep an update candidate?
	Category       SkipCategory // classification when not eligible (dep-level skip)
	Reason         string       // human reason, byte-identical to the legacy skipReason
	Target         string       // selected update target ("" when none / skipped)
	ResolvedTarget string       // set when a ceiling re-target lowered the target
	// contains filtered or unexported fields
}

CandidateSet is the per-dependency policy evaluation: whether the dep is an update candidate at all (Eligible), the selected Target, and a PREDICATE (Admits) that tests ANY version against the same declared policy. It is the single artifact the updater, remediation, and freshness reporting all read — one evaluation, many consumers, no re-derivation. Admits is a predicate rather than a materialized set so it works WITHOUT the full version list (which most ecosystems don't provide) and so the exclusion provenance (the binding PolicyLayer) is automatic. The set embeds the policy it was built under, so Admits is self-contained.

func Construct added in v0.7.0

func Construct(dep supplychain.Dependency, cfg UpdateConfig, ecosystemFilter map[string]bool, trackedFiles map[string]bool) CandidateSet

Construct evaluates declared policy for one dependency: it reproduces the legacy skip decision EXACTLY (same category + reason, same precedence) and computes the selected target. The filter is now a thin consumer of this — one evaluation the updater (here), remediation, and freshness all read.

func (CandidateSet) Admits added in v0.7.0

func (cs CandidateSet) Admits(v string) (bool, PolicyLayer, string)

Admits reports whether version v is acceptable under this dependency's declared policy and, when not, the BINDING policy layer plus a human detail. Layers are tested most-fundamental first, so the first rejection is the tightest bound. This is what RemediationEvaluation will ask of an advisory's fixed-in ("is the fix reachable under policy?"). Note: cooldown (min_release_age) is data-dependent — evaluable only when per-version publish dates are available, which most ecosystems don't yet supply, so it is not enforced here beyond its layer being defined for downstream use.

type CargoChurn added in v0.7.0

type CargoChurn struct {
	Intended int
	Mutated  int
}

CargoChurn measures lock-sync intent fidelity: how many distinct packages we asked cargo to update (Intended) vs how many lockfile entries actually changed (Mutated). A ratio near 1 is healthy; a large ratio means a wide transitive re-resolve — operationally meaningful review signal, since it is exactly where unintended blast radius hides.

type EnrichmentDetail added in v0.3.2

type EnrichmentDetail struct {
	Name       string
	Version    string
	Ecosystem  string
	Advisories int
}

EnrichmentDetail describes a single dependency that was enriched with advisories.

func EnrichDependencies

func EnrichDependencies(deps []supplychain.Dependency, advisories []security.Advisory) []EnrichmentDetail

EnrichDependencies merges scanner advisories into resolved dependencies. Conservative matching: requires both ecosystem AND normalized package name match. Skips advisories with ecosystem "unknown" — no guessing. Returns details for each enriched dependency, sorted by advisory count desc then name asc.

type PolicyLayer added in v0.7.0

type PolicyLayer string

PolicyLayer names a policy constraint that can exclude a version from a CandidateSet. When Admits rejects a version it returns the BINDING layer — the one that actually removed it — so callers explain the decision precisely rather than re-deriving it. (Lives in package dependency, not a sub-package, so it can share the SkipCategory / UpdateConfig types without an import cycle.)

const (
	PolicyNone             PolicyLayer = ""
	PolicyPinned           PolicyLayer = "pinned"            // toolchain governs the version (go replace)
	PolicyNativeConstraint PolicyLayer = "native_constraint" // manifest operator (cargo =/~/^/range)
	PolicyCeiling          PolicyLayer = "ceiling"           // max_update
	PolicyCooldown         PolicyLayer = "cooldown"          // min_release_age (data-dependent)
	PolicyPrerelease       PolicyLayer = "prerelease"        // stable-only unless current is prerelease
)

type RemediationEvaluation added in v0.7.0

type RemediationEvaluation struct {
	VulnID    string
	Package   string
	Version   string // the dep's current resolved version
	Severity  string // from the advisory, unchanged
	FixedIn   string
	State     RemediationState
	BlockedBy PolicyLayer // the binding layer when blocked
	Detail    string      // the binding constraint detail (e.g. "=1.8.0", "replace directive")
}

RemediationEvaluation is the per-advisory verdict, derived by asking the dependency's CandidateSet whether the advisory's fixed-in version is reachable under declared policy. This is the first-class "can policy fix it, and if not why" artifact — the answer StageFreight needs before it can automerge anything.

func EvaluateRemediation added in v0.7.0

func EvaluateRemediation(deps []supplychain.Dependency, cfg UpdateConfig, result *UpdateResult) []RemediationEvaluation

EvaluateRemediation produces a verdict for every advisory across `deps`, using the CandidateSet predicate to test each advisory's fixed-in against declared policy. `result` (if non-nil) marks advisories an applied update actually fixed.

func Residuals added in v0.7.0

func Residuals(evals []RemediationEvaluation, failOn string) []RemediationEvaluation

Residuals filters evaluations to the unremediated advisories at or above the failOn threshold — the gating set. Byte-identical to the prior residual gate (state != remediated ⟺ advisory not in the applied fixed-set), now carrying WHY. failOn "off"/"" or unrecognized → no gate (nil).

func (RemediationEvaluation) Explain added in v0.7.0

func (e RemediationEvaluation) Explain() string

Explain renders a residual's reason for disclosure. blocked_by_policy is surfaced as the first-class "unremediable under declared policy" outcome.

type RemediationState added in v0.7.0

type RemediationState string

RemediationState is the verdict for one advisory on one dependency: was it (or can it be) fixed, and if not, why. Severity comes from the advisory; STATE from this evaluation; the two are orthogonal — a CRITICAL stays CRITICAL whether remediated or blocked.

const (
	StateRemediated         RemediationState = "remediated"          // an applied update carried the fix
	StateReachableUnapplied RemediationState = "reachable_unapplied" // fix reachable under policy but not applied (evaluate-only / apply gap)
	StateBlockedByPolicy    RemediationState = "blocked_by_policy"   // fix exists but is excluded by declared policy — UNREMEDIABLE UNDER POLICY
	StateBlockedByCooldown  RemediationState = "blocked_by_cooldown" // fix exists but held by min_release_age (reserved: cooldown data-dependent)
	StateNoFix              RemediationState = "no_fix"              // no fixed-in version published upstream
)

type ResidualVuln added in v0.7.0

type ResidualVuln struct {
	Dep      string
	Version  string
	VulnID   string
	Severity string
}

ResidualVuln is a vulnerability still present AFTER the update pass — its advisory was not fixed by any applied update — at or above the gate threshold.

func ResidualVulnerabilities added in v0.7.0

func ResidualVulnerabilities(deps []supplychain.Dependency, result *UpdateResult, failOn string) []ResidualVuln

ResidualVulnerabilities is the deps module's Policy stage: the residual vulnerabilities (not fixed by any Applied update) at or above the failOn threshold. It is the simple []ResidualVuln view over the richer RemediationEvaluation model (EvaluateRemediation + Residuals) — behavior-identical: the residual SET is the state != remediated advisories. Callers wanting the WHY use EvaluateRemediation. failOn "off"/"" or an unrecognized label → no gate (nil).

type SkipCategory added in v0.7.0

type SkipCategory string

SkipCategory is the typed classification of WHY a dependency was not updated, set at the point the decision is made rather than reverse-engineered from prose afterwards. It is the single source of truth for the decision; the human-readable SkippedDep.Reason is the presentation of it.

This is the explanation spine: today grouping and disclosure still render the free-form Reason string (so output is unchanged), but every skip now carries an intrinsic category that downstream consumers — uniform disclosure, remediation evaluation, candidate-set provenance — can read WITHOUT string-matching. Adding a new skip site means naming its category here, not teaching a parser a new phrase.

const (
	// SkipNone is the zero value: the dependency is a candidate, not skipped.
	SkipNone SkipCategory = ""

	// --- policy / freshness decisions (filter.go) ---
	SkipUpToDate          SkipCategory = "up_to_date"           // already at the compatible target
	SkipMajorHeld         SkipCategory = "major_held"           // constraint-expanding major out of range, review-only
	SkipCeilingExceeded   SkipCategory = "ceiling_exceeded"     // beyond max_update, no in-ceiling re-target
	SkipSecurityOnly      SkipCategory = "security_only_policy" // non-vulnerable dep under policy=security
	SkipEcosystemFiltered SkipCategory = "ecosystem_filtered"   // excluded by the ecosystem filter
	SkipNotAutoUpdatable  SkipCategory = "not_auto_updatable"   // ecosystem cannot be auto-updated
	SkipIndirect          SkipCategory = "indirect"             // transitively managed, not updated directly
	SkipUnresolved        SkipCategory = "unresolved"           // latest could not be verified
	SkipFileUntracked     SkipCategory = "file_untracked"       // owning file not tracked by git
	SkipDockerConstraint  SkipCategory = "docker_constraint"    // digest-pin / ARG / untagged / latest tag

	// --- apply-layer decisions (apply_*.go) ---
	SkipSourceUnresolvable SkipCategory = "source_unresolvable" // manifest line pattern not recognized
	SkipSourceMismatch     SkipCategory = "source_mismatch"     // current value not found where expected
	SkipNoChange           SkipCategory = "no_change"           // replacement produced no diff
	SkipNoGoSource         SkipCategory = "no_go_source"        // content/tooling module, no Go source
	SkipReplaceDirective   SkipCategory = "replace_directive"   // go.mod replace present
	SkipNoFixedVersion     SkipCategory = "no_fixed_version"    // advisory with no known fixed version
	SkipConflict           SkipCategory = "conflict"            // conflicting desired versions within a module

	// SkipOther is the fallback for a decision not yet given its own category.
	SkipOther SkipCategory = "other"
)

type SkippedDep

type SkippedDep struct {
	Dep      supplychain.Dependency
	Category SkipCategory
	Reason   string
}

SkippedDep records a dependency that was not updated. Category is the typed classification of the decision (the source of truth); Reason is its human-readable presentation, kept verbatim so existing output is unchanged.

func FilterUpdateCandidates

func FilterUpdateCandidates(deps []supplychain.Dependency, cfg UpdateConfig, trackedFiles map[string]bool) (candidates []supplychain.Dependency, skipped []SkippedDep)

FilterUpdateCandidates separates deps into actionable candidates and skipped. Each skipped dep gets an explicit reason string.

type ToolchainDependency

type ToolchainDependency struct {
	Ecosystem    string // "golang"
	Name         string // "go"
	Version      string // "1.26.1"
	BuilderImage string // "docker.io/library/golang:1.26.1-alpine3.23"
	Dockerfile   string // repo-relative Dockerfile path
	ModuleDir    string // repo-relative module dir
}

ToolchainDependency records a resolved build toolchain for reporting and SBOM.

type UpdateConfig

type UpdateConfig struct {
	RootDir    string
	OutputDir  string // default ".stagefreight/deps/" — overwrites existing artifacts
	DryRun     bool
	Bundle     bool         // generate .tgz
	Verify     bool         // run tests after update (default true)
	Vulncheck  bool         // run govulncheck after update (default true)
	Ecosystems []string     // filter by ecosystem (empty = all)
	Policy     string       // freshness SCOPE: "all" (default), "security"
	MaxUpdate  string       // update-type CEILING: "major" | "minor" (default) | "patch"
	Ignore     []VulnIgnore // accepted-risk advisory suppressions (id/reason/until)
	Writer     io.Writer    // section-card output target (default os.Stderr); the deps progress card renders here
}

UpdateConfig holds configuration for the dependency update command.

type UpdateResult

type UpdateResult struct {
	Applied           []AppliedUpdate
	Skipped           []SkippedDep
	Toolchains        []ToolchainDependency // resolved build toolchains for SBOM/reporting
	Verified          bool
	VerifyLog         string
	VerifyErr         error
	Artifacts         []string
	ArtifactErr       error    // non-nil if artifact generation failed (non-fatal)
	TouchedModuleDirs []string // repoRoot-relative Go module dirs that were updated
	FilesChanged      []string // files modified by updates (go.mod, go.sum, Dockerfiles)
}

UpdateResult holds the outcome of a dependency update run.

func Update

Update resolves, filters, applies, verifies, and generates artifacts for dependency updates.

type VulnIgnore added in v0.7.0

type VulnIgnore struct {
	ID     string
	Reason string
	Until  string // YYYY-MM-DD; empty = no expiry
}

VulnIgnore is a single accepted-risk advisory suppression. Mirrors config's DependencyIgnore (mapped by the CLI) so the dependency engine stays config-agnostic.

Jump to

Keyboard shortcuts

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