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:
github.com/temporalio/deputy/internal/pin/githubactions: replaces mutable version tags with commit SHAs. Includes fork/imposter commit detection via the GitHub API. Resolution uses the git protocol (ls-remote).
github.com/temporalio/deputy/internal/pin/container: appends sha256 digest pins to Dockerfile FROM statements, workflow container/services fields, and docker:// uses. Resolution uses OCI registry HEAD requests.
github.com/temporalio/deputy/internal/pin/mise: replaces fuzzy tool version selectors in mise.toml-family configs and asdf .tool-versions files with exact, reproducible versions where Deputy can resolve them natively or through an explicitly configured mise fallback.
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 ¶
- func DedupeKey(r Ref) string
- func IsCommitSHA(s string) bool
- func IsSymlink(d fs.DirEntry) bool
- func IsWorkflowFile(relPath string) bool
- func ShouldSkipDir(name string) bool
- type Options
- type PinStatus
- type Ref
- type Report
- func Check(ctx context.Context, root *os.Root, opts Options, strategies ...Strategy) (*Report, error)
- func Pin(ctx context.Context, root *os.Root, opts Options, strategies ...Strategy) (*Report, error)
- func PinUpdate(ctx context.Context, root *os.Root, opts Options, strategies ...Strategy) (*Report, error)
- func Verify(ctx context.Context, root *os.Root, opts Options, strategies ...Strategy) (*Report, error)
- type Result
- type Stats
- type Strategy
- type Update
- type Verification
- type VerificationMode
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DedupeKey ¶
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 ¶
IsCommitSHA reports whether s is a 40-character hexadecimal Git commit SHA.
func IsWorkflowFile ¶
IsWorkflowFile checks if a relative path is a GitHub Actions workflow file.
func ShouldSkipDir ¶
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 ¶
DisplayName returns the full dependency name including subpath.
func (Ref) IsSHAPinned ¶
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 ¶
Pin discovers pinnable references, resolves them to immutable pins, optionally verifies them, and rewrites the files.
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. |