A lightweight tool to convert GitHub references (tags, branches) to their corresponding commit hashes in workflow files. This enhances security by helping you convert GitHub Actions to specific commit hashes instead of potentially mutable tags or branches.
______ __ ___ ___
| ___ \ / _|| \/ |
| |_/ / ___ | |_ | . . | ___
| / / _ \| _|| |\/| | / _ \
| |\ \| __/| | | | | || __/
\_| \_|\___||_| \_| |_/ \___|
gh extension install metcalfc/gh-refmenpm install -g gh-refme
# Optional: setup the Git pre-commit hook
install-hook.shAlways pin to a specific release tag, not main:
# Download from a specific release
curl -sL -o gh-refme https://raw.githubusercontent.com/metcalfc/gh-refme/refs/tags/v1.6.2/gh-refme
chmod +x gh-refme- Dual Mode: Use as a standalone script or a GitHub CLI extension
- Simple Interface: File-focused design for all operations
- No Dependencies: Uses only standard Unix tools and either
curlor GitHub CLI (gh) - Security: Properly validates all references and handles edge cases
- CI/CD Integration: Easily add to your GitHub Actions workflows
- Branch Support: Automatically resolves branches, tags, and short/full commit hashes
- Wildcard Support: Process multiple workflow files with a single command
- Nested Package Detection: Clear messaging for unsupported GitHub package formats
- Dry Run Mode: Preview changes without modifying files
- Comment Preservation: Adds helpful comments to track what tags/branches were converted to hashes
# Process a workflow file
gh-refme .github/workflows/my-workflow.yml
# Process multiple workflow files with wildcards
gh-refme .github/workflows/*.yml# Process a workflow file
gh refme .github/workflows/my-workflow.yml
# Process multiple workflow files with wildcards
gh refme .github/workflows/*.ymlIn March 2025, the tj-actions/changed-files supply chain attack compromised 23,000+ repositories by force-pushing malicious code to existing Git tags. Every repo using @v45 (or any mutable tag) silently ran the attacker's code, which dumped CI secrets to workflow logs.
The attack exploited a fundamental property of Git: tags are mutable. Anyone with write access can run git tag -f v1 && git push --force and every consumer using @v1 gets the new code with no warning.
GitHub's own security hardening guide states:
"Using a full length commit SHA is currently the only way to use an action in a way that's immune to both repository hijacking and branch/tag hijacking."
This tool makes it easy to follow this security best practice.
# Process a specific workflow file
./gh-refme .github/workflows/workflow.yml
gh refme .github/workflows/workflow.yml
# Process workflow files with wildcards
./gh-refme .github/workflows/*.yml
gh refme .github/workflows/*.yml
# Show what would be changed without making changes (dry run)
./gh-refme -n .github/workflows/workflow.yml
gh refme -n .github/workflows/workflow.yml# Show help information (standalone)
./gh-refme --help
# Show help information (GitHub CLI extension)
gh refme --help
# Show version information (standalone)
./gh-refme --version
# Show version information (GitHub CLI extension)
gh refme --versionAdd this to .github/workflows/refme.yml to automatically pin actions in your repository. Note: this example follows its own advice — actions/checkout is SHA-pinned and gh-refme runs from the checkout rather than being downloaded at runtime.
name: Convert GitHub Actions to Commit Hashes
on:
schedule:
- cron: '0 2 * * 1' # Runs weekly
workflow_dispatch: # Allow manual triggering
jobs:
refme:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Checkout gh-refme
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: metcalfc/gh-refme
ref: v1.6.2
path: .gh-refme
- name: Run gh-refme
run: |
.gh-refme/gh-refme .github/workflows/*.{yml,yaml} 2>&1 | tee refme_output.log
if git diff --quiet; then
echo "All workflows already pinned!"
exit 0
fi
BRANCH_NAME="security/refme-$(date +%Y%m%d%H%M%S)"
git checkout -b "$BRANCH_NAME"
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null || true
git commit -m "security: pin workflow actions to full commit hashes"
git push origin "$BRANCH_NAME"
gh pr create \
--title "Security: Pin GitHub Actions to commit hashes" \
--body "Automatically generated by [gh-refme](https://github.com/metcalfc/gh-refme)."
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}The tool includes several test scripts to verify functionality and security:
# Run basic functionality tests
./tests/test.sh
# Run branch and short hash reference tests
./tests/branch-ref-test.sh
# Run comprehensive test suite
./tests/run-all-tests.sh
# Run security tests
./tests/security-test.sh
# Check shell script best practices (requires shellcheck)
./tests/shellcheck-test.shThese tests ensure that:
- Basic functionality works as expected
- Branch references and short hashes are handled correctly
- Edge cases are handled properly
- The script is secure against common shell script vulnerabilities
- Best practices are followed
- Bash 4.0+
- Either
curlor GitHub CLI (gh) jqis recommended but not required (for better JSON parsing)git(for repository operations)
npm releases are published with provenance, which cryptographically links each package version to the specific GitHub Actions workflow run and commit that produced it. You can verify this on the npm package page.
The tool follows these security best practices:
- Strict mode:
set -euo pipefail— fail fast on errors, undefined variables, and broken pipes - Input validation: All references and file paths are validated before use
- No command injection: Special characters in references are properly handled
- Secure temp files: Temporary files are created securely and cleaned up properly
- Token protection: GitHub tokens are handled carefully to prevent exposure
- Controlled file operations: Files are only modified with explicit permission
- The tool parses GitHub Actions workflow files to find references in the format
owner/repo@ref - It queries the GitHub API to convert references to their corresponding commit hashes
- For workflow files, it uses pattern matching to locate and replace references
- It adds helpful comments to track what tags/branches were converted to hashes
# Before: - uses: actions/checkout@v7 # After: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # was: actions/checkout@v7
- Changes can be previewed before applying (dry run mode)
- Backups are created before modifying any files
-
refme: ignore - Add this comment before any action reference to skip its conversion:
# refme: ignore - uses: actions/checkout@v4
-
Nested Action References - The tool will detect and skip nested references like
github/codeql-action/init@v3with an info message
GITHUB_TOKEN: Authentication token for GitHub API (increases rate limits)
You can use gh-refme as a pre-commit hook to automatically convert GitHub references in workflow files when you commit changes:
# Run the installation script
./install-hook.shThis will install gh-refme as a pre-commit hook in your Git repository. The hook will automatically run when you commit changes to workflow files, converting any GitHub Action references to commit hashes.
For more advanced configurations and options, see the pre-commit hook guide.
Contributions are welcome! Here are some ways to help:
- Report bugs or suggest features by opening issues
- Improve documentation
- Add more tests
- Submit pull requests with improvements
MIT