Documentation
¶
Index ¶
- func ApplyIgnores(deps []supplychain.Dependency, ignores []VulnIgnore, now time.Time) []supplychain.Dependency
- func FetchAdvisories(ctx context.Context, fc forge.Forge, ref, rootDir string) ([]security.Advisory, error)
- func GenerateArtifacts(ctx context.Context, repoRoot, outputDir string, result *UpdateResult, ...) ([]string, error)
- func LoadAdvisories(rootDir string) ([]security.Advisory, error)
- func NormalizeSkipReason(reason string) string
- func RemediationSummaryMarkdown(evals []RemediationEvaluation) string
- func Verify(ctx context.Context, moduleDirs []string, repoRoot string, ...) (string, error)
- type AppliedUpdate
- type CandidateSet
- type CargoChurn
- type EnrichmentDetail
- type PolicyLayer
- type RemediationEvaluation
- type RemediationState
- type ResidualVuln
- type SkipCategory
- type SkippedDep
- type ToolchainDependency
- type UpdateConfig
- type UpdateResult
- type VulnIgnore
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 ¶
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
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
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
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
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 ¶
func Update(ctx context.Context, cfg UpdateConfig, deps []supplychain.Dependency) (*UpdateResult, error)
Update resolves, filters, applies, verifies, and generates artifacts for dependency updates.
type VulnIgnore ¶ added in v0.7.0
VulnIgnore is a single accepted-risk advisory suppression. Mirrors config's DependencyIgnore (mapped by the CLI) so the dependency engine stays config-agnostic.