pin

package
v0.0.0-...-80ccc60 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package pin implements dependency pinning for supply chain security.

Pinning replaces mutable version references with immutable identifiers, preventing tag-repointing and version-substitution attacks. The package provides a pluggable Strategy interface where ecosystem-specific implementations handle discovery, resolution, verification, and rewriting. Pin and PinUpdate reports include scoped changed-file metadata and a unified patch generated from only the files that Deputy successfully pinned or updated. Callers should use those fields instead of a repository-wide git diff when applying or forwarding pin-mode changes.

Supported ecosystems

Each ecosystem is implemented as a subpackage:

Future ecosystems

The Strategy interface is designed for these ecosystems to be added as new subpackages without modifying the orchestrator. Each shares the same structural pattern: a mutable reference that can be replaced with an immutable one.

Terraform modules: git-sourced modules (git::https://...?ref=v1.0) use mutable tags. Pin to commit SHA, same as GitHub Actions. HCL rewriting needed. Lockfile (.terraform.lock.hcl) hashes cover registry modules but not git-sourced ones.

Helm charts: OCI-based charts use mutable tags, same as container images. Digest pinning via the OCI registry applies directly. Chart.yaml and values files need rewriting.

CI script tool installs: commands like "go install pkg@latest", "npx tool@^2", "pip install black==24.3" in CI scripts and Dockerfiles use mutable versions. Pin to exact version + hash where the ecosystem supports it (pip --require-hashes, npm --integrity, go.sum).

Git submodules: .gitmodules can reference branches. Pin to commit SHA. Resolution via git ls-remote (same as GitHub Actions).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DedupeKey

func DedupeKey(r Ref) string

DedupeKey returns a stable key for deduplicating discovered refs. Uses DisplayName (which includes subpath) to avoid colliding refs like github/codeql-action/init and github/codeql-action/analyze.

func IsCommitSHA

func IsCommitSHA(s string) bool

IsCommitSHA reports whether s is a 40-character hexadecimal Git commit SHA.

func IsSymlink(d fs.DirEntry) bool

IsSymlink reports whether a directory entry is a symbolic link.

func IsWorkflowFile

func IsWorkflowFile(relPath string) bool

IsWorkflowFile checks if a relative path is a GitHub Actions workflow file.

func ShouldSkipDir

func ShouldSkipDir(name string) bool

ShouldSkipDir reports whether a directory should be excluded from dependency discovery walks. Skips version control, dependency caches, and hidden directories (except .github which contains workflows).

Types

type Options

type Options struct {
	DryRun bool
	// SkipVerification disables provenance checks. Equivalent to
	// Verification == VerificationOff; retained for backward compatibility.
	SkipVerification bool
	// Verification selects how provenance findings are handled. Empty defaults
	// to VerificationWarn.
	Verification VerificationMode
	Concurrency  int      // parallel API requests (default: 4)
	Exclude      []string // glob patterns for action names to skip
}

Options configures the pin operation.

type PinStatus

type PinStatus string

PinStatus indicates the outcome of pinning a dependency.

const (
	StatusPinned        PinStatus = "pinned"
	StatusAlreadyPinned PinStatus = "already-pinned"
	StatusUpdated       PinStatus = "updated"
	StatusUnpinned      PinStatus = "unpinned"
	StatusSkipped       PinStatus = "skipped"
	StatusError         PinStatus = "error"
	StatusVerified      PinStatus = "verified"
	StatusSuspicious    PinStatus = "suspicious"
)

type Ref

type Ref struct {
	// Ecosystem identifies the pinning ecosystem (e.g., "github-actions").
	Ecosystem string `json:"ecosystem"`

	// Name is the dependency identifier (e.g., "actions/checkout").
	Name string `json:"name"`

	// Subpath is an optional sub-path within the dependency (e.g., for
	// owner/repo/subpath@ref in GitHub Actions).
	Subpath string `json:"subpath,omitempty"`

	// Version is the current version reference (tag, branch, SHA, etc.).
	Version string `json:"version"`

	// FilePath is the root-relative path to the file containing this reference.
	FilePath string `json:"filePath"`

	// Raw is the original reference string as it appears in the file.
	Raw string `json:"raw"`

	// LockedVersion is an optional exact version from a lockfile associated with
	// the source reference. Strategies may prefer it when pinning would otherwise
	// resolve a fuzzy request to a newer upstream version.
	LockedVersion string `json:"-"`

	// Options carries ecosystem-specific tool options parsed from the source
	// (e.g. mise tool options like provider/exe/matching). Strategies use it to
	// decide resolvability; it is nil for ecosystems that have no such options.
	Options map[string]string `json:"options,omitempty"`
}

Ref is a discovered dependency reference that may be pinnable.

func (Ref) DisplayName

func (r Ref) DisplayName() string

DisplayName returns the full dependency name including subpath.

func (Ref) IsSHAPinned

func (r Ref) IsSHAPinned() bool

IsSHAPinned reports whether Version is a 40-character hex commit SHA.

type Report

type Report struct {
	Results      []Result `json:"results"`
	Stats        Stats    `json:"stats"`
	ChangedFiles []string `json:"changedFiles,omitempty"`
	Patch        string   `json:"patch,omitempty"`
}

Report aggregates all pin results.

func Check

func Check(ctx context.Context, root *os.Root, opts Options, strategies ...Strategy) (*Report, error)

Check discovers pinnable refs and reports which are pinned and which are not. It makes no API calls and writes no files; purely local file scanning.

func Pin

func Pin(ctx context.Context, root *os.Root, opts Options, strategies ...Strategy) (*Report, error)

Pin discovers pinnable references, resolves them to immutable pins, optionally verifies them, and rewrites the files.

func PinUpdate

func PinUpdate(ctx context.Context, root *os.Root, opts Options, strategies ...Strategy) (*Report, error)

PinUpdate re-pins already-pinned refs to the latest version in their major version channel. Unpinned refs are skipped.

func Verify

func Verify(ctx context.Context, root *os.Root, opts Options, strategies ...Strategy) (*Report, error)

Verify checks existing SHA-pinned refs for provenance (fork/imposter commits, signature status). It makes API calls but writes no files. Unpinned refs are skipped.

type Result

type Result struct {
	Ref          Ref           `json:"ref"`
	Status       PinStatus     `json:"status"`
	PinnedValue  string        `json:"pinnedValue,omitempty"`  // e.g., the commit SHA
	VersionTag   string        `json:"versionTag,omitempty"`   // e.g., the semver tag for comment
	PreviousRef  string        `json:"previousRef,omitempty"`  // the original version
	Verification *Verification `json:"verification,omitempty"` // fork/imposter check result
	Reason       string        `json:"reason,omitempty"`       // human-readable status detail
	Error        string        `json:"error,omitempty"`
}

Result captures the outcome for a single dependency.

type Stats

type Stats struct {
	Total         int `json:"total"`
	Pinned        int `json:"pinned"`
	AlreadyPinned int `json:"alreadyPinned"`
	Updated       int `json:"updated,omitempty"`
	FilesChanged  int `json:"filesChanged,omitempty"`
	Unpinned      int `json:"unpinned,omitempty"`
	Skipped       int `json:"skipped"`
	Errors        int `json:"errors"`
	Verified      int `json:"verified"`
	Suspicious    int `json:"suspicious"`
	// Flagged counts refs pinned despite a provenance concern (warn mode):
	// likely-imposter findings that did not block the pin.
	Flagged int `json:"flagged,omitempty"`
	// Unverifiable counts refs whose provenance could not be checked (rate
	// limit / network / missing token); these are pinned with a warning.
	Unverifiable int `json:"unverifiable,omitempty"`
}

Stats summarizes the pin operation outcomes.

type Strategy

type Strategy interface {
	// Ecosystem returns the ecosystem identifier (e.g., "github-actions",
	// "container-image").
	Ecosystem() string

	// IsPinned reports whether the ref is already pinned to an immutable
	// reference for this ecosystem (e.g., 40-char commit SHA for GitHub
	// Actions, sha256 digest for container images).
	IsPinned(ref Ref) bool

	// ShouldSkip reports whether the ref cannot or should not be pinned
	// (e.g., expression refs, scratch images, dynamic references).
	// Returns true and a reason string if the ref should be skipped.
	ShouldSkip(ref Ref) (skip bool, reason string)

	// Discover finds all pinnable references in the filesystem.
	Discover(ctx context.Context, fsys scalibrfs.FS) ([]Ref, error)

	// Resolve converts a mutable version reference to an immutable pin.
	// Returns the pinned value (e.g., commit SHA, sha256 digest) and a
	// human-readable version tag to preserve alongside the pin.
	Resolve(ctx context.Context, ref Ref) (pinnedValue, versionTag string, err error)

	// Verify checks whether an existing pin is trustworthy (e.g., not a
	// fork/imposter commit, valid signature). Returns nil Verification
	// when provenance checking is not available for this ecosystem.
	Verify(ctx context.Context, ref Ref) (*Verification, error)

	// Rewrite applies pin updates to files within the given root directory.
	// Path is root-relative. Implementations must preserve file formatting,
	// comments, and unrelated content.
	Rewrite(root *os.Root, path string, updates []Update) error

	// ResolveUpdate re-resolves an already-pinned ref to check for newer
	// versions. Returns the new pinned value, new version tag, and current
	// version tag. If already at latest, returns pinnedValue equal to
	// ref.Version (caller detects no-op).
	ResolveUpdate(ctx context.Context, ref Ref) (pinnedValue, newVersionTag, currentVersionTag string, err error)
}

Strategy defines how a specific ecosystem handles pinning. Each supported ecosystem implements this interface to provide discovery, resolution, verification, and rewriting for its dependency format.

Implemented by:

  • [GitHubActionsStrategy]: commit SHA pins for workflow action uses
  • [ContainerStrategy]: sha256 digest pins for Dockerfiles and workflow containers

The interface is designed so new ecosystems can be added by implementing these methods without modifying the orchestrator. See doc.go for the roadmap of future ecosystems.

type Update

type Update struct {
	// Name is the dependency name (e.g., "actions/checkout" or
	// "actions/checkout/subpath").
	Name string

	// FromVersion is the exact original ref this update applies to (e.g. "v4").
	// Rewriters MUST match it so that multiple versions of the same dependency
	// in one file (e.g. actions/checkout@v4 and @v6) each pin to their own SHA
	// rather than all collapsing to one. Empty means "match any version".
	FromVersion string

	// PinnedValue is the immutable pin (e.g., 40-char commit SHA).
	PinnedValue string

	// VersionTag is the human-readable version for the comment (e.g., "v4.2.2").
	VersionTag string
}

Update describes a single reference to rewrite in a file.

type Verification

type Verification struct {
	Signed          bool   `json:"signed"`
	SignatureValid  bool   `json:"signatureValid"`
	SignatureReason string `json:"signatureReason,omitempty"`
	OnBranch        bool   `json:"onBranch"`
	BranchName      string `json:"branchName,omitempty"`
	// IsForkCommit indicates a likely imposter: the commit is unsigned and not
	// reachable from the default branch (or absent from the repo entirely).
	IsForkCommit bool `json:"isForkCommit"`
	// Unverifiable indicates provenance could not be checked (rate limit,
	// network error, missing token, renamed repo). This is NOT an imposter
	// signal; it means "unknown", and must never be treated as fatal.
	Unverifiable bool     `json:"unverifiable,omitempty"`
	CommitAuthor string   `json:"commitAuthor,omitempty"`
	Warnings     []string `json:"warnings,omitempty"`
}

Verification captures the result of provenance checks on a pinned reference.

type VerificationMode

type VerificationMode string

VerificationMode controls how provenance findings affect the pin operation.

const (
	// VerificationWarn (the default) verifies and reports provenance concerns
	// but still pins every ref and exits 0. A floating tag already resolves to
	// the pinned SHA at runtime, so freezing it is no riskier than leaving it
	// floating; warnings surface the findings for review.
	VerificationWarn VerificationMode = "warn"
	// VerificationError pins only refs that pass verification; a flagged
	// (likely-imposter) ref is left unpinned and reported, and the operation
	// exits non-zero. Intended for strict CI gates.
	VerificationError VerificationMode = "error"
	// VerificationOff disables provenance checks entirely.
	VerificationOff VerificationMode = "off"
)

Directories

Path Synopsis
Package mise implements the pin.Strategy for mise-en-place toolchains.
Package mise implements the pin.Strategy for mise-en-place toolchains.

Jump to

Keyboard shortcuts

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