Raised while reviewing the Dependabot action bumps.
.github/workflows/coverage-badge.yml runs with permissions: contents: write and ends in git push --force origin badges, while referencing mutable major tags:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
A major tag is a moving pointer. Whoever controls it controls a job that already holds a write token for this repository. The blast radius is a force-push to the orphan badges branch — main is protected — but the token itself is the real asset, not the branch.
ci.yml is permissions: contents: read, so the same argument is much weaker there.
Proposed
Pin the two actions in coverage-badge.yml to full commit SHAs with the version in a trailing comment:
- uses: actions/checkout@<sha> # v7.0.0
Dependabot understands SHA pins and will keep bumping them, so this costs nothing ongoing.
Worth considering at the same time: whether the badge job needs contents: write at all. An alternative is publishing the endpoint JSON to a gist or as a job artifact, at the cost of an external dependency the current design deliberately avoids.
Acceptance
- Both actions in
coverage-badge.yml pinned by SHA with a version comment
- A note in the workflow explaining why this file is pinned when
ci.yml is not
Raised while reviewing the Dependabot action bumps.
.github/workflows/coverage-badge.ymlruns withpermissions: contents: writeand ends ingit push --force origin badges, while referencing mutable major tags:A major tag is a moving pointer. Whoever controls it controls a job that already holds a write token for this repository. The blast radius is a force-push to the orphan
badgesbranch —mainis protected — but the token itself is the real asset, not the branch.ci.ymlispermissions: contents: read, so the same argument is much weaker there.Proposed
Pin the two actions in
coverage-badge.ymlto full commit SHAs with the version in a trailing comment:Dependabot understands SHA pins and will keep bumping them, so this costs nothing ongoing.
Worth considering at the same time: whether the badge job needs
contents: writeat all. An alternative is publishing the endpoint JSON to a gist or as a job artifact, at the cost of an external dependency the current design deliberately avoids.Acceptance
coverage-badge.ymlpinned by SHA with a version commentci.ymlis not