fix(security): SHA-pin all third-party GitHub Actions in reusable workflows#27
Conversation
…kflows Addresses Medium F-001 from docs/security/audit-2026-05.md (PR #25). Reusable workflows in this repo are consumed by every muntianus/* repo via @main, so a tag-pinned third-party action upstream that goes hostile would execute in every consumer's CI with the secrets that consumer forwards (VPS_SSH_KEY, ANTHROPIC_API_KEY, GITHUB_TOKEN). Replacing every `uses: …@vN` with `uses: …@<40-char-sha> # vN` closes that vector. Actions pinned (SHA resolved from `git ls-remote https://github.com/<org>/<action> refs/tags/<tag>`): actions/checkout v4, v5 actions/setup-go v6 actions/setup-node v6 actions/setup-python v5 actions/upload-artifact v4 anthropics/claude-code-action v1 appleboy/scp-action v0.1.7 appleboy/ssh-action v1.2.0 golangci/golangci-lint-action v7 Also tighten the org rule in AGENTS.md so consumers know SHA- pinning is the standard and how to verify when bumping. `actionlint -no-color .github/workflows/*.yml` clean. Followup: cut a v1 release tag on this repo and migrate downstream consumers from `@main` references to `@v1` (F-002 in the same audit). https://claude.ai/code/session_01NXqUreamtsrtqYAVBURZY4
There was a problem hiding this comment.
Code Review
This pull request updates AGENTS.md to document a rule requiring SHA-pinning for third-party GitHub Actions. The review feedback correctly points out that using git ls-remote directly on refs/tags/<tag> can return the tag object's SHA instead of the dereferenced commit SHA for annotated tags, which would cause GitHub Actions to fail. It suggests updating the command to query refs/tags/<tag>\* to ensure the correct commit SHA is retrieved.
| reusables already follow this — keep it that way when bumping. Verify | ||
| the SHA with `git ls-remote https://github.com/<org>/<action> refs/tags/<tag>`. |
There was a problem hiding this comment.
If a tag is an annotated tag (which is common for release tags), querying refs/tags/<tag> directly with git ls-remote will only return the tag object's SHA, not the actual commit SHA. GitHub Actions require the actual commit SHA to run; using the tag object's SHA will cause the workflow to fail.
Using a wildcard \* (or querying refs/tags/<tag>^{}) ensures you can see and copy the dereferenced commit SHA (marked with ^{}).
| reusables already follow this — keep it that way when bumping. Verify | |
| the SHA with `git ls-remote https://github.com/<org>/<action> refs/tags/<tag>`. | |
| reusables already follow this — keep it that way when bumping. Verify | |
| the commit SHA with <code>git ls-remote https://github.com/<org>/<action> refs/tags/<tag>\*</code> (use the dereferenced ^{} SHA for annotated tags). |
…29) Architectural design for cutting v1.0.0 on this repo and migrating consumers from raw-SHA pinning (post #27) to the hybrid pattern @<sha> # v1.0.0. Hybrid is the only strategy that's positive across immutable-execution, readable-diff, Dependabot-friendly, and tag- rewrite-resistant — see comparison table. Key decisions: - Cut v1.0.0 + push floating v1 major; release-please with release-type: simple owns subsequent versioning from Conventional Commits. - Consumers pin @<sha> # v1.0.0; Dependabot rewrites BOTH lines on bump since dependabot-core PR #5951 (2024). - v* tags protected via rulesets (legacy tag-protection-rules deprecated): restrict creation/update/deletion, bypass = repo admins only. - Defines what counts as a "breaking change" for a reusable workflow (inputs/secrets/outputs/permissions/observable side effects). - Honest critique: hybrid wins for our 5-consumer scale; raw @v1 is unacceptable for workflows that handle secrets (tj-actions/changed- files March 2025 incident as reference). Includes: - 8-step one-shot rollout checklist (Conventional Commits adoption → release-please wiring → v1.0.0 cut → floating v1 → ruleset → 5 consumer PRs → Dependabot enablement) - release-please.yml workflow skeleton with floating-v1-tag move step - per-consumer dependabot.yml skeleton with grouped bumps under muntianus-workflows pattern - ongoing release process narrative No automation wired yet — that's the PR after this design lands. https://claude.ai/code/session_01NXqUreamtsrtqYAVBURZY4 Co-authored-by: claude <noreply@anthropic.com>
Summary
Addresses Medium F-001 from the audit landed in #25 — biggest leverage point in the second wave because reusable workflows here are consumed by every
muntianus/*repo via@main.A tag-pinned third-party action upstream that goes hostile would execute in every consumer's CI with whatever secrets the consumer forwards:
VPS_SSH_KEY,ANTHROPIC_API_KEY,GITHUB_TOKEN. Replacing everyuses: …@vNwithuses: …@<40-char-sha> # vNcloses that vector.Changes
All
uses:directives in.github/workflows/{claude,governance-ops,openapi-sync,reusable-ci-go,reusable-ci-node,reusable-deploy-vps,reusable-security-gate,update-profile-readme}.ymlnow reference a 40-char commit SHA, with the moving tag preserved in a trailing comment so bumps stay reviewable.AGENTS.mdhard-rule Org foundation pack: reusable workflows + agent guidance #3 tightened: SHA-pin third-party actions is now an org-wide expectation, with thegit ls-remoteverification command inline so future bumps don't drift.Actions pinned
actions/checkoutv434e114876b0b11c390a56381ad16ebd13914f8d5actions/checkoutv593cb6efe18208431cddfb8368fd83d5badbf9bfdactions/setup-gov64a3601121dd01d1626a1e23e37211e3254c1c06cactions/setup-nodev648b55a011bda9f5d6aeb4c2d9c7362e8dae4041eactions/setup-pythonv5a26af69be951a213d495a4c3e4e4022e16d87065actions/upload-artifactv4ea165f8d65b6e75b540449e92b4886f43607fa02anthropics/claude-code-actionv1537ffff2eff706bd7e3e1c3daf2d4b39067a9f85appleboy/scp-actionv0.1.7917f8b81dfc1ccd331fef9e2d61bdc6c8be94634appleboy/ssh-actionv1.2.07eaf76671a0d7eec5d98ee897acda4f968735a17golangci/golangci-lint-actionv79fae48acfc02a90574d7c304a1758ef9895495faTest plan
actionlint -no-color .github/workflows/*.ymlclean.rg "uses: [^./]+@v[0-9]" .github/workflows/returns empty — no tag-pinned external actions left.git ls-remote https://github.com/appleboy/ssh-action refs/tags/v1.2.0).Follow-up (separate PR)
F-002 from the same audit: this repo still has no release tag. Downstream consumers reference
@main, so a merge here ships immediately org-wide. After this lands, cutv1.0.0and migrate consumers to@v1.0.0(or its SHA).https://claude.ai/code/session_01NXqUreamtsrtqYAVBURZY4
Generated by Claude Code