Skip to content

audit

Scan workflow run: blocks and action source code for runtime fetch patterns that bypass SHA pinning.

Workflows are discovered under .github/workflows/, .forgejo/workflows/, and .gitea/workflows/ (whichever exist are all scanned). Forgejo and Gitea use GitHub-compatible workflow syntax.

Terminal window
pinprick audit
pinprick audit /path/to/repo
  • Shell commands in workflow run: blocks and composite action.yml steps, plus statically referenced non-vendored .sh, .bash, .zsh, and .ps1 helpers
  • JavaScript and TypeScript entrypoints declared by action metadata, plus statically referenced helpers, including minified bundles
  • Python helpers statically referenced by composite action steps
  • Dockerfiles named by a reachable container action’s runs.image field. Unreferenced example and test Dockerfiles are not executed by consumers and are not scanned

For the full list of every rule, including examples and severity, see the Detections reference.

pinprick skips exact action identities whose owner/repo[/subpath]@sha is already known to be clean. The check consults three sources, in order, and reports which one answered:

  • bundled — ships with the pinprick binary from audited-actions/ in the repo; entries from other detection-rules versions are ignored
  • local cache — written to $XDG_CACHE_HOME/pinprick/audited/ (default ~/.cache/pinprick/audited/) after a complete, clean live scan under the default runtime trust policy; entries from other scanner or detection-rules versions are ignored
  • pinprick.rs — fetched from the public audited-actions list (opt-in via fetch-remote = true in .pinprick.toml); signed entries from other detection-rules versions are ignored

See Audited Actions for details on how the list works and how to contribute.

When no GitHub token is available, audit scans workflow run: blocks and local actions referenced with uses: ./... or uses: $/.... Remote action source code is not fetched. External actions already covered by a valid bundled, local, or remote audited-actions verdict remain covered; any other external action makes the result incomplete and exits 2 rather than claiming the repository is clean.

$/path binds a same-repository action to the exact workflow commit on GitHub Actions runner 2.336.0 or newer. ./path resolves from the runner workspace. Pinprick statically scans both forms relative to the repository path supplied on the command line.

Missing entrypoints, symlinked source, truncated trees that omit the selected action metadata, resource limits, unsupported uses: targets, and nested action or reusable-workflow references that cannot be followed all make the scan incomplete. Human, JSON, and SARIF modes share this verdict.

  • Default: colored human-readable output with severity buckets
  • --json: machine-readable JSON for CI integration, including rules_version, the detection semantics used for the report
  • --sarif: SARIF 2.1.0 for upload to GitHub code scanning
  • --verbose: also report allowed matches (fetches that fired a rule but were dropped because the URL is versioned, data-shaped, piped to jq, checksum-verified, or matched by trusted-hosts)
  • --no-repo-config: ignore the scanned repository’s .pinprick.toml and use the global config (or defaults)
  • --no-audited-catalog: ignore the audited-actions catalog (bundled, local cache, and remote) and scan every action fresh — pinprick’s own CI uses this to re-verify catalog entries against the current detection rules

For GitHub Actions CI, starhaven-io/pinprick-action wraps this command and optional SARIF upload.

The scanned repository’s own .pinprick.toml applies during an audit — ignore rules, trusted-hosts, and severity can all suppress findings. That’s the right behavior for your own CI, but when auditing a third-party repository it means the target sets its own audit policy. Whenever a repo-local config changes the results, pinprick prints a note to stderr saying what it suppressed; pass --no-repo-config to ignore the file entirely.

$ pinprick audit
Scanning .github/workflows/ci.yml
actions/checkout@9c091bb audited (bundled)
actions/upload-artifact@043fb46 audited (bundled)
Scanning .github/workflows/release.yml
actions/attest@59d8942 audited (bundled)
rust-lang/crates-io-auth-action@c6f97d4 audited (local cache)
Fetching Homebrew/actions/setup-homebrew@main (unpinned)
No runtime fetch risks found.
Audited 4 actions: 3 bundled, 1 local cache.
1 branch ref scanned. Pin to a SHA manually.

If the workflow uses sliding tags like @v4 instead of branch refs, the summary suggests the auto-fix:

1 sliding tag scanned. Run `pinprick pin` to resolve.

pinprick pin can auto-resolve sliding tags to exact SHAs. Branch refs (@main) require manual pinning because there’s no version target to resolve to.

Per-action status is colored by semantic category, not by source, so a clean audit looks like a wall of uniform green with only the exceptions popping out:

  • audited — green. Matched an entry in the bundled list, local cache, or pinprick.rs list. Remote catalog lookups still use the network.
  • Fetching / scanned fresh — blue. pinprick fetched the action source over the network and scanned it fresh this run.
  • (unpinned) / unpinned ref scanned — yellow. The ref is a branch (@main) or sliding tag (@v4) — pinprick scans the current tip but the trust does not carry across runs because the content can change.
  • ignored — dimmed. Skipped per ignore.actions in .pinprick.toml.

The summary at the end is up to three lines: the audited total (durably trusted), a separate unpinned line if any branch/sliding-tag refs were scanned, and an ignored line if any config-driven skips were applied.