Repository navigation
Workflow actions pinned to mutable major tags, including download-artifact@v5 inside the id-token: write publish job (CR-A16-052) #465
Description
Wave 2 · theme D2-1: Security and integrity: medium/large effort · highest severity Low · 2 finding(s) · effort small
Summary
A moved or compromised actions/download-artifact v5 tag runs inside the OIDC job. It can swap dist/ before the SHA-pinned publish step uploads to PyPI, or mint the PyPI OIDC token itself.
Primary finding
CR-A16-052 — Workflow actions pinned to mutable major tags, including download-artifact@v5 inside the id-token: write publish job
severity Low · reachability default · intent intended-but-harmful · confidence confirmed · single reviewer · not reproduced (code trace) · already tracked: KNOWN-OPEN OPS-02..19 (S12) — docs/roadmap/phase-16-trust-surface-hardening.md:669-670
What happens. All first-party actions (checkout@v5, setup-python@v6, upload/download-artifact@v5, github-script@v8, pages actions) use movable tags; only pypa/gh-action-pypi-publish is SHA-pinned (publish.yml:136). publish.yml:123 runs tag-pinned download-artifact@v5 in the OIDC job before the pinned publish step, so a moved tag could swap dist/ or mint the token.
Where.
.github/workflows/ci.yml:28.github/workflows/publish.yml:21-23.github/workflows/publish.yml:116-136.github/workflows/site-deploy.yml:72-90.github/workflows/nightly.yml:345-356.github/workflows/publish.yml:121.github/workflows/publish.yml:123.github/workflows/publish.yml:129publish.yml:123(the only unpinned action in the id-token job)
Evidence.
rg -n 'uses:' .github/workflowscounts: checkout@v5 x11, setup-python@v6 x11, upload-artifact@v5 x4, download-artifact@v5 x2, github-script@v8, configure-pages@v5, upload-pages-artifact@v3, deploy-pages@v4, and pypa/gh-action-pypi-publish@cef2210... x1.- publish.yml:113-136. Verified that v1.14.0 dereferences to cef221092ed1bacb1cc03d23a2d87d1d172e277b, so the existing SHA pin is correct.
Recommended fix. SHA-pin every action, at least in the publish and deploy jobs, and let Dependabot/Renovate bump the SHAs. Verify dist hashes between build and publish (see the release-pipeline finding).
Other findings with the same root cause
These close with the same fix; update them in the same pull request.
TECH-T12-004 — Supply-chain tooling, network dependencies and vulnerability scanning: SHA-pinning of all workflow actions (only pypa/gh-action-pypi-publish is pinned) (aging)
severity Next release · already tracked: KNOWN-OPEN OPS-02|OPS-03|OPS-04|OPS-05|OPS-06|OPS-15|OPS-16|OPS-19 docs/roadmap/phase-16-trust-surface-hardening.md:669
Item. SHA-pinning of all workflow actions (only pypa/gh-action-pypi-publish is pinned) · verdict: aging · urgency: Next release
State in ForgeLM. Only publish.yml:136 is SHA-pinned; every actions/* use is a mutable major tag; workflow pip installs are unpinned (ci.yml:41, publish.yml:28,35); .github/ has no dependabot.yml; publish.yml:129-131 cites Scorecard 'pinned-dependencies' as the reason for pinning the publish action.
State of the ecosystem on 2026-10-01. GitHub's secure-use reference: 'Pinning an action to a full-length commit SHA is currently the only way to use an action as an immutable release', framed as mitigation for third-party action compromise; repository or organisation policy can require SHA pinning (changelog 2025-08-15). OpenSSF Scorecard Pinned-Dependencies (Risk: Medium) looks for unpinned dependencies in workflows, Dockerfiles and scripts and recommends pinning by hash. SLSA v1.2 does not require it; pinned dependencies appear only as a possible future Build L4 item.
Impact. All unpinned uses are GitHub first-party actions; the only third-party action is pinned. Residual risk: a retagged or compromised actions/* tag in build/cross-os-tests shaping dist/ before the pinned publish step uploads and attests it, and a partial Scorecard score that the project's own comment invokes.
Recommendation. Pin every action by full SHA with a version comment (publish.yml first), enable the repository SHA-pinning policy, add Dependabot for github-actions, and constrain release-job tooling (build, twine).
Sources.
- GitHub Docs: Secure use reference (pin actions to a full-length commit SHA) (undated (retrieved 2026-10-01))
- GitHub Changelog: Actions policy supports blocking and SHA pinning (2025-08-15)
- OpenSSF Scorecard checks.md (Pinned-Dependencies, Risk Medium; lock files for applications) (2026-04-23)
- SLSA v1.2 Future directions (pinned dependencies only a possible future Build L4 item) (undated (retrieved 2026-10-01))
Acceptance criteria
- A regression test reproduces the failure scenario and asserts the corrected behaviour.
- Documentation listed above is corrected in English and in its Turkish mirror in the same PR.
- Every finding in this issue is resolved or explicitly closed with a reason.
Found by the October 2026 ForgeLM code and documentation review against commit cab2947. Code links point at that commit; line numbers may have moved since. Finding IDs (CR-…, DOC-…, TECH-…) are stable references for this backlog.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity
chore: add community-health files for contributor intake (#964)
cemililik commentedon Oct 17, 2026
Progress on
development— verified 2026-10-18 at6c55900.Fixed:
github-actionsecosystem weekly (.github/dependabot.yml,9e6a8b2)Still open: every
uses:is a mutable tag exceptpypa/gh-action-pypi-publish; this includesactions/download-artifact@v5in theid-token: writejob ofpublish.yml.publish.ymlstill runspip install --upgrade pip buildandpip install twineunpinned, and no guard checks pin style. Whether the repository's "require full-length SHA" Actions policy is on cannot be seen from the repository.Everything else in this issue is unchanged.
feat(ci): let a contributor claim a good first issue with /assign (#978)