pinprick
A CLI tool for GitHub Actions supply chain security. Pins action references to full SHAs, checks for updates, and audits pinned actions for runtime fetch patterns that bypass pinning.
The name: pin (SHA pinning) + prick (a small, sharp probe finding tiny holes in your supply chain).
Why
For static analysis of your workflow files — template injection, excessive permissions, credential leaks — use zizmor. It's excellent.
pinprick picks up where static analysis leaves off. SHA-pinning actions is table stakes, but even a pinned action can curl down releases/latest at runtime. pinprick pins your actions, keeps them updated, and audits their source code for unversioned runtime fetches in shell scripts, JavaScript, Python, and Dockerfiles.
Installation
Homebrew
From source
From releases
Download a prebuilt binary from GitHub Releases.
Usage
All commands default to the current directory. Pass a path to target a different repository root. Use --json for machine-readable output.
# Pin action tags to full SHAs
# Check pinned actions for newer releases (dry-run)
# Apply updates
# Audit for runtime fetch patterns that bypass pinning
# Target a specific repo
# Generate shell completions
Pin
Resolve action tag references to full SHAs:
$ pinprick pin
.github/workflows/ci.yml
actions/checkout @v4 -> @de0fac2e…ce83dd # v6.0.2
actions/upload-artifact @v4 -> @bbbca2dd…f024f # v7.0.0
! actions/checkout@v4 -- sliding tag, resolved to v6.0.2
! Homebrew/actions/setup-homebrew@main -- branch ref — pin to a SHA manually
Pinned 2 actions across 1 file (2 skipped)
Sliding tags like @v4 are resolved to their exact version. Branch refs like @main are flagged.
Update
Check pinned actions for newer releases:
$ pinprick update
.github/workflows/ci.yml
actions/checkout v4.1.0 -> v6.0.2
1 update available. Run with --apply to apply.
Audit
Scan for runtime fetch patterns that bypass pinning:
$ pinprick audit
HIGH .github/workflows/ci.yml:42
action: some/action@abc123de
curl -L "https://github.com/.../releases/latest/download/tool.tar.gz"
curl fetching from a 'latest' URL — can change without notice
1 finding (1 high, 0 medium, 0 low)
Without a GitHub token, audit scans local run: blocks only. With a token (via GITHUB_TOKEN or gh auth), it also fetches and scans action source code — JavaScript, Python, Dockerfiles, and composite action steps.
What the audit detects
| Category | Examples | Severity |
|---|---|---|
| Pipe-to-shell | curl/wget piped to sh/bash/python (any URL) |
High |
| Pipe-to-shell | bash <(curl ...), bash -c "$(curl ...)", eval "$(curl ...)" |
High |
| Pipe-to-shell | PowerShell iex (iwr ...) / Invoke-Expression (... DownloadString ...) |
High |
| Shell | curl/wget to /latest/ URLs |
High |
| Shell | curl/wget to unversioned URLs |
Medium |
| Shell | go install @latest, unpinned pip/npm |
Low–Medium |
| PowerShell | Invoke-WebRequest/iwr/irm to /latest/ URLs |
High |
| PowerShell | Invoke-WebRequest/iwr/irm to unversioned URLs |
Medium |
| JavaScript | fetch()/axios/got to /latest/ URLs |
High |
| JavaScript | exec("curl ..."), child_process curl |
High |
| Python | requests.get/urllib to /latest/ URLs |
High |
| Python | subprocess shelling out to curl/wget |
High |
| Docker | FROM :latest or untagged |
High |
| Docker | RUN curl/wget piped to a shell |
High |
| Docker | curl/wget in RUN instructions |
Medium |
| Docker | ADD with an http(s):// URL source |
Medium |
Pipe-to-shell is flagged even when the URL is versioned — a piped payload is never written to disk, so it cannot be checksum-verified and the versioned path pins the URL but not the content.
Unversioned-URL rules don't fire when the URL's path ends in a data-format extension (.json, .yaml, .toml, .csv, etc.) — the payload is consumed as data, not executed. These matches are only visible under --verbose.
Findings followed by checksum verification (sha256sum, gpg --verify, etc.) within 3 lines are downgraded one severity level. Pipe-to-shell findings are exempt.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Clean — no findings, no pending updates |
| 1 | Findings present (audit) or updates available (update dry-run) |
| 2 | Error |
Building
A justfile provides common tasks:
Contributing
Commits must follow Conventional Commits format and include a DCO sign-off (git commit -s).
Acknowledgements
Built with Claude Code.
License
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-only).
Copyright (C) 2026 Patrick Linnane