Pinner ๐งช
A high-performance Rust CLI utility to hash-pin your CI/CD dependencies. Secure your supply chain by converting volatile, mutable tags (like @v2) into immutable, cryptographic commit SHAs (like @df4cb1c...).
Why Pin? ๐
Using mutable tags like @v2 or @main in GitHub Actions or other CI/CD providers introduces a security risk. If an attacker gains access to a dependency's repository, they can move the tag to a malicious commit, leading to a supply chain attack on your infrastructure.
Hash-pinning ensures that you run the exact code you've audited, every single time. Pinner automates this process while keeping your workflows readable by appending the original tag as a comment.
Features โจ
- Domain-Driven Pipeline: Built on a strict Scanner -> Resolver -> Patcher architecture, ensuring high testability, concurrency, and safe mutations.
- Surgical Replacement: Uses
tree-sitterfor precise YAML parsing, preserving comments, indentation, and formatting perfectly. - Multi-Forge Support: Works with GitHub, GitLab, Bitbucket, and Forgejo/Gitea.
- Tag Preservation: Automatically appends the original tag as a comment (e.g.,
@<hash> # v2). - Container Pinning: Automatically pins Docker images to their immutable digests (e.g.,
image: alpine@sha256:...). - Flexible Upgrades: Multiple strategies to keep your actions up to date (Major, Minor, Latest).
- CI Ready: Includes a
verifymode to ensure all actions remain pinned in your PRs. - Security Scanning: A
scansubcommand to query the OpenSSF OSV database for known vulnerabilities and supply-chain compromises. - Visual Security Feedback: Appends colorful indicators (
[โ vetted],[โ compromised], or[? not checked]) during dry-runs and diff outputs.
Installation ๐ ๏ธ
One-line installation (Recommended)
macOS/Linux:
|
Windows:
powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/ffalcinelli/pinner/main/install.ps1 | iex"
From source
Usage ๐
1. Pin all actions
Scans workflows and converts all tags to pinned hashes.
Input: - uses: actions/checkout@v3
Output: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
2. Upgrade to latest
Update pinned actions to their latest versions based on a strategy.
[!CAUTION] Automatic upgrades can undermine your security. The primary goal of hash-pinning is to ensure you run only code you have vetted. While
verifyshould be run in every CI pipeline to enforce this,upgradeshould be used as an intentional step in your development process, followed by a review of the new version to maintain the integrity of your supply chain. Automated, unvetted upgrades re-introduce the very supply chain risks that hash-pinning is designed to prevent.
# Default: Upgrade to latest available release
# Upgrade only within the current major version (e.g., v2.1.0 -> v2.4.5)
3. Verify pinning
Ensure that all actions in your workflows are pinned. Perfect for CI pipelines.
4. Install Git Hook
Automatically install a pre-commit hook to verify pinning before every commit.
5. Manual Set
Forcibly update a specific action to a provided hash across all workflows.
6. Initialize Configuration
Create a .pinner.toml with default settings.
7. Export SBOM
Generate a Software Bill of Materials for your CI dependencies.
8. Security Scan
Audits your dependencies for vulnerabilities. It queries the OpenSSF OSV database for both current hashes and proposed upgrade candidates, and executes Sigstore/Cosign provenance and signature verification for OCI container images. It presents an interactive report and updates your vetted whitelist or compromised blacklist.
# Scan workflows and interactively update your .pinner.toml config
# Scan workflows and automatically populate .pinner.toml config (great for automation)
9. Shell Completions
Generate tab-completion scripts for your shell. Automatically detects your current shell if no argument is provided.
# Auto-detect current shell
# Or specify explicitly
Configuration โ๏ธ
Pinner can be configured via a .pinner.toml file in your repository root.
# List of actions to ignore during pinning/upgrading
= ["my-org/private-action"]
# Number of concurrent API requests (default: 10)
= 5
# Custom API URLs (for Enterprise instances)
= "https://github.mycompany.com/api/v3"
= "https://gitlab.mycompany.com/api/v4"
# Vetted (trusted) dependency hashes/references (Whitelist)
# Supports plain strings or structured maps with tag versions and timestamps of insertion.
= [
# Plain string format (backwards compatible)
"actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332",
# Structured format generated automatically during "scan"
{ = "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10", = "v6.0.3", = "2026-06-18T15:28:25Z" }
]
# Compromised dependency hashes/references (Blacklist)
= [
"actions/checkout@badhash1234567890badhash1234567890bad",
{ = "actions/checkout@evilhash1234567890evilhash1234567890bad", = "v3.1.0", = "2026-06-18T15:28:25Z" }
]
# Disable visual security feedback (default: false)
= false
Global Configuration & Overrides ๐
Pinner automatically loads security configurations from global user locations, allowing you to share whitelists/blacklists across projects:
~/.cache/pinner/config.toml(Global cache file)~/.config/pinner/config.toml(User configuration file)~/.pinner.toml(Home directory configuration file)
Precedence (Local Overrides):
The local project-level .pinner.toml works as a strict override. If a dependency is marked vetted locally, it will override any global compromised status, and if marked compromised locally, it overrides any global vetted status. Non-conflicting items are combined automatically.
Supported Platforms ๐
Pinner supports multiple CI/CD and git hosting platforms:
| Forge / Platform | Syntax Examples | Env Var for Token |
|---|---|---|
| GitHub | actions/checkout@v4 |
GITHUB_TOKEN |
| GitLab | include: project: 'org/repo' |
GITLAB_TOKEN |
| Bitbucket | pipe: atlassian/aws-s3-deploy:1.0.0 |
BITBUCKET_TOKEN |
| Forgejo/Gitea | uses: forgejo/action@v1 |
FORGEJO_TOKEN |
| Azure Marketplace | task: NodeTool@0 |
GITHUB_TOKEN (via monorepo) |
| AWS ECR | image: <acc>.dkr.ecr.<reg>.amazonaws.com/repo:v1 |
PINNER_OCI_PASSWORD |
| CircleCI | image: cimg/node:16 |
(Public images only) |
AWS ECR Authentication
To pin private AWS ECR images, provide the authentication token generated by the AWS CLI:
PINNER_OCI_USERNAME=AWS PINNER_OCI_PASSWORD=
CircleCI Support
Pinner explicitly supports pinning CircleCI Docker Images (e.g., cimg/*) to their immutable digests. CircleCI Orbs are not hash-pinned as they use a centralized semantic versioning registry.
CI/CD Integration ๐ค
Add this to your workflow to ensure all actions stay pinned using the native GitHub Action:
jobs:
verify-pinning:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v4
- name: Verify Pinning
uses: ffalcinelli/pinner/action@main
with:
command: 'verify'
[!TIP] Pinnerception Warning ๐ Remember to pin the pinner! Trusting a security tool to verify your pinned dependencies using a mutable tag is like hiring a security guard who leaves the keys under the doormat. If we didn't pin the pinner, who would pin the pinner's pinners? (Warning: may cause mild existential dread or recursive loops in your CI logs).
Alternatively, you can install and run the CLI directly in any custom pipeline:
jobs:
verify-pinning:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v4
- name: Install Pinner
run: curl -LsSf https://raw.githubusercontent.com/ffalcinelli/pinner/main/install.sh | sh
- name: Verify Pinning
run: pinner verify
Name Origin โ๏ธ
The name Pinner is inspired by the Pinner reaction in organic chemistry. Discovered by Adolf Pinner, this reaction involves the acid-catalyzed conversion of a reactive nitrile into a highly stable Pinner salt.
Just as the Pinner reaction transforms a volatile compound into a stable, fixed salt, this CLI transforms "floating" tags into secure, immutable, and fixed commit SHAs.
License ๐
MIT License. See LICENSE for details.