ActionPin is a publishable CLI for hardening GitHub Actions workflows after the Bitwarden CLI supply-chain scare on April 23, 2026. It looks for the workflow patterns that make CI compromise expensive: mutable third-party actions, broad token permissions, install scripts that run near secrets, and bot or agent triggers that can still reach production credentials.
unpinned-action: external actions not pinned to a full 40-character commit SHAmissing-permissions: workflows that rely on repository defaults instead of explicitpermissions:write-all-permissions:permissions: write-allprivileged-trigger: risky triggers such asworkflow_runorpull_request_targetpaired with write-capable token scopesprod-deploy-permissions: production-like environments plus deploy-capableGITHUB_TOKENor OIDC scopessecret-touching-install-script:npm install,pip install,curl | bash, and similar bootstraps running near secrets, cloud auth, or production environmentsagent-privileged-workflow: automation-friendly triggers that can still touch production credentialswrite-default-permissions: GitHub API enrichment found repository default workflow permissions set towriteunprotected-production-environment: GitHub API enrichment found a production-like environment with no reviewers, wait timer, or custom deployment protection
npm install
node ./src/cli.mjs --helpIf you publish it to npm later, the same CLI entrypoint works as:
npx actionpin .actionpin . --fail-on high
actionpin . --format json
actionpin . --format sarif --output actionpin.sarif
actionpin . --repo owner/repo --github-token "$GITHUB_TOKEN"0: no findings at or above the configured--fail-onthreshold1: findings met or exceeded the threshold, or CLI validation failed
Human-readable summary for local runs and CI logs.
Full structured report including summary counts, enrichment details, and finding metadata.
Compatible with GitHub code scanning. Use --format sarif --output actionpin.sarif and upload the file in CI.
An example workflow lives at examples/scan-workflows.yml.
ActionPin auto-discovers any of these files from the scan root:
actionpin.config.jsonactionpin.config.yamlactionpin.config.yml.actionpinrc.actionpinrc.json.actionpinrc.yaml.actionpinrc.yml
Or load one explicitly:
actionpin . --config ./examples/actionpin.config.ymlExample config: examples/actionpin.config.yml
allow:
actions:
- docker/login-action
rules:
missing-permissions: high
ignore:
- ruleId: unpinned-action
file: .github/workflows/legacy-release.yml
step: Publish containerPass --repo owner/repo and optionally --github-token to enrich the static scan with repository settings:
- repository default workflow permissions
- environment protection details for production-like environments
This is the important second phase because static YAML alone cannot tell you whether missing permissions: inherits a write default or whether a production environment has required reviewers.
The repository now includes:
- npm-ready
binentry in package.json filesallowlist for packaging- MIT LICENSE
- composite action.yml wrapper for GitHub Actions users
- stable text, JSON, and SARIF outputs
- config discovery and rule overrides
- local tests covering static rules, config handling, SARIF, and GitHub enrichment
npm test
node ./src/cli.mjs ./test/fixtures/risky
node ./src/cli.mjs ./test/fixtures/risky --format sarif --output actionpin.sarifThis problem is a strong fit for static analysis because the highest-signal evidence lives in workflow YAML:
uses:tells us whether action refs are mutablepermissions:tells us token blast radiuson:,if:, andenvironment:reveal privileged trigger pathsrun:andenv:expose setup commands that execute next to secrets or cloud auth
The GitHub API fills in the gaps that YAML alone cannot prove, especially repository defaults and environment protections.