# 📌 gha-pinner
## Table of Contents
- [🤔 Why should you pin your GitHub Actions?](#-why-should-you-pin-your-github-actions)
- [🚀 What does `gha-pinner` do?](#-what-does-gha-pinner-do)
- [📦 Installation](#-installation)
- [🛠️ Usage](#️-usage)
- [🔄 Using as a GitHub Action](#-using-as-a-github-action)
- [👨💻 For Maintainers](#-for-maintainers)
## 🤔 Why should you pin your GitHub Actions?
Ever felt a bit of a thrill-seeker using `actions/checkout@v3` or even `actions/checkout@main` in your workflows? While convenient, using floating references like tags, branches, or the `latest` tag means you're living on the edge.
Pinning your GitHub Actions to a specific commit SHA is your seatbelt for this ride. Here's why it's a non-negotiable security best practice:
- **🔐 Rock-Solid Immutability**: A commit SHA is a unique, unchangeable fingerprint for a specific version of the code. No surprises. You can be absolutely certain about the code executing in your workflow.
- **🛡️ Dodge Malicious Updates**: Tags (like `v3`), branches (like `main`), and even the `latest` keyword are mutable pointers. The repository owner can move them to a different commit, potentially slipping malicious code into your workflow without you ever knowing. Pinning to a SHA is like saying, "I'll have *this* exact version, and nothing else, thank you."
- **✅ Crystal-Clear Verifiability**: A pinned SHA lets you put on your detective hat. You can browse the repository at that exact commit, inspect the code, and confirm it's safe and sound.
## 🚀 What does `gha-pinner` do?
`gha-pinner` is your friendly neighborhood GitHub Actions detective. It's a simple command-line tool that does the hard work of pinning your actions for you.
Given a GitHub Action in the format `owner/repo@ref` (where `ref` can be a branch, tag, or even `latest`), `gha-pinner` will:
1. **🔎 Track Down the `ref`**: It sleuths out the provided `ref` and resolves it to a specific, full-length commit SHA.
2. **📍 Serve the Pinned Version**: It then hands you back the action, neatly pinned to that commit SHA.
For example, if you provide `actions/checkout@v3`, `gha-pinner` will resolve `v3` to the latest commit SHA in the `v3` tag and provide you with `actions/checkout@<commit_sha>`.
It can also update all actions in a workflow file or directory in place.
## 📦 Installation
You can install `gha-pinner` using pip:
```bash
pip install gha-pinner
```
## 🛠️ Usage
Ready to lock down your workflows? Here's how you can use `gha-pinner`.
**Pin a single action:**
To get the commit SHA for a specific action, use the `action` subcommand:
```bash
$ gha-pinner action actions/checkout@v3
Original: actions/checkout@v3
Pinned: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b
```
You can then use the pinned version in your GitHub Actions workflow file:
```yaml
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b
```
This ensures that you are always using the exact same version of the action, protecting you from any potential malicious updates.
**Pin an entire workflow file:**
To pin all actions in a workflow file, use the `file` subcommand:
```bash
$ gha-pinner file .github/workflows/ci.yml
✅ Successfully pinned actions in '.github/workflows/ci.yml'
```
This will update the file in place, pinning all actions to their latest commit SHA and adding a comment with the original version, so you don't forget where you came from. For example:
```yaml
- uses: actions/checkout@v3
```
will be updated to:
```yaml
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v3
```
**Pin all workflow files in a directory:**
To pin all actions in all workflow files within a directory (recursively), use the `dir` subcommand:
```bash
$ gha-pinner dir .github/workflows
✅ Successfully pinned actions in '.github/workflows/ci.yml'
✅ Successfully pinned actions in '.github/workflows/release.yml'
```
This will scan the specified directory recursively, finding all workflow files (`.yml` and `.yaml` files), and pin all actions in each file. It's a convenient way to secure all your workflows at once.
**Validate actions without modifying files:**
To check if all actions in your workflows are properly pinned without modifying any files, use the `--validate` flag:
```bash
$ gha-pinner dir .github/workflows --validate
❌ - actions/checkout@v3 should be pinned as actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b
✅ Successfully validated actions in '.github/workflows/ci.yml'
```
When using the `--validate` flag, the command will exit with a non-zero status code if any actions need pinning, making it perfect for CI/CD pipelines.
## 🔄 Using as a GitHub Action
You can use `gha-pinner` as a GitHub Action in your workflows to validate that your actions are properly pinned.
### Validate Actions in CI/CD
Add this to your workflow to validate that all actions are properly pinned:
```yaml
name: Validate Actions
on:
pull_request:
paths:
- '.github/workflows/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate GitHub Actions
uses: gha-pinner/gha-pinner@v1
with:
target: '.github/workflows'
target-type: 'dir'
```
This will fail the workflow if any actions are not properly pinned, ensuring your team follows security best practices.
## 👨💻 For Maintainers
### Release Process
This project uses a dual release mechanism to publish both a PyPI package and a GitHub Action:
1. **Creating a Release**:
- To create a new release, push a tag in the format `v{major}.{minor}.{patch}` (e.g., `v1.2.3`)
- This will trigger the release workflow
2. **What Happens During Release**:
- Tests and linting are run
- The package is built and published to PyPI
- A GitHub Release is created with the version tag (e.g., `v1.2.3`)
- A major version tag (e.g., `v1`) is automatically created/updated to point to the latest release
3. **Version References**:
- For PyPI and specific version references, use the full version tag (e.g., `v1.2.3`)
- For GitHub Action references in workflows, use the major version tag (e.g., `v1`)
Example:
```yaml
- name: Pin GitHub Actions
uses: sapasapasapa/gha-pinner@v1 # This will always use the latest v1.x.x release
```
This approach ensures that users of the GitHub Action automatically get the latest patch and minor updates while maintaining compatibility, while also allowing specific version pinning for those who need it.
Raw data
{
"_id": null,
"home_page": "https://github.com/sapasapasapa/gha-pinner",
"name": "gha-pinner",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.8",
"maintainer_email": null,
"keywords": "github, actions, security, pin, sha",
"author": "Samuele Pasini",
"author_email": "sapa@bendingspoons.com",
"download_url": "https://files.pythonhosted.org/packages/fb/40/2436e506eff497228940f731e4b2cd9cbf7be95211865acf6e7f592b8eee/gha_pinner-1.0.1.tar.gz",
"platform": null,
"description": "# \ud83d\udccc gha-pinner\n\n## Table of Contents\n\n- [\ud83e\udd14 Why should you pin your GitHub Actions?](#-why-should-you-pin-your-github-actions)\n- [\ud83d\ude80 What does `gha-pinner` do?](#-what-does-gha-pinner-do)\n- [\ud83d\udce6 Installation](#-installation)\n- [\ud83d\udee0\ufe0f Usage](#\ufe0f-usage)\n- [\ud83d\udd04 Using as a GitHub Action](#-using-as-a-github-action)\n- [\ud83d\udc68\u200d\ud83d\udcbb For Maintainers](#-for-maintainers)\n\n## \ud83e\udd14 Why should you pin your GitHub Actions?\n\nEver felt a bit of a thrill-seeker using `actions/checkout@v3` or even `actions/checkout@main` in your workflows? While convenient, using floating references like tags, branches, or the `latest` tag means you're living on the edge.\n\nPinning your GitHub Actions to a specific commit SHA is your seatbelt for this ride. Here's why it's a non-negotiable security best practice:\n\n- **\ud83d\udd10 Rock-Solid Immutability**: A commit SHA is a unique, unchangeable fingerprint for a specific version of the code. No surprises. You can be absolutely certain about the code executing in your workflow.\n- **\ud83d\udee1\ufe0f Dodge Malicious Updates**: Tags (like `v3`), branches (like `main`), and even the `latest` keyword are mutable pointers. The repository owner can move them to a different commit, potentially slipping malicious code into your workflow without you ever knowing. Pinning to a SHA is like saying, \"I'll have *this* exact version, and nothing else, thank you.\"\n- **\u2705 Crystal-Clear Verifiability**: A pinned SHA lets you put on your detective hat. You can browse the repository at that exact commit, inspect the code, and confirm it's safe and sound.\n\n## \ud83d\ude80 What does `gha-pinner` do?\n\n`gha-pinner` is your friendly neighborhood GitHub Actions detective. It's a simple command-line tool that does the hard work of pinning your actions for you.\n\nGiven a GitHub Action in the format `owner/repo@ref` (where `ref` can be a branch, tag, or even `latest`), `gha-pinner` will:\n\n1. **\ud83d\udd0e Track Down the `ref`**: It sleuths out the provided `ref` and resolves it to a specific, full-length commit SHA.\n2. **\ud83d\udccd Serve the Pinned Version**: It then hands you back the action, neatly pinned to that commit SHA.\n\nFor example, if you provide `actions/checkout@v3`, `gha-pinner` will resolve `v3` to the latest commit SHA in the `v3` tag and provide you with `actions/checkout@<commit_sha>`.\n\nIt can also update all actions in a workflow file or directory in place.\n\n## \ud83d\udce6 Installation\n\nYou can install `gha-pinner` using pip:\n\n```bash\npip install gha-pinner\n```\n\n## \ud83d\udee0\ufe0f Usage\n\nReady to lock down your workflows? Here's how you can use `gha-pinner`.\n\n**Pin a single action:**\n\nTo get the commit SHA for a specific action, use the `action` subcommand:\n\n```bash\n$ gha-pinner action actions/checkout@v3\nOriginal: actions/checkout@v3\nPinned: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b\n```\n\nYou can then use the pinned version in your GitHub Actions workflow file:\n\n```yaml\n- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b\n```\n\nThis ensures that you are always using the exact same version of the action, protecting you from any potential malicious updates.\n\n**Pin an entire workflow file:**\n\nTo pin all actions in a workflow file, use the `file` subcommand:\n\n```bash\n$ gha-pinner file .github/workflows/ci.yml\n\u2705 Successfully pinned actions in '.github/workflows/ci.yml'\n```\n\nThis will update the file in place, pinning all actions to their latest commit SHA and adding a comment with the original version, so you don't forget where you came from. For example:\n\n```yaml\n- uses: actions/checkout@v3\n```\n\nwill be updated to:\n\n```yaml\n- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v3\n```\n\n**Pin all workflow files in a directory:**\n\nTo pin all actions in all workflow files within a directory (recursively), use the `dir` subcommand:\n\n```bash\n$ gha-pinner dir .github/workflows\n\u2705 Successfully pinned actions in '.github/workflows/ci.yml'\n\u2705 Successfully pinned actions in '.github/workflows/release.yml'\n```\n\nThis will scan the specified directory recursively, finding all workflow files (`.yml` and `.yaml` files), and pin all actions in each file. It's a convenient way to secure all your workflows at once.\n\n**Validate actions without modifying files:**\n\nTo check if all actions in your workflows are properly pinned without modifying any files, use the `--validate` flag:\n\n```bash\n$ gha-pinner dir .github/workflows --validate\n\u274c - actions/checkout@v3 should be pinned as actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b\n\u2705 Successfully validated actions in '.github/workflows/ci.yml'\n```\n\nWhen using the `--validate` flag, the command will exit with a non-zero status code if any actions need pinning, making it perfect for CI/CD pipelines.\n\n## \ud83d\udd04 Using as a GitHub Action\n\nYou can use `gha-pinner` as a GitHub Action in your workflows to validate that your actions are properly pinned.\n\n### Validate Actions in CI/CD\n\nAdd this to your workflow to validate that all actions are properly pinned:\n\n```yaml\nname: Validate Actions\n\non:\n pull_request:\n paths:\n - '.github/workflows/**'\n\njobs:\n validate:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n \n - name: Validate GitHub Actions\n uses: gha-pinner/gha-pinner@v1\n with:\n target: '.github/workflows'\n target-type: 'dir'\n```\n\nThis will fail the workflow if any actions are not properly pinned, ensuring your team follows security best practices.\n\n## \ud83d\udc68\u200d\ud83d\udcbb For Maintainers\n\n### Release Process\n\nThis project uses a dual release mechanism to publish both a PyPI package and a GitHub Action:\n\n1. **Creating a Release**:\n - To create a new release, push a tag in the format `v{major}.{minor}.{patch}` (e.g., `v1.2.3`)\n - This will trigger the release workflow\n\n2. **What Happens During Release**:\n - Tests and linting are run\n - The package is built and published to PyPI\n - A GitHub Release is created with the version tag (e.g., `v1.2.3`)\n - A major version tag (e.g., `v1`) is automatically created/updated to point to the latest release\n\n3. **Version References**:\n - For PyPI and specific version references, use the full version tag (e.g., `v1.2.3`)\n - For GitHub Action references in workflows, use the major version tag (e.g., `v1`)\n\nExample:\n```yaml\n- name: Pin GitHub Actions\n uses: sapasapasapa/gha-pinner@v1 # This will always use the latest v1.x.x release\n```\n\nThis approach ensures that users of the GitHub Action automatically get the latest patch and minor updates while maintaining compatibility, while also allowing specific version pinning for those who need it.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Pin GitHub Actions to specific commit SHAs for improved security",
"version": "1.0.1",
"project_urls": {
"Homepage": "https://github.com/sapasapasapa/gha-pinner",
"Repository": "https://github.com/sapasapasapa/gha-pinner"
},
"split_keywords": [
"github",
" actions",
" security",
" pin",
" sha"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "3f54290501b466308ce880a2c20b40f8f49af6c0ad9d8737430735c12b2b02ae",
"md5": "d4c8ba94052bffd39a2d8e2ccafd4afc",
"sha256": "41bd4eb48bddbd1e52dcce2024f19f91fdf0a62ab5754b15d1fa9bb248d4831a"
},
"downloads": -1,
"filename": "gha_pinner-1.0.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "d4c8ba94052bffd39a2d8e2ccafd4afc",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.8",
"size": 15514,
"upload_time": "2025-07-28T11:35:37",
"upload_time_iso_8601": "2025-07-28T11:35:37.616387Z",
"url": "https://files.pythonhosted.org/packages/3f/54/290501b466308ce880a2c20b40f8f49af6c0ad9d8737430735c12b2b02ae/gha_pinner-1.0.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "fb402436e506eff497228940f731e4b2cd9cbf7be95211865acf6e7f592b8eee",
"md5": "f38d99760e7ef46ed021f4abec6ef244",
"sha256": "3972c462577af06a8d412bec1e1835ef1fcc63a7e73bc3e82d7f36fefdcf7b0f"
},
"downloads": -1,
"filename": "gha_pinner-1.0.1.tar.gz",
"has_sig": false,
"md5_digest": "f38d99760e7ef46ed021f4abec6ef244",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.8",
"size": 14230,
"upload_time": "2025-07-28T11:35:38",
"upload_time_iso_8601": "2025-07-28T11:35:38.672415Z",
"url": "https://files.pythonhosted.org/packages/fb/40/2436e506eff497228940f731e4b2cd9cbf7be95211865acf6e7f592b8eee/gha_pinner-1.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-28 11:35:38",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "sapasapasapa",
"github_project": "gha-pinner",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "gha-pinner"
}