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 NewPrewarmedResolver ¶
func NewPrewarmedResolver(r *resolve.Resolver, live []dep.Dependency) *prewarmedResolver
NewPrewarmedResolver primes the adapter with the live resolution of refs. Pass live==nil when ResolveAllRecursive failed; checks that need a ref will fail open.
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" // 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` 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" // SelfHostedRunner means at least one job in the workflow uses a // non-GitHub-hosted runner label (self-hosted, custom label, or runner // group). Lockfile onboarding is limited to workflows that run // entirely on GitHub-hosted runners. SelfHostedRunner Category = "self-hosted-runner" // ExpressionRunner means the workflow's runs-on uses a template // expression (e.g. ${{ matrix.os }}) that can't be resolved // statically. The workflow is skipped because we can't verify // the labels at parse time. ExpressionRunner Category = "expression-runner" )
func (Category) IsInconclusive ¶
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)
}
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
// 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 ¶
func RunChecks(ctx context.Context, pw ParsedWorkflow, lf parserlock.File, r CheckResolver) []Finding
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). 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.
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) 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
// NonHostedRunner is true when at least one job in the workflow
// uses a runs-on label that is not a known GitHub-hosted runner
// (self-hosted, custom label, runner group, or expression). These
// workflows are skipped from onboarding.
NonHostedRunner bool
// NonHostedLabels holds the specific non-hosted runner labels found
// in the workflow (populated only when NonHostedRunner is true).
NonHostedLabels []string
}
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 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 ¶
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) 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.