pinact
Install | How to use | Configuration
pinact is a CLI to edit GitHub Workflow and Composite action files and pin versions of Actions and Reusable Workflows.
pinact can also update their versions, verify version annotations, and create reviews.
pinact run
$ git diff
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 84bd67a..5d92e44 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -113,17 +113,17 @@ jobs:
needs: path-filter
permissions: {}
steps:
- - uses: actions/checkout@83b7061638ee4956cf7545a6f7efe594e5ad0247 # v3
- - uses: actions/setup-go@v4
+ - uses: actions/checkout@83b7061638ee4956cf7545a6f7efe594e5ad0247 # v3.5.1
+ - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
- name: Cache Primes
id: cache-primes
- uses: actions/cache@v3.3.1
+ uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: prime-numbers
key: ${{ runner.os }}-primes
actionlint:
- uses: suzuki-shunsuke/actionlint-workflow/.github/workflows/actionlint.yaml@v0.5.0
+ uses: suzuki-shunsuke/actionlint-workflow/.github/workflows/actionlint.yaml@b6a5f966d4504893b2aeb60cf2b0de8946e48504 # v0.5.0
with:
aqua_version: v2.3.4
permissions:
Creating reviews:

Motivation
It is a good manner to pin GitHub Actions versions by commit hash.
GitHub tags are mutable so they have a substantial security and reliability risk.
See also Security hardening for GitHub Actions - GitHub Docs
> Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release.
> Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload
:thumbsup:
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
:thumbsdown:
uses: actions/cache@v3
uses: actions/cache@v3.3.1
Why not using Renovate's helpers:pinGitHubActionDigestsToSemver preset?
The Renovate preset helpers:pinGitHubActionDigestsToSemver is useful, but pinact is still useful:
- Renovate can't pin actions in pull requests before merging them.
If you use linters such as ghalint in CI, you need to pin actions before merging pull requests
(ref. ghalint policy to enforce actions to be pinned)
- Even if you use Renovate, sometimes you would want to update actions manually
- pinact is useful for non Renovate users
- pinact supports verifying version annotations
GitHub Access token
pinact calls GitHub REST API to get commit hashes and tags.
You can pass GitHub Access token via environment variable PINACT_GITHUB_TOKEN or GITHUB_TOKEN.
If no GitHub Access token is passed, pinact calls GitHub REST API without access token.
About GitHub Enterprise Server, see also GitHub Access Token for GHES.
Manage GitHub Access token using ghtkn
pinact >= v3.8.0
You can create a GitHub App User Access Token by ghtkn integration.
About ghtkn, please see the document of ghtkn.
You need to set up ghtkn first.
export PINACT_GHTKN=true
Manage GitHub Access token using Keyring
pinact >= v3.1.0
You can manage a GitHub Access token using secret store such as Windows Credential Manager, macOS Keychain, and GNOME Keyring.
- Configure a GitHub Access token by
pinact token set command:
$ pinact token set
Enter a GitHub access token: # Input GitHub Access token
or you can also pass a GitHub Access token via standard input:
echo "" | pinact token set -stdin
- Enable the feature by setting the environment variable
PINACT_KEYRING_ENABLED:
export PINACT_KEYRING_ENABLED=true
Note that if the environment variable GITHUB_TOKEN is set, this feature gets disabled.
You can remove a GitHub Access token from keyring by pinact token rm command:
pinact token rm
How to use
Please run pinact run on a Git repository root directory, then target files are fixed.
pinact run
Default target files are:
.github/workflows/*.yml
.github/workflows/*.yaml
action.yml
action.yaml
*/action.yml
*/action.yaml
*/*/action.yml
*/*/action.yaml
*/*/*/action.yml
*/*/*/action.yaml
You can change target files by command line arguments or configuration files.
e.g.
pinact run example.yaml
Update actions
#663 pinact >= v1.1.0
You can update actions using the -update (-u) option:
pinact run -u
Skip recently released versions
#1266 pinact >= v3.5.0
You can skip recently released versions using the --min-age (-m) option or the environment variable PINACT_MIN_AGE.
This helps avoid updating to potentially unstable versions that haven't had time to prove their stability.
pinact run -u --min-age 7
or
export PINACT_MIN_AGE=7
pinact run -u
This command skips versions released within the last 7 days.
- For GitHub Releases, the
PublishedAt date is checked
- For tags, the commit's
Committer.Date is checked (requires additional API call)
Fix example codes in documents
pinact can fix example codes in documents too.
pinact run README.md
Create reviews

As of pinact v3.3.0, pinact can create reviews by GitHub API.
A GitHub access token with pull_requests:write permission is required.
> [!NOTE]
> As of pinact v3.7.1, pinact supports SARIF format output.
> We recommend using the SARIF format output with reviewdog rather than the -review option.
pinact run \
-review \
-repo-owner \
-repo-name \
-pr \
-sha
If pinact is run via GitHub Actions pull_request event, options are auto-completed.
> [!WARNING]
> GitHub can't create pull request reviews on files not changed by the pull request.
> When pinact fails to create reviews, pinact outputs warning and creates GitHub Actions error messages to log instead.
> You can ignore the warning like this:
> > WARN[0004] create a review comment error="create a review comment: POST https://api.github.com/repos/szksh-lab-2/test-github-action/pulls/317/comments: 422 Validation Failed [{Resource:PullRequestReviewComment Field:pull_request_review_thread.path Code:invalid Message:} {Resource:PullRequestReviewComment Field:pull_request_review_thread.diff_hunk Code:missing_field Message:}]" line=" - uses: suzuki-shunsuke/watch-star-action@feat/first-pr" line_number=14 pinact_version=3.3.0-5 program=pinact review_pr_number=317 review_repo_name=test-github-action review_repo_owner=szksh-lab-2 review_sha=92f0b04efdc10acb793e78bdd1f70958dd3fd9a3 workflow_file=.github/workflows/watch.yaml >

Generate a configuration file .pinact.yaml
A configuration file is optional.
You can create a configuration file .pinact.yaml by pinact init.
pinact init
You can change the output path.
pinact init '.github/pinact.yaml'
About the configuration, please see Configuration.
Validation
pinact >= v1.6.0 #816
Instead of fixing files, you can validate if actions are pinned by --check option:
pinact run --check
Using this option, pinact doesn't fix files.
If actions aren't pinned, the command fails.
$ pinact run --check
ERRO[0000] parse a line action=actions/checkout@v2 error="action isn't pinned" pinact_version= program=pinact workflow_file=testdata/foo.yaml
ERRO[0000] parse a line action=actions/cache@v3.3.1 error="action isn't pinned" pinact_version= program=pinact workflow_file=testdata/foo.yaml
ERRO[0000] parse a line action=rharkor/caching-for-turbo@v1.6 error="action isn't pinned" pinact_version= program=pinact workflow_file=testdata/foo.yaml
ERRO[0000] parse a line action=actions/checkout@v3 error="action isn't pinned" pinact_version= program=pinact workflow_file=testdata/foo.yaml
ERRO[0000] parse a line action=actions/checkout@v3 error="action isn't pinned" pinact_version= program=pinact workflow_file=testdata/foo.yaml
ERRO[0000] parse a line action=suzuki-shunsuke/actionlint-workflow/.github/workflows/actionlint.yaml@v0.5.0 error="action isn't pinned" pinact_version= program=pinact workflow_file=testdata/foo.yaml
$ echo $?
1
If -check is set, files aren't fixed and no diff is outputted.
If you want to fix files, please use -fix option.
pinact run -check -fix
And if you want to output diff, please use -diff option.
pinact run -check -diff
Verify version annotations
Please see the document.
Output diff
$ pinact run -diff
INFO[0000] action isn't pinned
.github/workflows/test.yaml:8
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
pinact_version=v3.0.0-local program=pinact
Pin branches
pinact >= v3.10.0, #1529
By default, pinact doesn't pin branches such as main or master.
If you want to pin specific branches, you can use the --branch-to-tag option.
pinact run --branch-to-tag ''
The value is evaluated as a regular expression with partial match, just like --include / --exclude.
Anchor with ^...$ for an exact match — for short branch names like main this is recommended to avoid matching mainline etc.
Versions that don't match any of the supplied regexps continue to error out as before.
The branch is converted to the latest stable tag of the action. Pre-releases are used only when no stable tag exists.
--min-age is honored: when set, tags released within the cooldown window are skipped.
--branch-to-tag can be specified multiple times.
e.g.
pinact run --branch-to-tag '^main$' --branch-to-tag '^release/.*$'
-diff, -check, -fix options
The behaviour of pinact run command is changed by command line options -diff, -check, and -fix.
Default behaviour:
- Fix files
- Exit with code 1 if actions aren't pinned
- Don't output changes
Each option's behaviour:
-check disables to fix files
- If
-check is used with -fix, it fixes files. In that case, -check has no meaning
-diff outputs changes and disables to fix files
-fix uses with -diff to output changes and fix files
Fix or exclude only specific actions
#1082 pinact >= v3.4.0
You can fix only specific actions using the -include (-i) option.
You can also exclude only specific actions using the -exclude (-e) option.
e.g.
pinact run -i "actions/.*" -i "^aquaproj/aqua-installer$"
pinact run -e "actions/.*" -e "^aquaproj/aqua-installer$"
SARIF
pinact >= v3.7.0 #1294
pinact can output the result in the SARIF format.
pinact run --format sarif
This format is useful to integration tools like reviewdog and GitHub SARIF Code Scanning.
Reviewdog
pinact run --diff --format sarif |
reviewdog -f sarif -name pinact -reporter github-pr-review
GitHub SARIF Code Scanning
- run: pinact run --diff --format sarif > sarif.json || true
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: sarif.json
category: pinact
GitHub Actions
https://github.com/suzuki-shunsuke/pinact-action
We develop GitHub Actions to pin GitHub Actions and reusable workflows by pinact.
Configuration
A configuration file is optional.
pinact supports a configuration file .pinact.yaml, .github/pinact.yaml, .pinact.yml or .github/pinact.yml.
You can also specify the configuration file path by the environment variable PINACT_CONFIG or command line option -c.
As of pinact v2.2.0, pinact configuration file has a schema version.
version: 3
In general, you should use the latest schema version.
Schema v3 (latest)
pinact v2.2.0 or later supports this version.
.pinact.yaml
e.g.
version: 3
files:
- pattern: .github/workflows/*.yml
- pattern: .github/workflows/*.yaml
- pattern: .github/actions/*/action.yml
- pattern: .github/actions/*/action.yaml
ignore_actions:
# slsa-framework/slsa-github-generator doesn't support pinning version
# > Invalid ref: 68bad40844440577b33778c9f29077a3388838e9. Expected ref of the form refs/tags/vX.Y.Z
# https://github.com/slsa-framework/slsa-github-generator/issues/722
- name: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml
ref: "v\\d+\\.\\d+\\.\\d+"
- name: suzuki-shunsuke/.*
ref: main
# GitHub Enterprise Server Support
ghes:
api_url: https://ghes.example.com
fallback: true # optional, default is false
# Separator between version and tag comment (optional, default is " # ")
# pinact >= v3.9.0
separator: " # "
files
This is optional.
A list of target files.
files[].pattern
This is required.
A glob pattern of target files.
Go's path/filepath#Glob is used.
A relative path from pinact's configuration file.
If files are passed via positional command line arguments, the configuration is ignored.
e.g.
files:
- pattern: .github/workflows/*.yml
- pattern: .github/workflows/*.yaml
- pattern: README.md
ignore_actions
This is optional. A list of ignored actions and reusable workflows.
ignore_actions[].name
This is required.
A regular expression of ignored actions and reusable workflows.
ignore_actions:
- name: actions/.*
ref: main
> [!WARNING]
> Regular expressions must match with action names exactly.
> For instance, name: actions/ doesn't match with actions/checkout
Regarding regular expressions, Go's regexp package is used.
ignore_actions[].ref
This is required.
A regular expression of ignored action versions (branch, tag, or commit hash).
> [!WARNING]
> Regular expressions must match with action versions exactly.
> For instance, ref: main doesn't match with malicious-main
ghes
See GitHub Enterprise Support.
separator
pinact >= v3.9.0 #1365 #1372
This is optional. Default is #.
The separator between the action version (commit SHA) and the version tag comment.
It must include #.
You can also configure the separator by command line option --separator (-sep) or environment variable PINACT_SEPARATOR.
e.g.
# Default separator " # "
separator: " # "
# Results in: uses: actions/checkout@abc123... # v3.5.0
# Custom separator " # tag="
separator: " # tag="
# Results in: uses: actions/checkout@abc123... # tag=v3.5.0
# Custom separator with double space before #
separator: " # "
# Results in: uses: actions/checkout@abc123... # v3.5.0
Old Schemas
Please see here.
JSON Schema
If you look for a CLI tool to validate configuration with JSON Schema, ajv-cli is useful.
ajv --spec=draft2020 -s json-schema/pinact.json -d pinact.yaml
Input Complementation by YAML Language Server
Please see the comment too.
Version: main
# yaml-language-server: $schema=https://raw.githubusercontent.com/suzuki-shunsuke/pinact/main/json-schema/pinact.json
Or pinning version:
# yaml-language-server: $schema=https://raw.githubusercontent.com/suzuki-shunsuke/pinact/v1.1.2/json-schema/pinact.json
Q. Why doesn't pinact pin some actions?
> [!TIP]
> Since v3.10.0, the --branch-to-tag option lets you opt-in to pinning specific branches to the latest stable tag of an action.
In some cases pinact doesn't pin versions intentionally, which may confuse you.
So we describe the reason here.
By default, pinact doesn't pin actions whose versions aren't semver (e.g. main, master, release/v1).
This is because pinact is designed as a safe tool so that it doesn't change workflows behaviour.
pinact pins actions but doesn't change SHA of actions at the moment when pinact pins versions.
This design enables you to accept changes by pinact safely.
For instance, pinact changes the version v1 to v1.1.0 if their SHA are equivalent.
If there are no semver whose SHA is same with v1, pinact doesn't change the version.
And pinact doesn't change versions which aren't semver.
For instance, pinact doesn't change the version main.
uses: actions/checkout@main
We don't want to pin main to full commit length SHA like the following because we can't update this following semantic versioning.
uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 # main
Tools like Renovate can update the SHA, but it's not safe at all as main branch isn't stable.
And we don't want to change main to the latest semver like the following because SHA is changed and workflows may be broken.
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
We don't want to pin branches as SHA of branches is changed.
pinact doesn't check if a version is a tag or a branch because we would like to reduce the number of API calls as much as possible.
If a version isn't semver, pinact judges it may be a branch so pinact doesn't pin it.
Please see also #926.
GitHub Enterprise Server (GHES) Support
v3.6.0 #839 #1275
pinact also supports pinning versions of GitHub Actions hosted on GitHub Enterprise Server (GHES).
If the GHES support is enabled, pinact searches actions in GHES.
Fallback to github.com
The fallback to github.com is disabled by default.
All actions are searched on the GHES instance only.
If the fallback is enabled, repositories of actions are first searched on the GHES instance. If repositoires are not found (404), pinact falls back to github.com. This is suitable when GitHub Connect is enabled.
GitHub Access Token for GHES
Set a GitHub Access Token for GHES using one of the following environment variables (checked in order):
PINACT_GHES_TOKEN
GHES_TOKEN
GITHUB_TOKEN_ENTERPRISE
GITHUB_ENTERPRISE_TOKEN
export GHES_TOKEN=xxx
GITHUB_TOKEN is used for github.com.
Configuration File For GHES
GHES configuration is required via configuration file or environment variables.
The configuration file takes precedence over the environment variables.
ghes:
api_url: https://ghes.example.com
fallback: true # optional, default is false
api_url: API URL of the GHES instance. Can also be set via environment variables.
fallback: Whether to fallback to github.com when a repository is not found on GHES. Default is false.
Environment Variables For GHES
You can also configure GHES using environment variables instead of a configuration file.
GHES_API_URL
PINACT_GHES_FALLBACK
export GHES_API_URL=https://ghes.example.com
export PINACT_GHES_FALLBACK=true
If GHES_API_URL is not set, GITHUB_API_URL will be used instead.
This is convenient when running on GitHub Actions hosted on GHES.
Conditions for Enabling GHES
GHES mode is enabled when any of the following conditions are met:
ghes.api_url is configured in the configuration file
GHES_API_URL environment variable is set
GITHUB_API_URL environment variable is set and is not https://api.github.com
See also
LICENSE
MIT