{"id":48453983,"url":"https://github.com/kirskov/shapin","last_synced_at":"2026-04-19T09:04:33.639Z","repository":{"id":349196670,"uuid":"1201444264","full_name":"Kirskov/Shapin","owner":"Kirskov","description":"Pin floating tags in CI workflow files to immutable SHAs, making your pipelines reproducible and immune to tag mutation attacks.","archived":false,"fork":false,"pushed_at":"2026-04-10T11:58:32.000Z","size":99,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-10T12:04:22.834Z","etag":null,"topics":["cicd","compliance","security"],"latest_commit_sha":null,"homepage":"","language":"Go","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/Kirskov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"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}},"created_at":"2026-04-04T17:28:07.000Z","updated_at":"2026-04-10T11:58:34.000Z","dependencies_parsed_at":null,"dependency_job_id":"844378df-206c-4288-8ef4-884eef13b2e6","html_url":"https://github.com/Kirskov/Shapin","commit_stats":null,"previous_names":["kirskov/digestify-my-ci","kirskov/shapin"],"tags_count":39,"template":false,"template_full_name":null,"purl":"pkg:github/Kirskov/Shapin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kirskov%2FShapin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kirskov%2FShapin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kirskov%2FShapin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kirskov%2FShapin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Kirskov","download_url":"https://codeload.github.com/Kirskov/Shapin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kirskov%2FShapin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31746113,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T06:26:45.479Z","status":"ssl_error","status_checked_at":"2026-04-13T06:26:44.645Z","response_time":93,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["cicd","compliance","security"],"created_at":"2026-04-06T22:00:46.097Z","updated_at":"2026-04-19T09:04:33.625Z","avatar_url":"https://github.com/Kirskov.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Shapin\n\n[![Go 1.26.2](https://img.shields.io/badge/Go-1.26.2-00ADD8?logo=go)](https://go.dev/doc/go1.26)\n[![Go Reference](https://pkg.go.dev/badge/github.com/Kirskov/Shapin.svg)](https://pkg.go.dev/github.com/Kirskov/Shapin)\n[![Go Report Card](https://goreportcard.com/badge/github.com/Kirskov/Shapin)](https://goreportcard.com/report/github.com/Kirskov/Shapin)\n[![OpenSSF Baseline](https://www.bestpractices.dev/projects/12470/baseline)](https://www.bestpractices.dev/projects/12470)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12470/badge)](https://www.bestpractices.dev/projects/12470)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Kirskov/Shapin/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Kirskov/Shapin)\n\nPin floating tags in CI workflow files to immutable SHAs, making your pipelines reproducible and immune to tag mutation attacks.\n\n## Quick start\n\n```sh\n# Install\ncurl -fsSL https://raw.githubusercontent.com/Kirskov/Shapin/df97d9b9fd31e5e9ac80b2257d3eae7d7628509d/install.sh | sh\n\n# Preview what would be pinned (dry run, no files written)\nshapin --path ./myproject\n\n# Apply the changes\nshapin --path ./myproject --dry-run=false\n```\n\nFor GitHub Actions, a token is required:\n\n```sh\nshapin --path ./myproject --github-token ghp_xxx --dry-run=false\n```\n\n## Table of contents\n\n- [Quick start](#quick-start)\n- [What it does](#what-it-does)\n- [Supported files](#supported-files)\n- [Installation](#installation)\n  - [One-liner](#one-liner-linux--macos)\n  - [Manual](#manual)\n  - [Docker](#docker)\n  - [Verify release integrity](#verify-release-integrity)\n  - [Build from source](#build-from-source)\n- [Usage](#usage)\n- [Upgrading pinned refs](#upgrading-pinned-refs)\n- [Flags](#flags)\n- [Output formats](#output-formats)\n- [Config file](#config-file)\n- [Providers](#providers)\n  - [GitHub Actions](#github-actions)\n  - [GitLab CI](#gitlab-ci)\n  - [Forgejo Actions](#forgejo-actions)\n  - [CircleCI](#circleci)\n  - [Bitbucket Pipelines](#bitbucket-pipelines)\n  - [Woodpecker CI](#woodpecker-ci)\n  - [Dockerfile](#dockerfile)\n  - [Docker Compose](#docker-compose)\n- [When do you need a token?](#when-do-you-need-a-token)\n- [Rate limiting](#rate-limiting)\n- [What it can't do](#what-it-cant-do)\n- [Dependencies](#dependencies)\n- [Support](#support)\n- [Architecture](ARCHITECTURE.md)\n- [Contributing](CONTRIBUTING.md)\n- [Security](SECURITY.md)\n- [Governance](MAINTAINERS.md)\n\n## What it does\n\n| Reference type | Before | After |\n|---|---|---|\n| GitHub Action | `uses: actions/checkout@v4` | `uses: actions/checkout@abc1234... # v4` |\n| Forgejo Action | `uses: actions/checkout@v1` | `uses: actions/checkout@abc1234... # v1` |\n| Docker image (`image:`) | `image: maildev/maildev:2.2.1` | `image: maildev/maildev@sha256:180ef5... # maildev/maildev:2.2.1` |\n| Docker image (`image: name:`) | `image:`\u003cbr\u003e\u0026nbsp;\u0026nbsp;`name: maildev/maildev:2.2.1` | `image:`\u003cbr\u003e\u0026nbsp;\u0026nbsp;`name: maildev/maildev@sha256:180ef5... # maildev/maildev:2.2.1` |\n| Dockerfile `FROM` | `FROM golang:1.26.2-alpine AS builder` | `# golang:1.26.2-alpine`\u003cbr\u003e`FROM golang@sha256:f85330... AS builder` |\n| GitLab component ref | `component: gitlab.com/group/proj/name@v1.0.0` | `component: gitlab.com/group/proj/name@abc1234... # v1.0.0` |\n| GitLab `image:tag` variable | `TRIVY_TAG: aquasec/trivy:0.69.3` | `TRIVY_TAG: aquasec/trivy@sha256:eafae... # aquasec/trivy:0.69.3` |\n| GitLab bare version variable | `TF_VERSION: \"1.14.8\"` | `TF_DIGEST: \"sha256:6bbb82... # hashicorp/terraform:1.14.8\"` |\n| GitLab trigger input | `TF_VERSION: \"1.14.8\"` (under `inputs:`) | `TF_DIGEST: \"sha256:6bbb82... # hashicorp/terraform:1.14.8\"` |\n| GitLab dependency proxy | `image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:24.13.0` | `image: node@sha256:cd6fb7... # node:24.13.0` |\n| GitLab `services:` (bare) | `- postgres:15` | `- postgres@sha256:abc123... # postgres:15` |\n| GitLab `services:` (map) | `- name: redis:7` | `- name: redis@sha256:def456... # redis:7` |\n\nAlready-pinned refs and digests are left untouched. Every provider checks pinned SHAs against their current tag — a warning is printed if the tag has been moved to a different commit (drift detection). Using `latest` as a tag also prints a warning.\n\n## Supported files\n\nThe tool scans recursively under `--path`, skipping `node_modules`, `.git`, `vendor`, and `dist`.\n\n- **GitHub Actions**: any `.yml`/`.yaml` file inside `.github/workflows/` (and subdirectories)\n- **GitLab CI**:\n  - `.gitlab-ci.yml` / `.gitlab-ci.yaml` / `.gitlab-ci-*.yml` at any depth (supports monorepos where each subdirectory is its own project)\n  - Any `.yml`/`.yaml` file inside `.gitlab/` and its subdirectories, at any depth\n- **CircleCI**: `.circleci/config.yml` / `.circleci/config.yaml`\n- **Bitbucket Pipelines**: `bitbucket-pipelines.yml` / `bitbucket-pipelines.yaml`\n- **Forgejo Actions**: any `.yml`/`.yaml` file inside `.forgejo/workflows/` (and subdirectories)\n- **Woodpecker CI**:\n  - `.woodpecker.yml` / `.woodpecker.yaml` at the root\n  - Any `.yml`/`.yaml` file inside `.woodpecker/` and its subdirectories\n- **Dockerfiles**: `Dockerfile`, `Dockerfile.*`, `*.dockerfile`, `*.Dockerfile` (at any depth) — pins `FROM image:tag` lines\n- **Docker Compose**: `docker-compose.yml`, `docker-compose.yaml`, `docker-compose.*.yml`, `compose.yml`, `compose.yaml`\n\n## Installation\n\n### One-liner (Linux / macOS)\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/Kirskov/Shapin/df97d9b9fd31e5e9ac80b2257d3eae7d7628509d/install.sh | sh\n```\n\nThe script URL is pinned to a commit SHA so the install script itself cannot be tampered with. Supports Ubuntu, Debian, Kali, Arch, Alpine, Red Hat, Fedora, and macOS. The script will automatically detect your OS and architecture, download the correct binary, and install it to `/usr/local/bin`.\n\nIf you hit GitHub API rate limits (common on shared corporate networks), pass a [personal access token](https://github.com/settings/tokens) with the `public_repo` scope:\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/Kirskov/Shapin/df97d9b9fd31e5e9ac80b2257d3eae7d7628509d/install.sh | GITHUB_TOKEN=ghp_xxx sh\n```\n\nTo install a specific version, use the [Manual](#manual) method below.\n\n### Manual\n\nAll releases are [immutable](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#immutable-releases) — the Git tag, commit SHA, and release assets are locked and cannot be modified or deleted after publication.\n\nDownload the binary for your platform from the [releases page](https://github.com/Kirskov/Shapin/releases), verify the release attestation, and move it to your PATH:\n\n```sh\n# Example for Linux amd64\ncurl -fsSL https://github.com/Kirskov/Shapin/releases/download/v1.4.0/shapin-v1.4.0-linux-amd64 -o shapin\ngh attestation verify shapin --repo Kirskov/Shapin\nchmod +x shapin\nsudo mv shapin /usr/local/bin/\n```\n\n### Docker\n\nImages are published to GHCR and available for `linux/amd64` and `linux/arm64`. Always reference by digest, not tag:\n\n```sh\ndocker run --rm -v $(pwd):/repo ghcr.io/kirskov/shapin@sha256:ceb8dd7ec84b7478a2488beae5af92a6735620acb826106c8b20920023a9041a # v1.4.0 --path /repo\n```\n\nApply changes (disable dry-run):\n\n```sh\ndocker run --rm -v $(pwd):/repo ghcr.io/kirskov/shapin@sha256:ceb8dd7ec84b7478a2488beae5af92a6735620acb826106c8b20920023a9041a # v1.4.0 --path /repo --dry-run=false\n```\n\nWith API tokens:\n\n```sh\ndocker run --rm \\\n  -v $(pwd):/repo \\\n  -e GITHUB_TOKEN=ghp_xxx \\\n  -e GITLAB_TOKEN=glpat_xxx \\\n  ghcr.io/kirskov/shapin@sha256:ceb8dd7ec84b7478a2488beae5af92a6735620acb826106c8b20920023a9041a # v1.4.0 --path /repo\n```\n\nThe digest for each release is listed on the [releases page](https://github.com/Kirskov/Shapin/releases). Update the digest when upgrading to a new version.\n\n#### Verify the image signature\n\nImages are signed with [cosign](https://github.com/sigstore/cosign) keyless signing via GitHub Actions OIDC. Verify before running:\n\n```sh\ncosign verify \\\n  --certificate-identity \"https://github.com/Kirskov/Shapin/.github/workflows/release.yml@refs/tags/v1.4.0\" \\\n  --certificate-oidc-issuer \"https://token.actions.githubusercontent.com\" \\\n  ghcr.io/kirskov/shapin@sha256:ceb8dd7ec84b7478a2488beae5af92a6735620acb826106c8b20920023a9041a # v1.4.0\n```\n\n### Verify release integrity\n\nEvery release asset can be verified using three independent mechanisms:\n\n**1. Checksum verification** — a `checksums.txt` SHA-256 manifest is included in every release:\n\n```sh\n# Download the binary and checksum file\ncurl -fsSL https://github.com/Kirskov/Shapin/releases/download/v1.4.0/shapin-v1.4.0-linux-amd64 -o shapin\ncurl -fsSL https://github.com/Kirskov/Shapin/releases/download/v1.4.0/checksums.txt -o checksums.txt\n\n# Verify (expected output: \"shapin-v1.4.0-linux-amd64: OK\")\nsha256sum --ignore-missing -c checksums.txt\n```\n\n**2. cosign bundle signature** — each binary is signed with [cosign](https://github.com/sigstore/cosign) keyless signing via the Sigstore transparency log:\n\n```sh\ncurl -fsSL https://github.com/Kirskov/Shapin/releases/download/v1.4.0/shapin-v1.4.0-linux-amd64.sigstore.json -o shapin.sigstore.json\ncosign verify-blob shapin \\\n  --bundle shapin.sigstore.json \\\n  --certificate-identity \"https://github.com/Kirskov/Shapin/.github/workflows/release.yml@refs/tags/v1.4.0\" \\\n  --certificate-oidc-issuer \"https://token.actions.githubusercontent.com\"\n# Expected output: Verified OK\n```\n\n**3. SLSA provenance attestation** — build provenance is attested via GitHub's attestation framework:\n\n```sh\ngh attestation verify shapin --repo Kirskov/Shapin\n# Expected output: Attestation verification was successful\n```\n\n### Build from source\n\n```sh\ngit clone https://github.com/Kirskov/Shapin.git\ncd Shapin\ngo build -o shapin ./cmd/shapin\n```\n\n## Usage\n\n```sh\n# Dry run — show what would change, write nothing (default)\nshapin --path ./myproject\n\n# Apply changes\nshapin --path ./myproject --dry-run=false\n\n# Only pin Docker images, leave refs alone\nshapin --path ./myproject --pin-refs=false\n\n# Only pin CI refs, leave images alone\nshapin --path ./myproject --pin-images=false\n\n# Exclude specific files (comma-separated globs)\nshapin --path ./myproject --exclude \".github/workflows/generated.yml,*.skip.yml\"\n\n# Use a config file\nshapin --config .shapin.json\n\n# With API tokens (required to resolve unpinned action refs)\nshapin --path ./myproject --github-token ghp_xxx --gitlab-token glpat_xxx\n\n# Self-hosted GitLab instance\nshapin --path ./myproject --gitlab-host https://gitlab.mycompany.com --gitlab-token glpat_xxx\n```\n\n## Upgrading pinned refs\n\nTo upgrade a pinned ref to a newer version, update it and rerun `shapin`.\n\n**Action / component refs** — change the SHA back to the new tag:\n\n```yaml\n# before (pinned)\n- uses: actions/checkout@abc1234... # v4\n# edit to\n- uses: actions/checkout@v5\n```\n\n**Version variables** — just set the new version directly in the `_DIGEST` key:\n\n```yaml\n# before (pinned)\nTF_DIGEST: \"sha256:6bbb82... # hashicorp/terraform:1.14.8\"\n# edit to\nTF_DIGEST: \"1.15.0\"\n```\n\nThen rerun `shapin --path .` to resolve the new digest.\n\n## Flags\n\n| Flag | Default | Description |\n|---|---|---|\n| `--path` | `.` | Path to the project to scan |\n| `--dry-run` | `true` | Show diff without writing files |\n| `--pin-refs` | `true` | Pin `uses:` and `component:` refs to SHAs |\n| `--pin-images` | `true` | Pin Docker `image:` tags to digests |\n| `--exclude` | — | Comma-separated glob patterns of files to skip |\n| `--config` | `.shapin.json` | Path to config file |\n| `--github-token` | `$GITHUB_TOKEN` | GitHub API token |\n| `--gitlab-token` | `$GITLAB_TOKEN` | GitLab API token |\n| `--gitlab-host` | `https://gitlab.com` | GitLab instance URL |\n| `--forgejo-host` | `https://codeberg.org` | Forgejo instance URL |\n| `--forgejo-token` | `$FORGEJO_TOKEN` | Forgejo API token |\n| `--output` | — | Write output to a file instead of stdout |\n| `--format` | `text` | Output format: `text`, `json`, or `sarif` |\n\nTokens can also be set via environment variables `GITHUB_TOKEN` and `GITLAB_TOKEN`.\n\nWarnings (drift, branch refs, resolution failures) are always written to stderr, so they never pollute `--output` or piped output.\n\n## Output formats\n\n### JSON\n\n```sh\nshapin --path ./myproject --format json --output results.json\n```\n\nOutputs a JSON array of file changes, each with the file path and a list of old/new line pairs with their line numbers.\n\n### SARIF\n\n```sh\nshapin --path ./myproject --format sarif --output results.sarif\n```\n\nOutputs [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html) for upload to GitHub Code Scanning. Each result includes the file path and exact line number, so annotations appear inline in pull requests.\n\nUpload example:\n\n```yaml\n- name: Run Shapin\n  run: shapin --path . --format sarif --output shapin.sarif --dry-run=false\n\n- name: Upload SARIF\n  uses: github/codeql-action/upload-sarif@v3\n  with:\n    sarif_file: shapin.sarif\n```\n\n## Config file\n\nAll flags can be set in a `.shapin.json` file at the root of your project. CLI flags always take precedence over the config file.\n\n```json\n{\n  \"dry-run\": false,\n  \"pin-refs\": true,\n  \"pin-images\": false,\n  \"github-token\": \"ghp_...\",\n  \"gitlab-host\": \"https://gitlab.mycompany.com\",\n  \"exclude\": [\n    \".github/workflows/generated.yml\",\n    \".gitlab/auto-*.yml\"\n  ]\n}\n```\n\n## Providers\n\n### GitHub Actions\n\nPins `uses: owner/repo@tag` refs to their commit SHA. Requires `--github-token` to call the GitHub API.\n\n```yaml\n- uses: actions/checkout@v4\n# → - uses: actions/checkout@abc1234... # v4\n```\n\nAlready-pinned refs (`@sha # tag`) are checked for drift — a warning is printed if the tag has been moved to a different commit.\n\n**Branch ref warning:**\nIf a ref points to a well-known branch name (`main`, `master`, `develop`, `development`) or a common branch prefix (`feat/`, `fix/`, `bug/`, `hotfix/`, `feature/`, `bugfix/`, `release/`), a red warning is printed — the pinned SHA will become stale as the branch moves forward. Use a tag instead.\n\n---\n\n### GitLab CI\n\nScans `.gitlab-ci.yml`, `.gitlab-ci.yaml`, `.gitlab-ci-*.yml`, and any `.yml`/`.yaml` inside `.gitlab/` — at any directory depth, supporting monorepos where each subdirectory is its own project.\n\n#### Component refs\n\nComponent refs (`component: path@tag`) are pinned to their commit SHA using the GitLab tags API — no token required for public components.\n\n```yaml\ninclude:\n  - component: gitlab.com/my-group/my-catalogue/deploy@2.1.4\n    # → component: gitlab.com/my-group/my-catalogue/deploy@abc1234... # 2.1.4\n```\n\nThe predefined variables `$CI_SERVER_FQDN` and `$CI_SERVER_HOST` are automatically substituted with `--gitlab-host` (default: `gitlab.com`):\n\n```yaml\ncomponent: $CI_SERVER_FQDN/components/sast/sast@3.4.0\n# → component: $CI_SERVER_FQDN/components/sast/sast@0a29cf... # 3.4.0\n```\n\nOther `$VARIABLE` prefixes (e.g. `$SPLIT_GLOBAL_COMPONENT_ROOT`) cannot be resolved and are left untouched.\n\nFor **private components**, pass `--gitlab-token`. Without it a warning is printed:\n```\nwarn: GitLab component .../private-comp@v1.0.0: HTTP 404 — try --gitlab-token if this is a private component\n```\n\n**Branch ref warning:**\nSame as GitHub Actions — if the component ref is a well-known branch name, a red warning is printed.\n\n#### Version inputs\n\nTwo patterns are detected at any nesting level across the entire file:\n\n**1. `image:tag` values** — keys containing `TAG` with a full `image:tag` value:\n\n```yaml\nSCANNER_TAG: myregistry.com/custom-scanner:1.2.3\n# → SCANNER_TAG: myregistry.com/custom-scanner@sha256:... # myregistry.com/custom-scanner:1.2.3\n```\n\n**2. Bare version values** — keys ending or starting with `_VERSION`, `_TAG`, or `_DIGEST` whose stem matches a built-in or user-supplied image mapping. The key is renamed to use `_DIGEST`:\n\n```yaml\nTF_VERSION: '1.13.5'     # → TF_DIGEST: 'sha256:...' # hashicorp/terraform:1.13.5\nVERSION_TF: '1.13.5'     # → DIGEST_TF: 'sha256:...' # hashicorp/terraform:1.13.5\n```\n\nValues starting with `$` (CI variable interpolation) or already containing a digest are left untouched.\n\n#### Built-in stem mappings\n\nThe stem is the key name with `_VERSION`, `_TAG`, or `_DIGEST` stripped (prefix or suffix):\n\n| Stem(s) | Docker image |\n|---|---|\n| `TF`, `TERRAFORM` | `hashicorp/terraform` |\n| `NODE`, `NODEJS` | `node` |\n| `TRIVY` | `aquasec/trivy` |\n| `JAVA` | `eclipse-temurin` |\n| `ALPINE` | `alpine` |\n| `PYTHON` | `python` |\n| `GO`, `GOLANG` | `golang` |\n| `RUBY` | `ruby` |\n| `RUST` | `rust` |\n| `DOTNET` | `mcr.microsoft.com/dotnet/sdk` |\n| `KUBECTL` | `bitnami/kubectl` |\n| `HELM` | `alpine/helm` |\n| `POSTGRES` | `postgres` |\n| `MYSQL` | `mysql` |\n| `REDIS` | `redis` |\n| `NGINX` | `nginx` |\n| `SONAR`, `SONARQUBE` | `sonarsource/sonar-scanner-cli` |\n| `AWS_CLI`, `AWSCLI` | `amazon/aws-cli` |\n| `CURL` | `curlimages/curl` |\n| `GIT_CLIFF` | `orhunp/git-cliff` |\n| `DOCKER`, `DIND` | `docker` |\n| `KANIKO` | `gcr.io/kaniko-project/executor` |\n| `GRADLE` | `gradle` |\n| `MAVEN`, `MVN` | `maven` |\n| `PHP` | `php` |\n| `ELASTICSEARCH`, `ES` | `elasticsearch` |\n| `MONGO`, `MONGODB` | `mongo` |\n| `RABBITMQ` | `rabbitmq` |\n| `GRYPE` | `anchore/grype` |\n| `SEMGREP` | `semgrep/semgrep` |\n| `COSIGN` | `cgr.dev/chainguard/cosign` |\n| `PACKER` | `hashicorp/packer` |\n| `VAULT` | `hashicorp/vault` |\n| `GOLANGCI`, `GOLANGCI_LINT` | `golangci/golangci-lint` |\n| `OPENTOFU`, `TOFU` | `ghcr.io/opentofu/opentofu` |\n| `VALKEY` | `valkey/valkey` |\n| `GRAFANA` | `grafana/grafana` |\n| `PROMETHEUS` | `prom/prometheus` |\n| `ALERTMANAGER` | `prom/alertmanager` |\n| `TRAEFIK` | `traefik` |\n| `CADDY` | `caddy` |\n| `TELEGRAF` | `telegraf` |\n| `BASH` | `bash` |\n| `SELENIUM` | `selenium/standalone-chrome` |\n| `SYFT` | `anchore/syft` |\n\nFor images not in this list, add a `tag-mappings` entry to `.shapin.json`:\n\n```json\n{\n  \"tag-mappings\": {\n    \"MYAPP\": \"registry.internal/myapp\",\n    \"TF\": \"myregistry.internal/mirror/terraform\"\n  }\n}\n```\n\nUser-supplied mappings override the built-ins.\n\n#### Dependency proxy\n\nImages pulled through the [GitLab Dependency Proxy](https://docs.gitlab.com/ee/user/packages/dependency_proxy/) use a CI variable as their registry prefix:\n\n```yaml\nimage: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:24.13.0-alpine3.23\nimage: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/alpine:3.20\n```\n\nShapin automatically strips the proxy prefix and resolves the underlying Docker Hub image to a digest:\n\n```yaml\nimage: node@sha256:cd6fb7... # node:24.13.0-alpine3.23\nimage: alpine@sha256:... # alpine:3.20\n```\n\nBoth `${VAR}/` and `$VAR/` syntaxes are supported for `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` and `CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX`.\n\n\u003e **Note:** The GitLab Dependency Proxy only supports Docker Hub images. Shapin resolves the stripped image name against Docker Hub, which matches what the proxy itself does.\n\nThe `image: name:` map form is also supported:\n\n```yaml\nimage:\n  name: maildev/maildev:2.2.1\n  entrypoint: [\"\"]\n# →\nimage:\n  name: maildev/maildev@sha256:180ef5... # maildev/maildev:2.2.1\n  entrypoint: [\"\"]\n```\n\n#### Services\n\nDocker images in `services:` blocks are pinned, both the bare form and the `name:` map form:\n\n```yaml\nservices:\n  - postgres:15\n  - name: redis:7\n    alias: cache\n# →\nservices:\n  - postgres@sha256:abc123... # postgres:15\n  - name: redis@sha256:def456... # redis:7\n    alias: cache\n```\n\n**`latest` warning:**\nUsing `latest` as a tag is not pinnable to a meaningful digest — a warning is printed on stderr and the image is left untouched:\n```\nwarn: docker image postgres:latest: avoid 'latest' — pin to an explicit tag\n```\n\n**Limitations:**\n- `extends:` and `!reference` template includes are not followed\n\n---\n\n### Forgejo Actions\n\nPins `uses: owner/repo@tag` refs to their commit SHA. Falls back to `code.forgejo.org` for community actions.\n\n```yaml\n- uses: actions/checkout@v1\n# → - uses: actions/checkout@abc1234... # v1\n```\n\n**Branch ref warning:**\nSame as GitHub Actions — a red warning is printed if the ref is a well-known branch name.\n\n---\n\n### CircleCI\n\nPins Docker `image:` tags inside `.circleci/config.yml` and `.circleci/config.yaml` to digests.\n\n**Limitations:**\n- CircleCI orbs use semver versioning with no SHA pinning API — only `image:` tags are pinned\n\n---\n\n### Bitbucket Pipelines\n\nPins Docker `image:` tags inside `bitbucket-pipelines.yml` and `bitbucket-pipelines.yaml` to digests.\n\n**Limitations:**\n- Bitbucket Pipes use semver versioning with no SHA pinning API — only `image:` tags are pinned\n\n---\n\n### Woodpecker CI\n\nPins Docker `image:` tags inside `.woodpecker.yml`, `.woodpecker.yaml`, and any `.yml`/`.yaml` inside `.woodpecker/`.\n\n**Limitations:**\n- Woodpecker plugin steps are pinned by Docker image digest, but there is no SHA pinning API for the plugin registry itself\n\n---\n\n### Dockerfile\n\nPins `FROM image:tag` lines to digests at any depth. The `AS alias` is preserved. The original tag is recorded on the line above as a comment — Docker does not allow inline comments on `FROM` lines.\n\n```dockerfile\nFROM golang:1.26.2-alpine AS builder\n# →\n# golang:1.26.2-alpine\nFROM golang@sha256:... AS builder\n```\n\n`FROM scratch` is left untouched.\n\n#### GitLab CI `spec: inputs:` defaults\n\nWhen a GitLab CI file declares its inputs using the `spec: inputs:` preamble with a nested `default:` version, Shapin pins the default value and updates the `description:` field:\n\n```yaml\n# before\nspec:\n  inputs:\n    TF_IMAGE_DIGEST:\n      default: \"1.14.8\"\n      description: \"SHA256 digest of hashicorp/terraform\"\n\n# after\nspec:\n  inputs:\n    TF_IMAGE_DIGEST:\n      default: \"sha256:42ecfb...\"\n      description: \"SHA256 digest of hashicorp/terraform:1.14.8\"\n```\n\nThe `$[[ inputs.TF_IMAGE_DIGEST ]]` forwarding references in `include:` component inputs are left untouched.\n\n---\n\n### Docker Compose\n\nPins `image:` tags in `docker-compose.yml`, `docker-compose.yaml`, `docker-compose.*.yml`, `compose.yml`, and `compose.yaml` files at any depth.\n\n---\n\n## When do you need a token?\n\n| Operation | Token needed? |\n|---|---|\n| Pinning Docker images | No — uses the public registry API |\n| Pinning GitHub Actions `uses:` | Yes — `--github-token` |\n| Pinning GitLab components (public) | No — uses the public GitLab API |\n| Pinning GitLab components (private) | Yes — `--gitlab-token` |\n| Pinning Forgejo actions | No for public, `--forgejo-token` for private |\n| Scanning already-pinned files | No — skipped immediately |\n\n## Rate limiting\n\nAPI calls are automatically retried on HTTP 429 (rate limited) or 503 responses. The retry delay is read from the `Retry-After` or `X-RateLimit-Reset` headers, falling back to 60 seconds. Up to 3 retries are attempted before giving up.\n\n## What it can't do\n\n- **Private Docker registries** — only public registries (Docker Hub, GHCR, Quay.io, etc.) are supported\n- **Branch refs** — pinning `@main` resolves to the current HEAD SHA, which will become stale — use tags when possible\n- **Unknown GitLab CI variable prefixes** — component paths starting with `$SPLIT_GLOBAL_COMPONENT_ROOT` or similar custom variables cannot be resolved\n- **`parallel: matrix:` image arrays** — matrix values are CI variables resolved at runtime; Shapin cannot pin array entries like `IMAGE: [alpine:3.20, debian:12]`\n\n## Dependencies\n\nShapin has minimal runtime dependencies, all managed via Go modules.\n\n**Selection** — dependencies are chosen to be small, well-maintained, and auditable. The full dependency list with pinned versions and checksums is declared in [`go.mod`](go.mod) and [`go.sum`](go.sum).\n\n**Obtaining** — dependencies are fetched by the Go toolchain (`go mod download`) during development and CI builds. All checksums are verified against `go.sum` and the [Go checksum database](https://sum.golang.org) on every build.\n\n**Tracking** — [Dependabot](https://github.com/Kirskov/Shapin/blob/main/.github/dependabot.yml) is configured to open weekly pull requests for outdated Go module and GitHub Actions dependencies. Security advisories are tracked via GitHub's dependency graph and the `Vulnerabilities` OpenSSF Scorecard check.\n\n## Support\n\nOnly the **latest release** is actively supported. When a new version is published, the previous release is no longer maintained.\n\n| Type | Included |\n|---|---|\n| Security vulnerability fixes | Yes — latest release only |\n| Bug fixes | Yes — latest release only |\n| Backports to older releases | No |\n\n**A release stops receiving security updates as soon as a newer version is published.** Users should upgrade to the latest release to remain protected.\n\nFor bug reports open a [GitHub Issue](https://github.com/Kirskov/Shapin/issues). For security vulnerabilities follow the [private disclosure process](SECURITY.md). There is no formal LTS program — upgrading is straightforward as Shapin is a single self-contained binary.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkirskov%2Fshapin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkirskov%2Fshapin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkirskov%2Fshapin/lists"}