Pin github actions
Skill emaarco/hogwarts/plugins/felix-felicis/skills/pin-github-actions
A magical place where my skills, rules, and plugins for AI agents are defined, which magically boost productivity π°πͺ
npx -y skills add emaarco/hogwarts --skill pin-github-actionsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Supply-chain audit: verifies every GitHub Actions reference is pinned to a full commit SHA (not a mutable tag or branch), reports unpinned uses with evidence, and optionally rewrites them to SHA + version comment.
SKILL.md
5.7 KB, as published. Nobody here has run it
Skill: pin-github-actions
Audits a repository's GitHub Actions and verifies that every action reference is pinned to a full-length commit SHA. Mutable tags (@v4, @v4.2.2) and branches (@main) can be silently repointed by whoever controls the upstream repo β the tj-actions/changed-files compromise (CVE-2025-30066, March 2025) repointed existing tags to a malicious commit that dumped CI secrets; only SHA-pinned consumers were unaffected. SHA pinning is the GitHub-recommended hardening and the OpenSSF Scorecard Pinned-Dependencies check.
Run this when asked to "check if actions are pinned", harden CI supply chain, or as the GitHub-Actions slice of a release/supply-chain audit (see the sibling skill release-audit).
Pinning rule
A reference is pinned only when the ref after @ is a full 40-hex-character git commit SHA, ideally followed by a comment naming the human-readable version:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
(The SHA/version above only illustrates the format β never copy it; resolve the current SHA fresh via the Phase 3 commands.)
| Reference form | Verdict |
|---|---|
owner/repo@<40-hex-sha> | β pinned |
owner/repo/path@<40-hex-sha> (action in subdir) | β pinned |
owner/repo/.github/workflows/x.yml@<40-hex-sha> (reusable workflow) | β pinned |
docker://image@sha256:<digest> | β pinned |
owner/repo@v4 / @v4.2.2 (tag) | β mutable |
owner/repo@main / @master (branch) | β mutable |
docker://image:tag | β mutable |
./.github/actions/foo (local, same repo) | β N/A β no pin needed |
First-party
actions/*andgithub/*are lower-risk but still in scope β pin them too (Scorecard makes no exception). If the team deliberately allows major-tag for first-party actions, record it as a documented exception, not a silent gap.
Phase 1 β Detect
Find every action reference across workflows and composite/local actions:
# Workflows + composite action definitions
grep -rEno '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]*\S+' \
.github/workflows .github/actions 2>/dev/null
Also scan action.yml / action.yaml anywhere in the repo (composite actions can uses: other actions).
Phase 2 β Classify & report
For each uses: line, strip any inline comment, take the ref after the last @, and classify it against the table above. A ref is pinned iff it matches ^[0-9a-f]{40}$ (or sha256: for docker://). Local ./ references need no pin.
Report findings as a table, sorted unpinned-first, with severity:
Highβ third-party action pinned to a branch (@main) or a floating tag (@v4); fully attacker-controllable.Mediumβ third-party action pinned to an immutable-looking patch tag (@v4.2.2); still mutable in principle (tags can be force-moved).Lowβ first-partyactions/*/github/*on a tag.OK/N/Aβ SHA-pinned, or local action.
| File:line | Reference | Current ref | Verdict | Severity |
|---|
End with a one-line summary: N references Β· M pinned Β· K unpinned.
Phase 3 β Fix (only when the user asks)
For each unpinned third-party reference, resolve the ref to its current SHA and rewrite in place, preserving the original version as a trailing comment:
# Resolve a tag/branch to its commit SHA
gh api repos/<owner>/<repo>/commits/<ref> --jq '.sha'
# Offline / no gh: git ls-remote https://github.com/<owner>/<repo> <ref>
Then edit the line to uses: owner/repo@<sha> # <original-ref>. Resolve the SHA from the upstream tag, not from a third-party mirror, and only after confirming the tag currently points where expected. Re-run Phase 1 to confirm zero unpinned references remain.
Phase 4 β Recommend automation & enforcement
SHA pins are static, so leave the repo a way to keep them current and to fail the build on regressions:
- Keep pins fresh β Dependabot updates SHA pins and the version comment automatically:
(Renovate's# .github/dependabot.yml version: 2 updates: - package-ecosystem: github-actions directory: "/" schedule: { interval: weekly }helpers:pinGitHubActionDigestspreset is the equivalent.) - Bulk-pin existing repos β
pinactorratchet pinrewrite all tags to SHAs in one pass. - Enforce in CI β fail PRs that introduce unpinned actions, e.g.
zgosalvez/github-actions-ensure-sha-pinned-actions(itself SHA-pinned), or OpenSSF Scorecard'sPinned-Dependenciescheck.
Sources
- GitHub β Security hardening for GitHub Actions: https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions
- OpenSSF Scorecard β Pinned-Dependencies: https://github.com/ossf/scorecard/blob/main/docs/checks.md#pinned-dependencies
- tj-actions/changed-files advisory (CVE-2025-30066): https://github.com/tj-actions/changed-files/security/advisories
- Dependabot for Actions: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot