{"id":51004793,"url":"https://github.com/ffalcinelli/pinner","last_synced_at":"2026-06-20T19:02:01.027Z","repository":{"id":363514059,"uuid":"1263542632","full_name":"ffalcinelli/pinner","owner":"ffalcinelli","description":"Secure CI/CD workflows by pinning mutable tags to immutable SHA-1 hashes. A high-performance Rust CLI that preserves YAML formatting and comments. Supports GitHub, GitLab, Bitbucket, Forgejo, and Docker image pinning.","archived":false,"fork":false,"pushed_at":"2026-06-16T18:08:36.000Z","size":407,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T18:25:47.871Z","etag":null,"topics":["automation","bitbucket","circleci","cli","codeberg","devops","docker","git","github-actions","gitlab","hash-pinning","rust","security","security-tools","supply-chain-security","workflow-automation"],"latest_commit_sha":null,"homepage":"https://ffalcinelli.github.io/pinner/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ffalcinelli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["ffalcinelli"],"thanks_dev":"u/gh/ffalcinelli"}},"created_at":"2026-06-09T03:44:47.000Z","updated_at":"2026-06-16T18:07:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ffalcinelli/pinner","commit_stats":null,"previous_names":["ffalcinelli/pinner"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ffalcinelli/pinner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffalcinelli%2Fpinner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffalcinelli%2Fpinner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffalcinelli%2Fpinner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffalcinelli%2Fpinner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ffalcinelli","download_url":"https://codeload.github.com/ffalcinelli/pinner/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffalcinelli%2Fpinner/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34520463,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-19T02:00:06.005Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","bitbucket","circleci","cli","codeberg","devops","docker","git","github-actions","gitlab","hash-pinning","rust","security","security-tools","supply-chain-security","workflow-automation"],"created_at":"2026-06-20T19:02:00.328Z","updated_at":"2026-06-20T19:02:01.022Z","avatar_url":"https://github.com/ffalcinelli.png","language":"Rust","funding_links":["https://github.com/sponsors/ffalcinelli","https://thanks.dev/u/gh/ffalcinelli"],"categories":[],"sub_categories":[],"readme":"# Pinner 🧪\n\n[![Crates.io Version](https://img.shields.io/crates/v/pinner?style=flat)](https://crates.io/crates/pinner)\n[![CI Status](https://img.shields.io/github/actions/workflow/status/ffalcinelli/pinner/ci.yml?branch=main\u0026style=flat\u0026label=ci)](https://github.com/ffalcinelli/pinner/actions/workflows/ci.yml)\n[![Codecov](https://img.shields.io/codecov/c/gh/ffalcinelli/pinner?style=flat)](https://codecov.io/gh/ffalcinelli/pinner)\n[![Docs.rs](https://img.shields.io/docsrs/pinner?style=flat)](https://docs.rs/pinner)\n[![License](https://img.shields.io/badge/license-MIT-green?style=flat)](https://github.com/ffalcinelli/pinner/blob/main/LICENSE)\n[![Rust Version](https://img.shields.io/badge/rust-1.80%2B-blue?style=flat)](https://www.rust-lang.org)\n\nA 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...`).\n\n[**🚀 Get Started Guide**](https://ffalcinelli.github.io/pinner/getting-started.html)\n\n## Why Pin? 🔒\n\nUsing 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. \n\nHash-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.\n\n## Features ✨\n\n- **Domain-Driven Pipeline**: Built on a strict Scanner -\u003e Resolver -\u003e Patcher architecture, ensuring high testability, concurrency, and safe mutations.\n- **Surgical Replacement**: Uses `tree-sitter` for precise YAML parsing, preserving comments, indentation, and formatting perfectly.\n- **Multi-Forge Support**: Works with GitHub, GitLab, Bitbucket, and Forgejo/Gitea.\n- **Tag Preservation**: Automatically appends the original tag as a comment (e.g., `@\u003chash\u003e # v2`).\n- **Container Pinning**: Automatically pins Docker images to their immutable digests (e.g., `image: alpine@sha256:...`).\n- **Flexible Upgrades**: Multiple strategies to keep your actions up to date (Major, Minor, Latest).\n- **CI Ready**: Includes a `verify` mode to ensure all actions remain pinned in your PRs.\n- **Security Scanning**: A `scan` subcommand to query the OpenSSF OSV database for known vulnerabilities and supply-chain compromises.\n- **Visual Security Feedback**: Appends colorful indicators (`[✓ vetted]`, `[✗ compromised]`, or `[? not checked]`) during dry-runs and diff outputs.\n\n## Installation 🛠️\n\n### One-line installation (Recommended)\n\n**macOS/Linux:**\n```bash\ncurl -LsSf https://raw.githubusercontent.com/ffalcinelli/pinner/main/install.sh | sh\n```\n\n**Windows:**\n```powershell\npowershell -ExecutionPolicy ByPass -c \"irm https://raw.githubusercontent.com/ffalcinelli/pinner/main/install.ps1 | iex\"\n```\n\n### From source\n\n```bash\ncargo install pinner\n```\n\n## Usage 🚀\n\n### 1. Pin all actions\nScans workflows and converts all tags to pinned hashes.\n```bash\npinner pin\n```\n*Input:* `- uses: actions/checkout@v3`  \n*Output:* `- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3`\n\n### 2. Upgrade to latest\nUpdate pinned actions to their latest versions based on a strategy.\n\n\u003e [!CAUTION]\n\u003e **Automatic upgrades can undermine your security.** The primary goal of hash-pinning is to ensure you run only code you have vetted. While `verify` should be run in every CI pipeline to enforce this, `upgrade` should 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.\n\n```bash\n# Default: Upgrade to latest available release\npinner upgrade\n```\n\n```bash\n# Upgrade only within the current major version (e.g., v2.1.0 -\u003e v2.4.5)\npinner upgrade --upgrade-strategy major\n```\n\n### 3. Verify pinning\nEnsure that all actions in your workflows are pinned. Perfect for CI pipelines.\n```bash\npinner verify\n```\n\n### 4. Install Git Hook\nAutomatically install a pre-commit hook to verify pinning before every commit.\n```bash\npinner install-hook\n```\n\n### 5. Manual Set\nForcibly update a specific action to a provided hash across all workflows.\n```bash\npinner set actions/checkout 8f4b7f84864484a7bf31766abe9204da3cbe65b3\n```\n\n### 6. Initialize Configuration\nCreate a `.pinner.toml` with default settings.\n```bash\npinner init\n```\n\n### 7. Export SBOM\nGenerate a Software Bill of Materials for your CI dependencies.\n```bash\npinner export-sbom\n```\n\n### 8. Security Scan\nAudits 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.\n```bash\n# Scan workflows and interactively update your .pinner.toml config\npinner scan\n\n# Scan workflows and automatically populate .pinner.toml config (great for automation)\npinner scan --yes\n```\n\n### 9. Shell Completions\nGenerate tab-completion scripts for your shell. Automatically detects your current shell if no argument is provided.\n```bash\n# Auto-detect current shell\npinner generate-completion \u003e ~/.zshrc.d/_pinner\n\n# Or specify explicitly\npinner generate-completion bash \u003e /etc/bash_completion.d/pinner\n```\n\n## Configuration ⚙️\n\nPinner can be configured via a `.pinner.toml` file in your repository root.\n\n```toml\n# List of actions to ignore during pinning/upgrading\nignore = [\"my-org/private-action\"]\n\n# Number of concurrent API requests (default: 10)\nconcurrency = 5\n\n# Custom API URLs (for Enterprise instances)\ngithub_url = \"https://github.mycompany.com/api/v3\"\ngitlab_url = \"https://gitlab.mycompany.com/api/v4\"\n\n# Vetted (trusted) dependency hashes/references (Whitelist)\n# Supports plain strings or structured maps with tag versions and timestamps of insertion.\nvetted = [\n    # Plain string format (backwards compatible)\n    \"actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332\",\n\n    # Structured format generated automatically during \"scan\"\n    { ref = \"actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10\", tag = \"v6.0.3\", timestamp = \"2026-06-18T15:28:25Z\" }\n]\n\n# Compromised dependency hashes/references (Blacklist)\ncompromised = [\n    \"actions/checkout@badhash1234567890badhash1234567890bad\",\n    { ref = \"actions/checkout@evilhash1234567890evilhash1234567890bad\", tag = \"v3.1.0\", timestamp = \"2026-06-18T15:28:25Z\" }\n]\n\n# Disable visual security feedback (default: false)\nno_security_feedback = false\n```\n\n## Global Configuration \u0026 Overrides 🌍\n\nPinner automatically loads security configurations from global user locations, allowing you to share whitelists/blacklists across projects:\n1. `~/.cache/pinner/config.toml` (Global cache file)\n2. `~/.config/pinner/config.toml` (User configuration file)\n3. `~/.pinner.toml` (Home directory configuration file)\n\n**Precedence (Local Overrides)**:\nThe 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.\n\n\n## Supported Platforms 🌐\n\nPinner supports multiple CI/CD and git hosting platforms:\n\n| Forge / Platform | Syntax Examples | Env Var for Token |\n|-------|---------|-------------------|\n| GitHub | `actions/checkout@v4` | `GITHUB_TOKEN` |\n| GitLab | `include: project: 'org/repo'` | `GITLAB_TOKEN` |\n| Bitbucket | `pipe: atlassian/aws-s3-deploy:1.0.0` | `BITBUCKET_TOKEN` |\n| Forgejo/Gitea | `uses: forgejo/action@v1` | `FORGEJO_TOKEN` |\n| **Azure Marketplace** | `task: NodeTool@0` | `GITHUB_TOKEN` (via monorepo) |\n| **AWS ECR** | `image: \u003cacc\u003e.dkr.ecr.\u003creg\u003e.amazonaws.com/repo:v1` | `PINNER_OCI_PASSWORD` |\n| **CircleCI** | `image: cimg/node:16` | (Public images only) |\n\n### AWS ECR Authentication\nTo pin private AWS ECR images, provide the authentication token generated by the AWS CLI:\n```bash\nPINNER_OCI_USERNAME=AWS PINNER_OCI_PASSWORD=$(aws ecr get-login-password --region us-east-1) pinner pin\n```\n\n### CircleCI Support\nPinner 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.\n\n## CI/CD Integration 🤖\n\nAdd this to your workflow to ensure all actions stay pinned using the native GitHub Action:\n\n```yaml\njobs:\n  verify-pinning:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v4\n      - name: Verify Pinning\n        uses: ffalcinelli/pinner/action@main\n        with:\n          command: 'verify'\n```\n\n\u003e [!TIP]\n\u003e **Pinnerception Warning 🌀**\n\u003e 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).\n\nAlternatively, you can install and run the CLI directly in any custom pipeline:\n\n```yaml\njobs:\n  verify-pinning:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v4\n      - name: Install Pinner\n        run: curl -LsSf https://raw.githubusercontent.com/ffalcinelli/pinner/main/install.sh | sh\n      - name: Verify Pinning\n        run: pinner verify\n```\n\n## Name Origin ⚗️\n\nThe 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.\n\nJust 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.\n\n## License 📄\n\nMIT License. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fffalcinelli%2Fpinner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fffalcinelli%2Fpinner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fffalcinelli%2Fpinner/lists"}