Summary
Every third-party action in the workflows is referenced by a mutable tag. A tag is a pointer the upstream owner can move at any time, so the code CI executes on the next run is not necessarily the code that was reviewed when the tag was added.
.github/workflows/go.yml:128 nick-fields/retry@v4
.github/workflows/go.yml:203 nick-fields/retry@v4
.github/workflows/go.yml:332 nick-fields/retry@v4
.github/workflows/go.yml:461 golangci/golangci-lint-action@v9
.github/workflows/go.yml:471 tj-actions/changed-files@v47
.github/workflows/go.yml:557 golangci/golangci-lint-action@v9
.github/workflows/typos.yml:14 crate-ci/typos@v1.48.0
website-prod.yml:71,75 docker/login-action@v4
website-prod.yml:136 google-github-actions/auth@v3
website-stage.yml:145 google-github-actions/auth@v3
tj-actions/changed-files is the one worth acting on first: in March 2025 that action's tags were retargeted to a malicious commit that dumped runner memory — including secrets — into build logs, across every repo using it. The mechanism was precisely tag mutability; SHA-pinned consumers were unaffected. GitHub's own hardening guidance for third-party actions is to pin by full commit SHA for this reason.
Blast radius here, honestly stated
Limited but not nil. go.yml sets permissions: contents: read at the top and the code_quality job doesn't raise it, so a compromised changed-files gets a read-only GITHUB_TOKEN and no repository secrets — QLTY_TOKEN lives in upload_coverage, a different job. The realistic damage is log exfiltration of whatever is in that runner's memory and tampering with the lint result, not a repo write.
The deploy workflows are the higher-value target: website-prod.yml / website-stage.yml use docker/login-action and google-github-actions/auth in jobs that hold GOFR_WEBSITE_GOFR_DEV_DEPLOYMENT_KEY and packages: write. Those are first-party-ish (Docker, Google) and so lower-risk in practice, but they are the ones where a moved tag would actually cost something.
Suggested fix
Pin by SHA with the human-readable version in a trailing comment, which is also what #3864 just did for ls-lint/action:
uses: tj-actions/changed-files@<sha> # v47
Then let Dependabot maintain them — .github/dependabot.yml already exists in this repo, and the github-actions ecosystem updates SHA pins and rewrites the version comment automatically, so pinning does not mean going stale. Worth confirming the github-actions ecosystem is actually enabled there as part of this.
Priority
Not urgent — nothing is known to be wrong today. Filing it because tj-actions specifically has already been the vector once, and the fix is mechanical.
Context
Found while reviewing run 31266842627.
Summary
Every third-party action in the workflows is referenced by a mutable tag. A tag is a pointer the upstream owner can move at any time, so the code CI executes on the next run is not necessarily the code that was reviewed when the tag was added.
tj-actions/changed-filesis the one worth acting on first: in March 2025 that action's tags were retargeted to a malicious commit that dumped runner memory — including secrets — into build logs, across every repo using it. The mechanism was precisely tag mutability; SHA-pinned consumers were unaffected. GitHub's own hardening guidance for third-party actions is to pin by full commit SHA for this reason.Blast radius here, honestly stated
Limited but not nil.
go.ymlsetspermissions: contents: readat the top and thecode_qualityjob doesn't raise it, so a compromisedchanged-filesgets a read-onlyGITHUB_TOKENand no repository secrets —QLTY_TOKENlives inupload_coverage, a different job. The realistic damage is log exfiltration of whatever is in that runner's memory and tampering with the lint result, not a repo write.The deploy workflows are the higher-value target:
website-prod.yml/website-stage.ymlusedocker/login-actionandgoogle-github-actions/authin jobs that holdGOFR_WEBSITE_GOFR_DEV_DEPLOYMENT_KEYandpackages: write. Those are first-party-ish (Docker, Google) and so lower-risk in practice, but they are the ones where a moved tag would actually cost something.Suggested fix
Pin by SHA with the human-readable version in a trailing comment, which is also what #3864 just did for
ls-lint/action:Then let Dependabot maintain them —
.github/dependabot.ymlalready exists in this repo, and thegithub-actionsecosystem updates SHA pins and rewrites the version comment automatically, so pinning does not mean going stale. Worth confirming thegithub-actionsecosystem is actually enabled there as part of this.Priority
Not urgent — nothing is known to be wrong today. Filing it because
tj-actionsspecifically has already been the vector once, and the fix is mechanical.Context
Found while reviewing run 31266842627.