actions-pinning
Pins every
reference in GitHub Actions workflows, composite
actions, and reusable workflows to a commit SHA, verifies each SHA
belongs to the claimed action repository, and keeps the pins current
with Dependabot.
This skill covers supply-chain pinning only.
covers
and
hardening for the same
workflow files;
covers wall-clock speed. Run those
separately.
Protocol
1. Inventory
Run the bundled scanner against every workflow and composite/reusable
workflow file:
bash
python3 scripts/pin_check.py .
It walks
,
, and every
or
manifest in the tree. It skips VCS and
dependency directories:
,
,
,
,
,
,
,
,
,
, and
. It scans
, because a
reference can run a vendored action.
It prints a table of
(file, line, action, ref, status)
for every
line and exits 1 when any reference is unpinned. Use
for machine-readable output when scripting the remaining steps.
The scanner uses regular expressions, not a YAML parser. It removes one
matching pair of surrounding quotes from each
value. It has two
known limits; check the workflow by hand when either applies:
- A line inside a block scalar gives a false row.
- A flow-style mapping such as gives no row.
2. Classify
The scanner assigns one status per reference:
| Status | Meaning | Action needed |
|---|
| already a full 40-character commit SHA | none |
| a tag-shaped ref such as or | resolve to a SHA |
| a common branch name such as | resolve to a SHA |
| any other ref, such as ; type unknown | resolve to a SHA |
| a commit-like hex ref, such as a short SHA or an uppercase 40-character SHA | resolve to the full lowercase SHA |
| no at all | resolve to a SHA |
| a self-repository reference with an suffix | remove the suffix |
| a local action or a self-repository reference | skip — not a supply-chain risk |
| a reference | flag separately — SHA pinning applies to the image digest, not this scanner |
3. Resolve each ref to a commit SHA
For each
,
,
,
, or
finding:
bash
# Tag: the ref object is the commit (lightweight) or a tag object (annotated).
gh api repos/{owner}/{repo}/git/ref/tags/{tag} --jq '.object'
# Branch: the ref object is the commit directly.
gh api repos/{owner}/{repo}/git/ref/heads/{branch} --jq '.object.sha'
# Annotated tag: dereference the tag object to get the commit SHA.
gh api repos/{owner}/{repo}/git/tags/{tag_sha} --jq '.object.sha'
For an
, try the
path first, then the
path.
For an
, the tag and branch paths cannot resolve the
ref. Resolve it through the commits endpoint, which returns the full
lowercase SHA:
bash
gh api repos/{owner}/{repo}/commits/{ref} --jq '.sha'
Impostor-commit check — always run before rewriting a pin. Confirm
the resolved SHA actually belongs to the action's own repository, not
a fork, a rewritten ref, or a spoofed value:
bash
gh api repos/{owner}/{repo}/commits/{sha}
A 404 here means the SHA does not belong to that repository. Stop and
re-resolve; do not write an unverified SHA.
Once verified, rewrite the line:
yaml
uses: owner/repo@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Keep the original tag as a trailing comment — it is the human-readable
version marker; the SHA is the enforced pin.
4. Keep pins fresh with Dependabot
Add (or merge into an existing
) the
ecosystem entry in
assets/dependabot-actions-snippet.yml
. Dependabot
resolves tag bumps to new commit SHAs automatically and opens a PR
with the version comment updated — review and merge those PRs rather
than hand-editing pins later.
Version updates (not security updates) default to a 3-day
registry-availability cooldown as of 2026-07-14; a newly published tag
won't appear in a Dependabot PR until it clears that window. See
for the source.
For the full
scaffold across every ecosystem in the
repo, use
— this skill only owns the
entry.
5. Repository and organization pinning policy
GitHub can enforce SHA pinning at the allowed-actions policy level
(repo, org, or enterprise), including blocking specific actions
outright. Read the current policy at the scope of the request before
proposing any change:
bash
# Repository scope.
gh api repos/{owner}/{repo}/actions/permissions
# Organization scope. Use this for an organization-wide request.
gh api orgs/{org}/actions/permissions
These are reads. Never call the corresponding write endpoints
(
PUT repos/{owner}/{repo}/actions/permissions
,
PUT orgs/{org}/actions/permissions
, and the
/
variants) without the user's explicit authorization
for that specific change — policy changes can block other workflows
from running.
The SHA-pinning policy applies to actions only. Reusable workflows
can still use a tag under that policy, so this skill pins reusable
workflow references itself.
6. Flag mutable downloads inside workflow scripts
Regex pinning covers
only. While reading each workflow, also
flag (findings only, no rewrite) any
step that fetches code
outside the pinned-action mechanism:
- or piping a remote script straight
into a shell.
- from a URL with no version or checksum.
- (or , , ...) with no
version pin.
These are supply-chain risk in the same family as an unpinned action,
but fixing them is per-ecosystem and often changes build behavior —
report them, do not rewrite them.
7. Optional: zizmor
If
is installed, run it for a second opinion (it also checks
and composite-action internals this scanner does
not):
bash
zizmor .github/workflows/
Skip silently if it is not installed — it is optional tooling, not a
required gate.
8. Verify
Re-run the scanner after every rewrite batch:
bash
python3 scripts/pin_check.py . && echo "all uses: references pinned"
Exit 0 with no
,
,
,
,
, or
rows means every
actionable reference is pinned. Report
and
rows
separately — they are informational, not failures.
What this skill is NOT for
- and hardening (least-privilege
blocks, risk,
interpolation into shell) — use .
- CI run-time speed — use .
- Choosing or writing the full for non-Actions
ecosystems — use .
- Applying an org-wide allowed-actions policy without the repo owner's
explicit authorization for that specific write.
References
- — cited primary sources for syntax,
SHA pinning, the allowed-actions policy, SHA resolution endpoints,
the Dependabot cooldown, and zizmor.
- — the inventory/classification scanner used
in steps 1, 2, and 8 ( runs its embedded fixtures;
for machine-readable output).
assets/dependabot-actions-snippet.yml
— the
ecosystem entry for .