pinpoint

command module
v0.0.0-...-b387701 Latest Latest
Warning

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

Go to latest
Published: Sep 18, 2026 License: Apache-2.0 Imports: 2 Imported by: 0

README ΒΆ

πŸ“Pinpoint

Pinpoint is a GitHub Actions pinning manager. It scans the Actions code of a repository and reports the action references that are not pinned to a commit hash, pins them in place, and keeps the pinned ones up to date. The same scan can be exported as an in-toto attestation or as an SBOM, naming every action a repository runs.

Pinning actions to hashes is what keeps a workflow running the code it was reviewed with: tags and branches can be moved, commits cannot. A pinned reference keeps the version it points to in a trailing comment, which is the convention pinpoint reads and writes:

- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

Installing

Released binaries

Once released, binaries for the common platforms are published on the releases page. Each release ships with its SLSA provenance attestation and an SBOM, so a download can be verified before it runs.

With go install
go install github.com/carabiner-dev/pinpoint@latest
From source
git clone https://github.com/carabiner-dev/pinpoint
cd pinpoint
go build .

Authenticating

Pinpoint reads versions from the GitHub API. Export a token in GITHUB_TOKEN or GH_TOKEN to authenticate the calls: anonymous calls work for public repositories but are rate limited to a handful of scans per hour.

export GITHUB_TOKEN=$(gh auth token)

Commands that need no version data (check --offline, status --offline) make no API calls at all.

What gets scanned

Actions code is not only what sits in .github/workflows: repositories publish composite actions from their root, keep starter workflows under .github/workflow-templates and hold the workflows of a monorepo's subprojects wherever they belong. Every command scans them all.

Pinpoint finds them by where they sit and by what they hold. Every YAML file under a forge's workflows directory is a workflow and every action.yml in the tree is an action definition; on top of those, any other YAML file declaring the jobs it runs on an event is read as a workflow, and any file saying how an action runs is read as an action definition. The .git, vendor, node_modules and testdata directories are never walked, and the YAML a repository keeps for anything else is left alone.

Checking a repository

pinpoint check scans the Actions code of a directory (the current one by default) and lists the references that are not pinned, along with the version each one would be pinned to. It also reports the pinned references that have a newer release, covering both halves of keeping actions under control: pinning them and keeping them current.

$ pinpoint check

Action references not pinned to a commit hash:

  Workflow                    Line   Job       Action                Pin to
 ──────────────────────────────────────────────────────────────────────────────────────
  .github/workflows/ci.yaml     14   build     actions/setup-go@v6   v6.5.0 (924ae3a…)

Pinned references with a newer release available:

  Workflow                    Lines   Action             Using    Latest
 ──────────────────────────────────────────────────────────────────────────────
  .github/workflows/ci.yaml      12   actions/checkout   v5.0.0   v7.0.1 (3d3c42e…)

Error: found 1 action reference(s) not pinned to a hash

The command exits with an error when unpinned references are found, so it can gate a CI job. Outdated references are reported but do not fail the run.

Two flags control how much pinpoint asks the forge: --update=false stops the search for newer releases, and --offline makes no calls at all, only reporting which references are pinned.

Pinning the references

pinpoint pin rewrites the unpinned references in place, pinning each entry to the commit its version points at and recording the version in the trailing comment. The changes are listed first and confirmed with a question, so a run can be inspected before it touches the workflows:

$ pinpoint pin

  File                        Line   Action                Pin to
 ─────────────────────────────────────────────────────────────────────────────
  .github/workflows/ci.yaml     14   actions/setup-go@v6   v6.5.0 (924ae3a…)

Pin 1 action(s) to hashes? (y/N) y
Pinned 1 action reference(s) in 1 file(s).

Use --yes to skip the question, which is also what unattended runs need: with no one to answer, pinpoint stops instead of guessing.

Two flags widen what a run does:

  • --update pins the references to the latest release of each action instead of the version they are using, acting on what check reports as updatable.
  • --all looks at every reference instead of only the unpinned ones. Combined with --update it moves the whole repository to the newest releases.

References that cannot be pinned to a repository commit (local actions and those run from container images) are left untouched.

Seeing the whole picture

pinpoint status lists every external action reference: those pointing at other repositories or at container images, in workflows and in action definitions alike. Each row shows the version in use and whether the entry is pinned and up to date:

$ pinpoint status

  Workflow                    Line   Action                     Version    Pinned   Up to date
 ─────────────────────────────────────────────────────────────────────────────────────────────
  .github/workflows/ci.yaml     12   actions/checkout@08c690…   v5.0.0       βœ“          βœ—
  .github/workflows/ci.yaml     14   actions/setup-go@v6        v6           βœ—          βœ—
  .github/workflows/ci.yaml     19   docker://alpine@sha256:…   sha256:…     βœ“          ?
  .github/workflows/ci.yaml     21   docker://alpine:3.22       ?            βœ—          ?

References pinpoint cannot look up, container images among them, show a question mark in the update column. With --offline the column is dropped and the scan only reports which references are pinned. Status is a report, not a gate: the command exits cleanly whatever it finds.

Exporting an SBOM

With --format, status writes the scan as an SBOM on standard output instead of the table: --format=spdx renders SPDX 2.3 and --format=cyclonedx renders CycloneDX 1.7, both as JSON.

pinpoint status --format=spdx > actions.spdx.json

The document models the repository at its current commit as the root of the graph, with every external action version hanging from it as a development tool and connected to the workflow files that use it. Actions are identified by purls in the convention GitHub uses in its own SBOMs (pkg:githubactions/actions/checkout@08c6903…, pkg:oci/… for container actions), carry a VCS locator back to the repository hosting them, and are versioned with the tag their reference resolves to. Every version is verified against the forge: a reference that cannot be resolved fails the export, so an emitted SBOM always carries verified versions.

Attesting the results

pinpoint check --attest writes the full scan results (both the pinned and the unpinned references) as an unsigned in-toto attestation instead of the table. The subject is the scanned repository at its current commit; when the scan runs in a fork, pinpoint names the repository the fork was created from and notes the fork in the subject annotations.

pinpoint check --attest > pinning.intoto.json

Attestations are data, not verdicts, so the command exits cleanly when generating one, leaving the pass/fail decision to whoever evaluates it. The output can be signed and published with bnd.

Using pinpoint as a library

The pkg/pinpoint package exposes the same views the command line renders. ScanStatus describes the pinning state of every external action reference and BuildSBOM models the repository as a protobom graph:

import "github.com/carabiner-dev/pinpoint/pkg/pinpoint"

resolver, err := pinpoint.NewGitHubResolver()
if err != nil { /* … */ }

// The pinning state of every external action reference.
status, err := pinpoint.ScanStatus(ctx, resolver, ".")
for _, ref := range status.References {
    fmt.Println(ref.Reference.Uses, ref.Pinned, ref.Outdated)
}

// The same scan as a protobom document, ready for any of its serializers.
doc, err := pinpoint.BuildSBOM(ctx, resolver, ".")

The lower level pieces are available too: Scanner collects the references, CheckUpdates looks up the versions available for them and Updater computes and applies the pinning plans.

License

Copyright Carabiner Systems, Inc. Released under the Apache 2.0 license.

Documentation ΒΆ

Overview ΒΆ

Pinpoint checks that the actions referenced in GitHub Actions workflows are pinned to commit hashes.

Directories ΒΆ

Path Synopsis
internal
cmd
Package cmd implements the pinpoint command line interface.
Package cmd implements the pinpoint command line interface.
Package options defines the reusable option sets of the pinpoint commands.
Package options defines the reusable option sets of the pinpoint commands.
pkg
pinpoint
Package pinpoint scans repositories for GitHub Actions workflows and collects the action references used in them to check they are pinned to commit hashes.
Package pinpoint scans repositories for GitHub Actions workflows and collects the action references used in them to check they are pinned to commit hashes.

Jump to

Keyboard shortcuts

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