|
| 1 | +# Action pinning — supply-chain protection |
| 2 | +# |
| 3 | +# External actions must be pinned to a full 40-character commit SHA. |
| 4 | +# Mutable tags like @v1 can be reassigned to point at malicious commits. |
| 5 | +# Local composite actions (./...) are exempt. |
| 6 | +# |
| 7 | +# Good: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 |
| 8 | +# Bad: actions/checkout@v4 |
| 9 | +# Bad: actions/checkout@main |
| 10 | +# |
| 11 | +# How to fix: |
| 12 | +# 1. Find the tag you want to pin (e.g. v4.3.1). |
| 13 | +# 2. Look up the commit SHA: |
| 14 | +# git ls-remote --tags https://github.com/<owner>/<action>.git '<tag>^{}' '<tag>' |
| 15 | +# 3. Replace the tag with the SHA and add a comment with the tag name: |
| 16 | +# uses: actions/checkout@<sha> # v4.3.1 |
| 17 | +# |
| 18 | +# Always include the "# <tag>" suffix comment so humans can tell which |
| 19 | +# version is pinned. This cannot be enforced by conftest (YAML strips |
| 20 | +# comments during parsing), so it is a convention to follow manually. |
| 21 | + |
| 22 | +package main |
| 23 | + |
| 24 | +import rego.v1 |
| 25 | + |
| 26 | +_is_pinned(ref) if { |
| 27 | + regex.match(`^[^@]+@[0-9a-f]{40}$`, ref) |
| 28 | +} |
| 29 | + |
| 30 | +_is_local(ref) if { |
| 31 | + startswith(ref, "./") |
| 32 | +} |
| 33 | + |
| 34 | +# Workflow files: jobs.<name>.steps[].uses |
| 35 | +deny contains msg if { |
| 36 | + some job_name, job in input.jobs |
| 37 | + some i, step in job.steps |
| 38 | + step.uses |
| 39 | + not _is_local(step.uses) |
| 40 | + not _is_pinned(step.uses) |
| 41 | + msg := sprintf("%s: step %d: action '%s' must be pinned to a full commit SHA", [job_name, i, step.uses]) |
| 42 | +} |
| 43 | + |
| 44 | +# Composite actions: runs.steps[].uses |
| 45 | +deny contains msg if { |
| 46 | + some i, step in input.runs.steps |
| 47 | + step.uses |
| 48 | + not _is_local(step.uses) |
| 49 | + not _is_pinned(step.uses) |
| 50 | + msg := sprintf("step %d: action '%s' must be pinned to a full commit SHA", [i, step.uses]) |
| 51 | +} |
0 commit comments