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
  • JavaScript and TypeScript files (.js, .ts) inside each action’s source tree, including minified bundles
  • Python files (.py) inside each action’s source tree
  • Dockerfile and *.dockerfile files inside each action’s source tree

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

pinprick skips actions whose owner/repo@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
  • local cache — written to ~/.cache/pinprick/audited/ after a successful live scan on this machine
  • pinprick.rs — fetched from the public audited-actions list (opt-in via fetch-remote = true in .pinprick.toml)

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: ./.... Remote action source code is not fetched. This still catches the most common patterns in repository-owned workflow code, but misses JavaScript, Python, and Docker patterns inside remote actions.

  • Default: colored human-readable output with severity buckets
  • --json: machine-readable JSON for CI integration
  • --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)

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. No network work needed.
  • 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.