checks

package
v0.0.13 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package checks implements the structural, misleading-sha, and resolver-bound validators run against parsed workflows.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnrichImpostorFindings

func EnrichImpostorFindings(ctx context.Context, report *Report, tl *tag.Lister, r ReachabilityChecker, pool *pinpool.Pool)

EnrichImpostorFindings walks the report and attaches a recommended release to every ImpostorCommit finding when one is available. Mutates findings in place. Safe to call when tl or r is nil — becomes a no-op so non-network code paths (tests, --offline) don't trigger lookups.

Findings that have been walked are also marked via RecommendedSearched so renderers can distinguish "didn't look" from "looked and found nothing" — the latter is itself useful signal (e.g. an action whose entire release flow detaches tag commits from any branch, warranting harder escalation to the publisher).

func FindRecommendedRelease

func FindRecommendedRelease(ctx context.Context, tl *tag.Lister, r ReachabilityChecker, pool *pinpool.Pool, owner, repo string) (recTag, sha string)

FindRecommendedRelease walks the action repo's tags newest-first and returns the first stable release whose commit is reachable from a branch. It's the remediation half of the ImpostorCommit detection: when we flag a pinned SHA as orphaned, this answers "what should the user re-pin to?"

Returns ("", "") when no qualifying tag is found within the bounded walk (e.g. the action has never tagged a reachable release, or all recent releases are also orphaned and the user should escalate to the publisher).

func NewPrewarmedResolver

func NewPrewarmedResolver(r *resolve.Resolver, live []dep.Dependency, reach []resolve.ReachabilityResult, extraReach ...[]resolve.ReachabilityResult) *prewarmedResolver

NewPrewarmedResolver primes the adapter with the live resolution of refs and a pre-computed reachability sweep. Pass live==nil when ResolveAllRecursive failed; checks that need a ref will fail open. extraReach carries reach results for SHAs outside the canonical lockfile sweep — typically the observed SHA of a moved ref.

Types

type Category

type Category string

Category classifies the state of a workflow or action dependency. The string values are part of the schema surfaced to consumers (SARIF rule IDs, JSON output, doc URL slugs); the frozen-strings test guards against accidental renames.

const (
	// NotPinned means the workflow has action refs but no
	// corresponding dependencies entry in the lockfile.
	NotPinned Category = "not-pinned"
	// ShaAsRef means a dependency is pinned to a bare SHA with no
	// human-readable tag ref alongside it.
	ShaAsRef Category = "sha-as-ref"
	// RefChanged means the workflow uses: ref was manually changed
	// (e.g. v6.2.0 → v6) and the lockfile no longer matches.
	RefChanged Category = "ref-changed"
	// RefMoved means the upstream tag now resolves to a different
	// SHA than what the lockfile has recorded.
	RefMoved Category = "ref-moved"
	// Stale means the pinned SHA no longer matches what the ref
	// resolves to today.
	Stale Category = "stale"
	// ImpostorCommit means the pinned SHA is not in the ref's git
	// history (possible fork-network commit). Matches zizmor's
	// impostor-commit audit ID.
	ImpostorCommit Category = "impostor-commit"
	// MisleadingSHA means a ref looks like a SHA but resolves to a
	// different commit.
	MisleadingSHA Category = "misleading-sha"
	// LockfileForgery means the pinned SHA is not an ancestor of the
	// current ref — the lockfile entry was likely injected or
	// tampered with.
	LockfileForgery Category = "lockfile-forgery"
	// Valid means the dependency is pinned and verified.
	Valid Category = "valid"
	// RunOnly means the workflow has no action refs (only run:
	// steps), so pinning is not applicable.
	RunOnly Category = "run-only"
	// AncestryUnknown means the Compare API couldn't decide whether
	// the pinned SHA is in the ref's history (typically rate-limited
	// or transient error). Non-blocking diagnostic: we know the SHAs
	// differ but can't classify the move as benign-but-known
	// (ref-moved) vs. tampered (lockfile-forgery).
	AncestryUnknown Category = "ancestry-unknown"
	// ReachabilityUnknown means branch_commits couldn't decide
	// whether the pinned SHA is still reachable from any branch in
	// the upstream repo (resolver failure, GraphQL rate limit, etc).
	// Non-blocking diagnostic: surfaced so consumers can retry rather
	// than treating the dep as verified.
	ReachabilityUnknown Category = "reachability-unknown"
	// OnboardingRequired means a `check --no-onboard` run encountered a
	// workflow (or an action within one) that has no existing entry in the
	// lockfile. Under --no-onboard the tool refuses to add new entries: the
	// workflow/action is skipped and surfaced rather than silently pinned.
	// Already-tracked entries are still re-pinned. The operator must onboard
	// explicitly (run `gh actions-lock check` without --no-onboard) to add it.
	OnboardingRequired Category = "onboarding-required"
	// VersionRef is an informational nudge: a dependency is pinned with a
	// ref that is not a full semver tag (e.g. v4, v3.1, main). Full semver
	// tags (v4.2.1) each resolve to exactly one commit, making the lock
	// comment durable across re-pins.
	VersionRef Category = "version-ref"
	// LocalAction means the workflow uses at least one local path action
	// (uses: ./some-path). Lockfile onboarding is not supported for
	// workflows that reference local actions — the entire workflow is
	// skipped.
	LocalAction Category = "local-action"
)

func (Category) IsInconclusive

func (c Category) IsInconclusive() bool

IsInconclusive reports whether c represents a diagnostic that couldn't reach a verdict (network/rate-limit fallback). These are surfaced as warnings but are not blocking: consumers (e.g. Dependabot FindingMapper) treat them as "scan inconclusive, retry" rather than "lockfile is bad".

type CheckResolver

type CheckResolver interface {
	// ResolveRef returns the live SHA for owner/repo@ref. ok=false means
	// the resolver could not answer (network failure, unknown ref); checks
	// fail open on that.
	ResolveRef(owner, repo, ref string) (sha string, ok bool)
	// PeelTagObject reports whether a hex SHA names an annotated tag
	// object (or chain of tag-of-tag) and, if so, returns the commit OID
	// it ultimately points at.
	PeelTagObject(ctx context.Context, owner, repo, sha string) (commit string, ok bool)
	// CheckAncestry asks whether candidate is an ancestor of head and
	// returns a short human-readable detail alongside the status — the
	// rate-limit or compare-base detail callers surface to operators.
	CheckAncestry(ctx context.Context, owner, repo, candidate, head string) (resolve.AncestryStatus, string)
	// CheckReachability asks whether sha is reachable from ref's history.
	CheckReachability(owner, repo, sha, ref string) resolve.ReachabilityStatus
}

CheckResolver is the surface the resolver-bound checks need. The production implementation is *prewarmedResolver; tests use stubs.

type Confidence

type Confidence string

Confidence is how certain the producer is the finding is real, modeled on zizmor's audit output.

const (
	// ConfidenceLow marks a signal that could not be fully verified
	// (resolver failure, reachability inconclusive).
	ConfidenceLow Confidence = "low"
	// ConfidenceMedium marks a signal inferred from a fallback
	// (tag-object peel, ancestry unknown due to rate limit).
	ConfidenceMedium Confidence = "medium"
	// ConfidenceHigh marks a signal resting on authoritative data
	// (exact SHA comparison, upstream reachability answer).
	ConfidenceHigh Confidence = "high"
)

type Finding

type Finding struct {
	// WorkflowPath is the path to the workflow file.
	WorkflowPath string
	// Category of the finding.
	Category Category
	// Severity of the finding.
	Severity Severity
	// Confidence of the finding — see the Confidence type docs. Always
	// populated at construction; an empty value is a bug and the
	// no-empty-confidence test will catch it.
	Confidence Confidence
	// ActionRef is the action reference this finding relates to (nil for workflow-level findings).
	ActionRef *parserlock.ActionRef
	// Dependency is the existing pinned dep if any.
	Dependency *dep.Dependency
	// ParentNWO is the dep key of the direct action that pulls in this transitive dep (empty if direct).
	ParentNWO string
	// Detail is a human-readable explanation.
	Detail string
	// Remediation describes what the check command can do about it.
	Remediation string
	// ObservedSHA is the SHA the resolver got at scan time, recorded when
	// it differs from the pinned SHA (e.g. ref-moved, misleading-sha,
	// lockfile-forgery).
	ObservedSHA string
	// DocURL points to docs explaining the finding. Populated by the
	// engine adapter so it's parity-aligned with the editor's
	// codeDescription link; "" when no URL is mapped.
	DocURL string
	// RecommendedTag is the most recent stable tag whose commit is
	// reachable from a branch, populated for unreachable-SHA findings
	// (ImpostorCommit) when one can be found. Empty otherwise.
	RecommendedTag string
	// RecommendedSHA is the commit SHA the recommended tag points to.
	RecommendedSHA string
	// RecommendedSearched is true when the release walk ran for this
	// finding (regardless of outcome). Lets renderers distinguish
	// "we didn't look" from "we looked and found nothing."
	RecommendedSearched bool
}

Finding represents a single diagnosed issue (or clean bill) for a workflow.

func RunChecks

RunChecks evaluates all enabled validators against the given parsed workflow and returns findings in catalog order. The lockfile snapshot scopes the structural checks; the resolver enables the resolver-bound checks (misleading-sha, ref-moved, forgery, impostor). When r is nil, resolver-bound checks are skipped silently.

Returned findings have their primitive fields populated, plus ActionRef (for direct uses) and Dependency (for ref-tied entries). DocURL and ParentNWO are attached by the caller (diagnoseOneParsed) because they need lookup tables runChecks doesn't carry.

func (*Finding) DepKey

func (f *Finding) DepKey() string

DepKey returns a dependency identifier for display grouping.

func (*Finding) IsValid

func (f *Finding) IsValid() bool

IsValid returns true for findings that don't represent integrity violations.

func (*Finding) IsWarning

func (f *Finding) IsWarning() bool

IsWarning returns true for findings that should render as warnings (not errors).

type InventoryEntry

type InventoryEntry struct {
	Dep    dep.Dependency
	File   string
	Direct bool
	// Parents lists the dep keys of parent composite actions that pull in this
	// transitive dependency. Empty for direct dependencies.
	Parents []string
}

InventoryEntry describes a single dependency with context.

type ParsedWorkflow

type ParsedWorkflow struct {
	Path          string
	Refs          []parserlock.ActionRef
	LocalPaths    []string
	ExistingDeps  []dep.Dependency
	ParseWarnings []string
	LoadErr       error
	DepsErr       error
	// Resolved, when true, instructs DiagnoseParsed to run this
	// workflow's diagnostics with a nil resolver. Network-bound checks
	// (ref-moved, impostor-commit) are skipped and the engine relies on
	// purely structural validation against the on-disk lockfile. Caller
	// is asserting "this workflow is already fully resolved" — typically
	// set on the fast path when every direct ref in the workflow is
	// already recorded in the lockfile.
	Resolved bool
	// SkipReachWhenUnchanged, when true, instructs DiagnoseParsed to skip
	// the per-dep reachability network call for any ExistingDep whose
	// (NWO, Ref, SHA) matches an entry in the freshly-resolved live deps
	// for this workflow. A Reachable result is synthesized in place. This
	// is the per-workflow analogue of the cmd-level fast path: when at
	// least one direct ref is new/changed (so the workflow couldn't be
	// fully trusted), the remaining unchanged pins still don't need a
	// fresh network reachability sweep on every run. Callers should leave
	// this false when --rescan or an equivalent "verify everything" flag
	// is in effect.
	SkipReachWhenUnchanged bool
}

ParsedWorkflow holds the per-workflow parse result that both phases need. LoadErr / DepsErr capture early failures so DiagnoseParsed can surface them as findings without re-loading the file.

func (ParsedWorkflow) IsFullyRecorded

func (pw ParsedWorkflow) IsFullyRecorded() bool

IsFullyRecorded returns true when every direct ref has a matching lockfile entry — the steady-state happy path.

func (ParsedWorkflow) PartitionRefs

func (pw ParsedWorkflow) PartitionRefs() (recorded, unrecorded []parserlock.ActionRef)

PartitionRefs splits refs into recorded (matching a lockfile entry by NWO@Ref or NWO@SHA) and unrecorded (need network resolution). When an error prevented loading refs or deps, everything is unrecorded.

func (ParsedWorkflow) RecordedDeps

func (pw ParsedWorkflow) RecordedDeps(recorded []parserlock.ActionRef) []dep.Dependency

RecordedDeps returns the subset of ExistingDeps whose NWO@Ref or NWO@SHA matches one of the given recorded refs.

type ReachabilityChecker

type ReachabilityChecker interface {
	CheckReachability(ctx context.Context, owner, repo, sha, ref string) resolve.ReachabilityResult
}

ReachabilityChecker is the subset of resolve.Resolver needed to verify that a tag's commit is reachable from a branch in the action repo. Defined as an interface so tests can stub without a real resolver.

type Report

type Report struct {
	Workflows []WorkflowReport
	// RepoFindings are findings that apply to the repository as a whole
	// (not to any individual workflow).
	RepoFindings []Finding
}

Report aggregates all workflow reports for a check run.

func (*Report) HasInconclusive

func (r *Report) HasInconclusive() bool

HasInconclusive reports whether the report contains any inconclusive findings (reachability-unknown, ancestry-unknown). These are treated as warnings by default, but callers that need a strict gate (e.g. --rescan) can use this to fail when verification couldn't complete.

func (*Report) IsValid

func (r *Report) IsValid() bool

IsValid returns true if all workflows in the report pass validation.

func (*Report) Summary

func (r *Report) Summary() map[Category]int

Summary returns counts by category across all workflows.

func (*Report) WorkflowsNeedingAttention

func (r *Report) WorkflowsNeedingAttention() []WorkflowReport

WorkflowsNeedingAttention returns only workflows with actionable findings.

type Severity

type Severity string

Severity indicates how serious a finding is if it represents a real problem. Pair with Confidence to express how strongly the tool stands behind the call.

const (
	// SeverityOK means the finding represents a clean state — no
	// action needed.
	SeverityOK Severity = "ok"
	// SeverityInfo is purely informational and does not require
	// action.
	SeverityInfo Severity = "info"
	// SeverityWarning indicates a concern worth surfacing but not
	// blocking on.
	SeverityWarning Severity = "warning"
	// SeverityError indicates a blocking issue the operator must
	// resolve.
	SeverityError Severity = "error"
)

type WorkflowReport

type WorkflowReport struct {
	Path     string
	Findings []Finding
	// ActionRefs are all action references found in the workflow.
	ActionRefs []parserlock.ActionRef
	// Deps are the existing pinned dependencies (nil if not pinned).
	Deps []dep.Dependency
	// Inventory lists all dependencies with direct/transitive classification.
	Inventory []InventoryEntry
	// ParseWarnings from ExtractActionRefs (e.g. malformed uses: lines).
	ParseWarnings []string
}

WorkflowReport aggregates all findings for a single workflow file.

func (*WorkflowReport) CountByCategory

func (r *WorkflowReport) CountByCategory(c Category) int

CountByCategory returns the number of findings with the given category.

func (*WorkflowReport) IsValid

func (wr *WorkflowReport) IsValid() bool

IsValid returns true if no findings represent integrity violations.

func (*WorkflowReport) NeedsAttention

func (r *WorkflowReport) NeedsAttention() bool

NeedsAttention returns true if this workflow has any non-OK findings.

Jump to

Keyboard shortcuts

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