checks

package
v0.1.6 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 8 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 IsImmutableRef added in v0.1.6

func IsImmutableRef(ref string) bool

IsImmutableRef reports whether ref is a full semver tag (e.g. v4.2.1), which resolves to exactly one commit for its entire lifetime. Full semver pins are re-verified against upstream on the default path; mutable refs (v4, v4.2, branches) are trusted until --rescan because they legitimately move.

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 (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"
	// UnreachablePin means the pinned SHA is not an ancestor of, and
	// not reachable from, the ref's current head. The pin points at a
	// commit that was never in the ref's history or was dropped by an
	// upstream history rewrite (squash/force-push) — or the lockfile
	// entry was tampered with. The check can't distinguish those, so it
	// fails closed without asserting an attack.
	UnreachablePin Category = "unreachable-pin"
	// 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 (unreachable-pin).
	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"
	// StaleWorkflow is an informational, repo-level notice: the lockfile
	// records a workflow entry whose file no longer exists on disk. On a
	// default (full-directory) fix run these are pruned automatically; in
	// read-only mode they are surfaced but not removed. Non-blocking.
	StaleWorkflow Category = "stale-workflow"
	// SelfRepositoryAction means the workflow references a same-repo action via
	// the self repository `$/…` syntax. These resolve against the defining
	// repo at the running ref, so they are inherently pinned and need no
	// lockfile entry — always in compliance.
	SelfRepositoryAction Category = "self-repository-action"
	// InvalidSelfRepositoryRef means a `$/…` reference is malformed or its
	// target cannot be inspected. This includes a forbidden `@ref` suffix.
	InvalidSelfRepositoryRef Category = "invalid-self-repository-ref"
)

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)
}

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
	// Remediable is true for workflow-level findings that pinning can repair.
	Remediable bool
	// 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,
	// unreachable-pin).
	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

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.

func (*Finding) DepKey

func (f *Finding) DepKey() string

DepKey returns a dependency identifier for display grouping.

func (*Finding) IsRemediableNotPinned added in v0.1.6

func (f *Finding) IsRemediableNotPinned() bool

IsRemediableNotPinned reports whether pinning can resolve this finding.

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 are all remote dependency roots attributed to the workflow. This
	// includes refs found inside in-repo `$/…` actions.
	Refs []parserlock.ActionRef
	// RewriteRefs are workflow-YAML refs that pinning may rewrite. A ref also
	// used inside a `$/…` action is excluded because rewriting only the workflow
	// occurrence would leave the action file and lockfile out of sync.
	RewriteRefs        []parserlock.ActionRef
	LocalPaths         []string
	SelfRepositoryRefs []string
	// SelfRepositoryRefErrs holds malformed `$/…@ref` values (the invalid form).
	SelfRepositoryRefErrs        []string
	SelfRepositoryResolutionErrs []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
}

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

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 remote dependency roots attributed to the workflow,
	// including refs found inside in-repo `$/…` actions.
	ActionRefs []parserlock.ActionRef
	// RewriteRefs are the workflow-YAML refs eligible for source rewriting.
	RewriteRefs []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