Skip to main content

pin-deep

Recursive GitHub Actions pin checker. You pinned your Actions — but did the actions you pinned pin their dependencies?

pin-deep starts from a SHA-pinned GitHub Action, fetches its action.yml, and recursively flags every nested uses: that is not pinned to a full commit SHA. Pinning has a depth dimension; this tool checks it.

Why this exists

Pinning uses: to a commit SHA is standard supply-chain hygiene — until you realize the pinned action itself pulls in other actions by tag. The tj-actions incident (CVE-2025-30066) is the textbook case: the compromise reached victims through reviewdog, which referenced the compromised action. Pinning reviewdog itself was not enough, because the hole was one level deeper.

"You pinned your Actions, but the Actions you pinned didn't pin their dependencies."

Differentiation

Tools like zizmor analyze your workflow files statically — they check your uses: lines. pin-deep checks one level further: it resolves what your pinned actions themselves depend on, recursively. Static workflow linters don't do recursive nested checks; pin-deep is the depth dimension of pinning.

Install

pip install pin-deep

Requires Python 3.9+. No dependencies — stdlib only.

Usage

Scan a workflow file:

pin-deep .github/workflows/ci.yml

Scan a single pinned action (starts the recursion from there):

pin-deep reviewdog/action-setup@e04ffabe3898a1af8f75141d79fa1ba7a3636eb3

Example output:

[unpinned] depth=1 actions/checkout@v4
    source: action.yml of acme/root@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    chain:  acme/root@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  ->  actions/checkout@v4
    note:   not pinned to a full commit SHA

Options:

pin-deep --max-depth 5 --format json .github/workflows/ci.yml
pin-deep --strict .github/workflows/ci.yml   # also fail on short SHAs and fetch errors

Exit code is 1 when unpinned nested actions are found, so it drops straight into CI:

- run: pip install pin-deep
- run: pin-deep .github/workflows/ci.yml

GITHUB_TOKEN (or --token) is used for the API fallback when fetching action.yml; public repos work without one.

How it works

  1. Parse every uses: value from the target (workflow file or bare owner/repo@ref).
  2. Classify each: pinned (full 40/64-char SHA), unpinned (tag, branch, or no ref), short-sha (warning), or skipped (./local, docker://, dynamic expressions).
  3. For each pinned action, fetch its action.yml at that exact SHA and repeat — up to --max-depth (default 3), with a visited set so reference cycles can't loop forever.
  4. Unpinned refs are reported but never recursed into: a moving tag has no fixed content to analyze.

Limitations

  • Only recurses into full-SHA pins. Analyzing the children of a floating tag would audit today's state, not the state you ship.
  • uses: parsing is line-based, not a full YAML parse — exotic YAML layouts may be missed.
  • Private repos need GITHUB_TOKEN.

License

MIT — see LICENSE.

Metadata

Release files for pin-deep 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pin-deep 0.1.0
File Size Uploaded
pin_deep-0.1.0.tar.gz 9.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pin-deep 0.1.0
File Interpreter ABI Platform
pin_deep-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 17.2 kB

Release files / pin_deep-0.1.0.tar.gz

Download URL pin_deep-0.1.0.tar.gz
Size 9.1 kB
Tags Source
SHA-256 checksum
How to use checksums
782813566ed33a46d7cfcc6807cae429214291e266f80d1477d1da25cfb11d4a
BLAKE2b-256 checksum
How to use checksums
a56af007b7fe3ff7e4a0a9dbac001e3f06835b4ba6c645c82757e4ef1ba2ff63
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / pin_deep-0.1.0-py3-none-any.whl

Download URL pin_deep-0.1.0-py3-none-any.whl
Size 8.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c01d4db11ec37c2885afb73a56bdb878ec55505f9c5179cd8b7207dd31861cfb
BLAKE2b-256 checksum
How to use checksums
36f82f240caeb531fe13f786c3cdd022501a92f64c286631f3bc30679c2c8115
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page