lacs-project / lacs-project/sysknife
Four action pins name a version they are not, and nothing checks
- Dominant language
- Rust
- Stars
- 12
- Forks
- 19
- Avg merge
- 18h 57m
- Merged PRs (30d)
- 116
Description
Every `uses:` in `.github/workflows` is pinned to a SHA with a version comment
beside it, which is the right shape: a tag is mutable and a SHA is not. Nothing
checks that the comment is true, and four of them are not.
```
FAIL actions/deploy-pages pinned cd2ce8fc but v5 is 368f8252
FAIL taiki-e/install-action pinned 37f7c578 but v2 is 5bf6ce01
FAIL actions/dependency-review-action claims v4, which is not a tag in that repo
FAIL dtolnay/rust-toolchain claims stable, which is not a tag in that repo
```
That block is the run from 2026-09-04. Recounted at `d881d49` on 2026-09-06, the
four entries are the same four and one number has moved: `v2` in
`taiki-e/install-action` now resolves to `84f5ac31`, not `5bf6ce01`. `v2` is a
major alias the maintainers advance, so expect it to differ again by the time you
read this. The pinned SHA `37f7c578` has not moved, which is the point of pinning.
Nothing is broken by this. Every pinned SHA still resolves and every workflow
still runs what it ran yesterday, which is exactly what pinning is for. What is
wrong is the audit trail: the comment is the only thing telling a reader which
version a forty-character hex string is, and four of them name a version that
SHA is not on. The last two name a moving reference rather than a tag at all,
so there is nothing for a reader to check against.
## The check
`lacs-project/sysknife` has no pin verifier. `vladimirrott/maintainer-agent` has
`scripts/verify-action-pins.sh`, which now takes a repository root, so this
output came from pointing it at this checkout:
```sh
git clone https://github.com/vladimirrott/maintainer-agent /tmp/maintainer-agent
bash /tmp/maintainer-agent/scripts/verify-action-pins.sh .
```
Run it from the root of your `sysknife` clone. It takes a repository root as its
one argument and needs `gh` authenticated, because it resolves each tag through
the GitHub API.
Copying that script in and adding it to `docs-and-hygiene` is the whole fix. One
detail matters and is the reason not to write a fresh one: **annotated tags point
at a tag object, not a commit.** A naive comparison of `refs/tags/X.object.sha`
against the pin fails for every repository that signs its tags. I wrote that
naive version by hand first and it reported eleven failures instead of four,
including `github/codeql-action`, which is correctly pinned. A checker with that
flag rate teaches people to ignore it.
## Two decisions in it
**The two "not a tag" entries need a policy, not a bump.** `dtolnay/rust-toolchain@stable`
and `actions/dependency-review-action@v4` are branch or major-alias references.
Either the comment should say so, or they should be pinned to a real tag. The
`trufflesecurity/trufflehog` pin is the same shape and carries the same bare
comment, `# main`, so it needs whatever convention this issue settles on.
**Bumping a pin is a supply-chain decision.** `deploy-pages` and
`install-action` have moved by however many commits; updating the pin adopts all
of them. Worth reading what changed rather than taking the new SHA because a
checker asked.
## Getting started
Everything here is a workflow file and one shell script. No VM, no daemon, no LLM
provider and no credentials beyond an authenticated `gh`.
The seventeen pins live in `.github/workflows/`:
```sh
grep -rhoE 'uses:\s*[A-Za-z0-9_.-]+/[A-Za-z0-9_./-]+@[0-9a-f]{40}.*' .github/workflows/ \
| sed 's/^uses:\s*//' | sort -u
```
Resolve one comment by hand before you write any code, and use the annotated case,
because it is the one that will bite you:
```
$ gh api repos/github/codeql-action/git/ref/tags/v4 --jq '.object.type, .object.sha'
tag
fddeee1a7ece751b577e409a89057319e3172939
$ gh api repos/github/codeql-action/git/tags/fddeee1a7ece751b577e409a89057319e3172939 --jq .object.sha
cdf488f595d80d6e07e03d4674febd5ab45fa938
```
`fddeee1a` is the tag object. `cdf488f5` is the commit, and it is what the
workflow pins, so this action is correct. Compare against `fddeee1a` and you
report a failure on a pin that is right. Four of the pinned actions carry annotated tags:
`github/codeql-action`, `ossf/scorecard-action`, `Swatinem/rust-cache` and
`anchore/sbom-action`. That is five of the seventeen `uses:` lines, because
`github/codeql-action` is pinned twice.
Lightweight tags skip the second call. `gh api repos/actions/checkout/git/ref/tags/v7`
returns `commit` and the SHA is already the commit, so branch on `.object.type`.
The new script belongs in `scripts/`, wired into the `docs-and-hygiene` job in
`.github/workflows/ci.yml`, and into `scripts/ci-local.sh` so a contributor gets
the same answer locally. #346 is the issue about those two lists drifting apart,
so add it to both.
## Tests first
Two directions, and the second is the one that matters here.
**Break what the guard protects.** Add a fixture workflow whose `uses:` line pins
a real SHA under a version comment that names a different tag, point the checker
at it, and assert it exits non-zero and names that action. Then correct the
comment in the fixture and assert it exits zero.
**Break the guard's own input.** This checker has a specific way of being useless:
it can pass because it found nothing to check. Assert on the count, not just the
exit code. A fixture directory with three pinned actions must report three
verdicts, so that a regex which stops matching (a `uses:` written with different
spacing, a pin on its own continuation line) turns into a red test rather than a
silent green run over zero pins.
The annotated-tag case deserves its own test: pin `github/codeql-action` correctly
in a fixture and assert the checker calls it `OK`. That is the exact case a naive
implementation gets wrong, and it produced eleven failures instead of four when I
wrote the naive version by hand.
## Difficulty
`medium`. The shell is short. Getting the tag peeling and the "not a tag" case
right is the work, and the fixture design above is most of the thinking.
Contributor guide
Research direction
Start by reading the existing pin layout in .github/workflows and running the referenced verify-action-pins.sh against the checkout. Add the verifier under scripts/, wire it into .github/workflows/ci.yml and scripts/ci-local.sh, and create fixtures covering mismatched comments, annotated tags, and pin-count checking. Done means the checks detect incorrect pins without false positives and report all expected entries.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github-actions, shell
- Domain
- ci-cd, devops, security
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100