Problem
All 8 GitHub Actions references in this repo ride mutable major tags (@v6/@v7). A tag is a movable pointer: whoever controls the action's repository can repoint it at new code, which then executes inside our CI with that workflow's token. sync-wiki.yml runs with contents: write and secrets.WIKI_TOKEN, so that is the blast radius of a compromised upstream tag.
zizmor is not run anywhere — not in CI, not in pre-commit — so nothing catches this today or prevents a regression tomorrow.
Evidence
zizmor .github/workflows/ on main (8aaee26) reports 17 findings: 8 x unpinned-uses (High), 4 x excessive-permissions (Medium), 5 x artipacked (Low).
Unpinned action references (High):
.github/workflows/lint.yml:16 — actions/checkout@v6
.github/workflows/lint.yml:19 — actions/setup-python@v6
.github/workflows/test.yml:16 — actions/checkout@v6
.github/workflows/test.yml:19 — actions/setup-python@v6
.github/workflows/test.yml:39 — actions/checkout@v6
.github/workflows/test.yml:42 — actions/setup-python@v6
.github/workflows/sync-wiki.yml:18 — actions/checkout@v6
.github/workflows/sync-wiki.yml:21 — actions/checkout@v6
Excessive permissions (Medium) — no permissions: block, so the job inherits the default token scope:
.github/workflows/lint.yml:12 (lint)
.github/workflows/test.yml:2 (workflow level)
.github/workflows/test.yml:12 (pytest)
.github/workflows/test.yml:29 (molecule)
Credential persistence (Low) — checkout leaves the token in .git/config because persist-credentials: false is not set:
.github/workflows/lint.yml:15
.github/workflows/test.yml:15
.github/workflows/test.yml:38
.github/workflows/sync-wiki.yml:17
.github/workflows/sync-wiki.yml:20
No zizmor anywhere: .github/workflows/ contains only lint.yml, test.yml, sync-wiki.yml; lint.yml:32-48 runs yamllint, ansible-lint, and a playbook syntax check. .pre-commit-config.yaml has no zizmor or actionlint hook.
Expected
Every action reference is pinned to a full commit SHA with a trailing version comment, and zizmor runs in CI failing the build on High-severity findings.
Proposed approach
-
Pin all 8 references to full SHAs with a version comment, e.g.
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
-
Add the helpers:pinGitHubActionDigests preset to extends in renovate.json5:3-5. Renovate then keeps the digests current, and the existing automerge rule already lists digest in matchUpdateTypes, so digest refreshes merge themselves without adding review burden.
-
Add a zizmor step to lint.yml.
-
Add least-privilege permissions: blocks to the three jobs that lack them, and persist-credentials: false to the checkouts that do not push.
Do not set persist-credentials: false on .github/workflows/sync-wiki.yml:20 — those persisted credentials are exactly what git push at .github/workflows/sync-wiki.yml:45 relies on. zizmor offers an auto-fix here that would break the wiki sync.
Notes
Extends the pinning precedent from #14 (container image versions) to the CI supply chain.
Filed from the review of #122 and #123, where these were recorded as pre-existing findings out of scope for a version bump. Both PRs touch every line listed above; merge them first or this work will conflict.
Problem
All 8 GitHub Actions references in this repo ride mutable major tags (
@v6/@v7). A tag is a movable pointer: whoever controls the action's repository can repoint it at new code, which then executes inside our CI with that workflow's token.sync-wiki.ymlruns withcontents: writeandsecrets.WIKI_TOKEN, so that is the blast radius of a compromised upstream tag.zizmoris not run anywhere — not in CI, not in pre-commit — so nothing catches this today or prevents a regression tomorrow.Evidence
zizmor .github/workflows/onmain(8aaee26) reports 17 findings: 8 xunpinned-uses(High), 4 xexcessive-permissions(Medium), 5 xartipacked(Low).Unpinned action references (High):
.github/workflows/lint.yml:16—actions/checkout@v6.github/workflows/lint.yml:19—actions/setup-python@v6.github/workflows/test.yml:16—actions/checkout@v6.github/workflows/test.yml:19—actions/setup-python@v6.github/workflows/test.yml:39—actions/checkout@v6.github/workflows/test.yml:42—actions/setup-python@v6.github/workflows/sync-wiki.yml:18—actions/checkout@v6.github/workflows/sync-wiki.yml:21—actions/checkout@v6Excessive permissions (Medium) — no
permissions:block, so the job inherits the default token scope:.github/workflows/lint.yml:12(lint).github/workflows/test.yml:2(workflow level).github/workflows/test.yml:12(pytest).github/workflows/test.yml:29(molecule)Credential persistence (Low) — checkout leaves the token in
.git/configbecausepersist-credentials: falseis not set:.github/workflows/lint.yml:15.github/workflows/test.yml:15.github/workflows/test.yml:38.github/workflows/sync-wiki.yml:17.github/workflows/sync-wiki.yml:20No zizmor anywhere:
.github/workflows/contains onlylint.yml,test.yml,sync-wiki.yml;lint.yml:32-48runs yamllint, ansible-lint, and a playbook syntax check..pre-commit-config.yamlhas no zizmor or actionlint hook.Expected
Every action reference is pinned to a full commit SHA with a trailing version comment, and
zizmorruns in CI failing the build on High-severity findings.Proposed approach
Pin all 8 references to full SHAs with a version comment, e.g.
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7Add the
helpers:pinGitHubActionDigestspreset toextendsinrenovate.json5:3-5. Renovate then keeps the digests current, and the existing automerge rule already listsdigestinmatchUpdateTypes, so digest refreshes merge themselves without adding review burden.Add a
zizmorstep tolint.yml.Add least-privilege
permissions:blocks to the three jobs that lack them, andpersist-credentials: falseto the checkouts that do not push.Do not set
persist-credentials: falseon.github/workflows/sync-wiki.yml:20— those persisted credentials are exactly whatgit pushat.github/workflows/sync-wiki.yml:45relies on. zizmor offers an auto-fix here that would break the wiki sync.Notes
Extends the pinning precedent from #14 (container image versions) to the CI supply chain.
Filed from the review of #122 and #123, where these were recorded as pre-existing findings out of scope for a version bump. Both PRs touch every line listed above; merge them first or this work will conflict.