action-pinner
v0.2.1
Published
Modern, maintained utility for pinning GitHub Actions to commit SHAs.
Maintainers
Readme
action-pinner
Pin GitHub Actions refs like @v4 or @main to immutable commit SHAs so your workflows are safer to review, harder to tamper with, and easier to reproduce. action-pinner scans workflow files, rewrites refs in place, enforces policy in CI, and can open a pull request with the changes.
Quick Start
No install needed - just run:
npx action-pinner scan # find unpinned refs
npx action-pinner fix # pin them to SHAs
npx action-pinner fix --dry-run # preview without writingOr install globally:
npm install -g action-pinner
action-pinner scanRequires: Node.js 20+
Why Pin Actions?
- Tags can move.
@v4and@mainare mutable; a SHA is not. - Supply chain risk goes down. Pinning limits surprise changes from compromised or retagged releases.
- Builds become reproducible. The same workflow definition resolves to the same code every time.
- Audits get easier. SHA-based refs and exception metadata leave a clearer review trail.
CLI Commands
All commands work with npx action-pinner <command> or just action-pinner <command> if installed globally.
scan
Find unpinned uses: refs without modifying files.
action-pinner scan
action-pinner scan --path ".github/workflows"
action-pinner scan --exclude-path ".github/workflows/legacy/**"
action-pinner scan --include-action "actions/*" --exclude-action "actions/cache"
action-pinner scan --github-org octo-org --include-repo "platform-*" --exclude-repo "*-archive"
action-pinner scan --repo octo-org/service-a octo-org/service-b --jsonFlags:
--config <path>: config file path (default:.action-pinner.json)--path <path...>: workflow file, directory, or glob to scan--exclude-path <path...>: workflow file, directory, or glob to skip--include-action <pattern...>: only scan matching actions--exclude-action <pattern...>: skip matching actions--repo <owner/repo...>: explicit multi-repo targets--github-org <org>: enumerate repositories from an organization--include-repo <pattern...>/--exclude-repo <pattern...>: repo filters for org or explicit targets--json: emit machine-readable JSON--token <token>: GitHub token override--github-api-url <url>: GitHub API base URL, including GHES--use-netrc: read credentials from.netrc/_netrc
fix
Resolve mutable refs to SHAs and update workflow files in place.
action-pinner fix
action-pinner fix --dry-run
action-pinner fix --path ".github/workflows/release.yml"
action-pinner fix --continue-on-error --fail-on-ambiguous
action-pinner fix --comment-format "pin@{ref}"Flags:
--dry-run: preview changes without writing files- All
scanflags except--json --continue-on-error: skip unresolved refs instead of failing the run--fail-on-ambiguous: fail if a ref resolves ambiguously--comment-format <template>: customize pinned version comments with{ref},{action}, and{sha_short}tokens
enforce
Use policy mode for CI. enforce reports allowed refs, violations, invalid exceptions, and exits non-zero when policy fails.
action-pinner enforce
action-pinner enforce --allow-action "actions/*"
action-pinner enforce --exception "actions/upload-artifact@v3::**/legacy.yml"
action-pinner enforce --jsonFlags:
- All
scanflags --allow-action <pattern...>: allowlist unpinned actions by pattern--exception <rule...>: allow a specific exception using<action>[@ref][::workflow-glob]--continue-on-error: continue when a ref cannot be resolved--fail-on-ambiguous: fail if a ref resolves ambiguously
pr
Pin refs, create a branch, and publish a pull request using the pr config block.
action-pinner pr
action-pinner pr --path ".github/workflows"
action-pinner pr --continue-on-error --fail-on-ambiguous
action-pinner pr --comment-format "{ref}"Flags:
- All
scanflags except--json --continue-on-error: skip unresolved refs instead of failing the run--fail-on-ambiguous: fail if a ref resolves ambiguously--comment-format <template>: override the configured version comment template for this run
dependabot-snippet
Generate a github-actions Dependabot snippet for pinned workflows.
action-pinner dependabot-snippet
action-pinner dependabot-snippet --path ".github/workflows" "packages/*/.github/workflows"
action-pinner dependabot-snippet --checkFlags:
--path <path...>: workflow file, directory, or glob to scan--check: compare the generated snippet against.github/dependabot.ymlor.github/dependabot.yaml
GitHub Action Usage
Run action-pinner as a GitHub Action:
- uses: jongalloway/action-pinner@v1
with:
mode: scan
config: .action-pinner.jsonUse enforce to gate workflow changes in CI:
name: enforce-pinned-actions
on:
pull_request:
push:
branches: [main]
jobs:
action-pinner:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: jongalloway/action-pinner@v1
with:
mode: enforce
config: .action-pinner.jsonAction inputs:
mode:scan,fix,enforce, orprconfig: config file pathpath,exclude_path,include_action,exclude_actionallow_actions,exception_rulesjson
Pre-commit
Use action-pinner as a pre-commit hook to scan workflow changes before they land:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/jongalloway/action-pinner
rev: v0.1.0
hooks:
- id: action-pinner-scanAvailable hooks:
action-pinner-scan: runsaction-pinner scanagainst.github/workflowsand fails if unpinned refs are found.action-pinner-fix: runsaction-pinner fixagainst.github/workflowsto auto-pin refs before commit.
Configuration
Example .action-pinner.json:
{
"$schema": "./schemas/action-pinner.schema.json",
"mode": "scan",
"include": [
".github/workflows/**/*.yml",
".github/workflows/**/*.yaml"
],
"exclude": [
".github/workflows/legacy/**"
],
"repos": [
"octo-org/service-a",
"octo-org/service-b"
],
"includeRepos": [
"platform-*"
],
"excludeRepos": [
"*-archive"
],
"excludeActions": [
"actions/cache"
],
"org": {
"name": "octo-org",
"includePrivate": true,
"includeArchived": false
},
"enforcement": {
"enabled": true,
"failOnUnpinned": true,
"allowActions": [
"actions/*",
"github/codeql-action"
],
"exceptions": [
{
"action": "actions/upload-artifact",
"ref": "v3",
"workflow": "**/legacy.yml",
"reason": "Temporary migration exception",
"expiresAt": "2026-12-31"
}
]
},
"pr": {
"create": true,
"branchPrefix": "chore/action-pinner",
"title": "Pin GitHub Actions to commit SHAs",
"labels": [
"security",
"dependencies"
],
"reviewers": [
"octocat"
],
"assignees": [
"hubot"
]
},
"dependabot": {
"addVersionComments": true,
"commentFormat": "{ref}",
"generateConfigSnippet": false
},
"githubApiUrl": "https://enterprise.example.com/api/v3",
"useNetrc": false
}Notes:
- CLI flags override config values.
reasonandexpiresAtmake exceptions easier to review and clean up.pr.create: falsecreates the branch and commit without publishing a PR.dependabot.commentFormatsupports{ref},{action}, and{sha_short}tokens. Use""for no version comment, or setdependabot.addVersionComments: falseto suppress comments entirely.
Authentication
Authentication precedence:
| Priority | Source |
| --- | --- |
| 1 | --token <token> |
| 2 | PIN_ACTIONS_TOKEN |
| 3 | .netrc / _netrc when --use-netrc is enabled |
| 4 | GITHUB_TOKEN |
| 5 | Anonymous GitHub API access |
Examples:
action-pinner scan --token ghp_xxx
action-pinner scan --use-netrc
action-pinner scan --github-api-url https://enterprise.example.com/api/v3For GitHub Enterprise Server, set --github-api-url or githubApiUrl in config. See docs/ENTERPRISE.md.
Multi-Repo and Org Scanning
Scan explicit repositories:
action-pinner scan --repo octo-org/service-a octo-org/service-bDiscover repositories from an organization, then narrow the set:
action-pinner scan --github-org octo-org --include-repo "platform-*" --exclude-repo "*-archive"Target a subset of workflow files across selected repos:
action-pinner scan --github-org octo-org --path ".github/workflows/**" --exclude-path "**/legacy/**"For user-owned repositories, pass explicit --repo owner/repo values.
Enforcement Allowlists and Exceptions
Allowlist broad cases:
action-pinner enforce --allow-action "actions/*"Add a narrow CLI exception:
action-pinner enforce --exception "actions/upload-artifact@v3::**/legacy.yml"Config-driven exceptions are better for review history:
{
"enforcement": {
"failOnUnpinned": true,
"allowActions": ["actions/*"],
"exceptions": [
{
"action": "actions/upload-artifact",
"ref": "v3",
"workflow": "**/legacy.yml",
"reason": "Legacy workflow still migrating",
"expiresAt": "2026-12-31"
}
]
}
}Rules:
allowActionsis pattern-based and broad.exceptionsare specific and auditable.- Expired or malformed exceptions fail closed.
Security
- Fail closed: unresolved refs, invalid exceptions, and policy violations fail enforcement by default.
- Token safe: tokens are redacted from logs; use the smallest possible scopes.
- Deterministic output: scans, rewrites, and fingerprints are stable on the same input.
See SECURITY.md for the security policy and docs/ENTERPRISE.md for GHES guidance.
Acknowledgments
This project was inspired by mheap/pin-github-action, which pioneered the idea of pinning GitHub Actions to commit SHAs. action-pinner is a completely new implementation built from scratch using modern Node.js and the GitHub REST API, designed to address long-standing community requests including:
- Enterprise GitHub support
- Support netrc auth
- Published as a GitHub Action
- Default to
.github/workflows/
Thank you to @mheap and the contributors to that project for the inspiration.
Contributing
Clone the repo, install dependencies, and run:
npm test
npm run lint
npm run buildOpen an issue or PR at github.com/jongalloway/action-pinner/issues.
