pin-github-actions
Pin GitHub Actions to Commit SHAs
You are helping the user migrate their GitHub Actions workflows from tag-based references
(e.g., actions/checkout@v4) to commit SHA-pinned references with version comments
(e.g., actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.7).
This prevents supply-chain attacks where a tag can be moved to point to malicious code.
Step 1: Discover Workflows and Current State
-
Find all workflow files:
find .github/workflows -name '*.yml' -o -name '*.yaml' -
Extract all
uses:references from each workflow file -
Check for an existing Dependabot configuration at
.github/dependabot.ymlor.github/dependabot.yaml -
Check for git submodules (
.gitmodules) and other dependency ecosystems (e.g.,package.json,go.mod,Gemfile) that Dependabot could manage
Present a summary table of the current state:
| Action | Workflow | Current Ref | Pinned? |
| ----------------------------------- | --------------------- | ----------- | ------- |
| actions/checkout | build.yaml | @v4 | No |
| docker/build-push-action | build.yaml | @abc123... | Yes |
Step 2: Resolve Latest Releases
For each action that is not already SHA-pinned, resolve the latest release version.
Resolving the Latest Release
Use the GitHub API via gh CLI to find the latest release tag:
gh api repos/{owner}/{repo}/releases/latest --jq '.tag_name'
Important: Always use the exact release tag (e.g., v4.2.2), never a major tag
(e.g., v4). Major tags are mutable aliases that move with each release.
Resolving the Commit SHA
Get the SHA for the exact release tag:
gh api repos/{owner}/{repo}/git/ref/tags/{tag} --jq '.object.sha'
Some tags are annotated (they point to a tag object, not a commit). Dereference them:
# Get the tag object SHA
tag_sha=$(gh api repos/{owner}/{repo}/git/ref/tags/{tag} --jq '.object.sha')
# Try to dereference — if it's annotated, this returns the commit SHA
commit_sha=$(gh api repos/{owner}/{repo}/git/tags/$tag_sha --jq '.object.sha' 2>/dev/null)
# If dereference fails (404), the tag is lightweight and tag_sha is already the commit SHA
Detecting Major Version Jumps
Compare the currently used major version with the latest release major version.
If a major version jump is detected (e.g., @v3 → latest is v4.2.0):
-
Flag it clearly to the user:
⚠ Major version jump detected: actions/checkout: v3 → v4.2.0 Check the changelog for breaking changes before upgrading. -
Link to the release notes or changelog
-
Ask the user whether to upgrade to the latest major version or pin to the latest patch of the current major version
-
If the user wants to stay on the current major, resolve the latest patch release for that major version:
gh api repos/{owner}/{repo}/releases --jq '[.[] | select(.tag_name | startswith("v3.")) | .tag_name] | first'
Present the resolution results:
| Action | Current | Latest Release | SHA | Major Jump? |
| ------------------------ | ------- | -------------- | -------- | ----------- |
| actions/checkout | @v4 | v4.2.2 | abc123.. | No |
| peaceiris/actions-hugo | @v2 | v3.0.0 | def456.. | Yes (v2→v3) |
Step 3: Apply Pinned Versions
For each action reference, replace the tag with the commit SHA and add a version comment:
Before:
- uses: actions/checkout@v4
After:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.2.2
Rules
- The version comment uses the exact release tag (e.g.,
v4.2.2), not the major tag - The comment format is
# v{version}with a single space after# - Preserve all existing
with:,env:,if:, andname:configuration - If the same action appears in multiple workflows, use the same SHA and version everywhere
- Actions already pinned to a SHA should be left unchanged (but verify the comment is accurate)
Step 4: Configure Dependabot
Dependabot keeps pinned SHAs up to date by opening PRs when new versions are released.
Create or Update .github/dependabot.yml
GitHub Actions Ecosystem
Always include the github-actions ecosystem with grouped updates:
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
groups:
dependencies:
patterns:
- '*'
Additional Ecosystems
Scan the repository for other dependency sources and add them:
| File Found | Ecosystem to Add |
|---|---|
.gitmodules |
gitsubmodule |
package.json |
npm |
go.mod |
gomod |
Gemfile |
bundler |
requirements.txt |
pip |
pyproject.toml |
pip |
Cargo.toml |
cargo |
pom.xml |
maven |
build.gradle |
gradle |
Dockerfile |
docker |
*.tf |
terraform |
flake.nix |
nix |
Each ecosystem entry should follow the same pattern with grouped updates:
- package-ecosystem: '{ecosystem}'
directory: '/'
schedule:
interval: 'weekly'
groups:
dependencies:
patterns:
- '*'
Merging with Existing Configuration
If a dependabot.yml already exists:
- Do not duplicate existing ecosystem entries
- Add missing ecosystems that were discovered
- Add
groupsconfiguration to existing entries that lack it - Preserve existing configuration (labels, reviewers, assignees, ignore rules, etc.)
Step 5: Present Changes and Confirm
Before applying any changes, present a clear summary:
- Workflow changes: list each file and the actions that will be pinned
- Major version jumps: highlight any that need user decision
- Dependabot changes: show what will be added or modified
- Wait for user approval before writing any files
Anti-patterns to Avoid
| Anti-pattern | Why it is wrong | Correct approach |
|---|---|---|
Using major tags in comments (# v4) |
Ambiguous, does not identify exact release | Use exact version: # v4.2.2 |
| Skipping annotated tag dereference | Wrong SHA, action may not resolve | Always check if tag needs dereferencing |
| Silently upgrading major versions | May introduce breaking changes | Flag and ask the user first |
| Adding dependabot without groups | Creates noisy individual PRs | Always configure grouped updates |
| Pinning Docker-based actions by SHA | Docker actions use container tags | Only pin JavaScript/composite actions |
| Ignoring existing dependabot config | May duplicate or override user settings | Merge carefully with existing configuration |
Important Guidelines
- Always verify SHAs by resolving them from the GitHub API — never guess or reuse stale values
- Exact versions only — use
v4.2.2, neverv4in version comments - Ask before major upgrades — major version jumps may have breaking changes
- Group dependabot updates — reduces PR noise by bundling updates into single PRs
- Check all ecosystems — do not limit to
github-actions; scan for all dependency sources - Preserve existing config — merge with, do not overwrite, existing dependabot settings