ci: SHA-pin gate for GitHub Actions uses: refs (mache-b8900d)#470
Conversation
Motivation: dependabot bumps action refs but occasionally leaves a bare tag behind (it did on lfs-guard.yml in #457, caught only by hand). A bare tag is a mutable, remotely-controlled pointer — a supply-chain foothold. This makes the invariant executable so the next one can't merge unnoticed. Reframing (vs the bead's original "find_smells rule" idea): find_smells is SQL-only over the parsed code .db, and workflow YAML isn't in that DB — a text rule would need a new engine rule-type (the larger mache-445887 work). So this ships the value the way mache already does workflow/doc hygiene: a script + Taskfile target + CI wiring, mirroring docs-lint.sh. - scripts/actions-pin-lint.sh: flags any .github/workflows `uses:` ref whose @-suffix isn't a 40-hex SHA; exempts local (./) and docker:// refs. - task actions:lint; wired into `check` + `ci` aggregates and the CI lint job (taskfile-ci-parity: CI invokes the task target). - Go tests: flags unpinned, passes when pinned, and a live guard that the repo itself stays clean (fails `go test ./cmd/` on regression). Pairs with actionlint (workflow syntax, mache-f60571); this is pinning.
find_smells (advisory)Scoped to files changed in this PR. Rules below run on the standalone (no-LLO) cross-ref tables; No structural smells in changed files. ✓ Rules: the registry is |
There was a problem hiding this comment.
Pull request overview
This PR introduces a repo-enforced gate to ensure all GitHub Actions uses: references in .github/workflows/ are pinned to immutable identifiers, reducing supply-chain risk from mutable tags.
Changes:
- Add
scripts/actions-pin-lint.shto detect non–SHA-pinneduses:refs under.github/workflows/. - Wire the lint into Taskfile (
actions:lint) and aggregate gates (checkandci), and add it to the GitHub Actions CIlintjob. - Add Go tests to validate the lint behavior and add a “repo stays clean” guard test.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| Taskfile.yml | Adds actions:lint task and runs it as part of check and ci aggregates. |
| scripts/actions-pin-lint.sh | Implements the workflow uses: pinning lint logic. |
| cmd/actions_pin_lint_test.go | Adds unit tests and a repo-clean regression guard for the lint script. |
| .github/workflows/ci.yml | Runs task actions:lint in the lint job to gate PRs in CI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…, portable grep, case-insensitive SHA - Docker refs are no longer blanket-exempt: require docker://img@sha256:<64-hex>; a mutable docker tag is now flagged (was a supply-chain hole the comment claimed was covered). - Replace GNU-only `grep -r --include` with portable `find … -exec grep -H` (BSD/macOS-safe, since this runs locally via task check/ci). - Accept uppercase SHAs ([0-9a-fA-F]) for both 40-hex commit and 64-hex digest. - Tests updated: mutable docker tag flagged; uppercase-SHA + docker-digest pass.
Motivation
Dependabot bumps action refs but occasionally leaves a bare tag behind — it did on
lfs-guard.ymlin #457, caught only by hand. A bare tag (actions/checkout@v4) is a mutable, remotely-controlled pointer — a supply-chain foothold. This makes "all actions must be SHA-pinned" executable so the next one can't merge unnoticed.Reframing vs the bead
mache-b8900dwas filed as afind_smellsrule, but find_smells is SQL-only over the parsed code.db, and workflow YAML isn't in that DB. A text rule would need a new engine rule-type — that's the largermache-445887("find_smells as a standalone lint") work, out of scope here. So this delivers the value the way mache already does workflow/doc hygiene: a script + Taskfile target + CI wiring, mirroringscripts/docs-lint.sh.Changes
scripts/actions-pin-lint.sh— flags any.github/workflowsuses:ref whose@-suffix isn't a 40-hex SHA. Exempts local./composite refs anddocker://refs (different pinning format).task actions:lint— wired into thecheck+ciaggregates and the GitHub CIlintjob, so it gates PRs (taskfile-ci-parity: CI invokes the task target, not a re-implemented command).cmd/actions_pin_lint_test.go) — flags an unpinned ref, passes when pinned, exempts local/docker, and a live guard that the repo itself stays clean (a regression failsgo test ./cmd/too).Pairs with actionlint (workflow syntax,
mache-f60571); this is pinning.Verification
task actions:lint→ all refs SHA-pinned ✓ (incl. this PR's ci.yml edit)go test ./cmd/ -run TestActionsPinLint→ 3/3 passgo vet+golangci-lint(pre-commit) → pass