Third-party CI actions are pinned to full commit SHAs, not mutable tags
Why it matters
A uses: someorg/some-action@v3 line in a GitHub Actions workflow trusts whoever controls that tag, forever: tags are mutable, so a compromised or malicious maintainer can retag v3 to point at new code and your CI runs it on the next push, with full access to your repository checkout and every secret exposed to the job. That is exactly what happened in the tj-actions/changed-files compromise (March 2025, CVE-2025-30066): attackers retagged the action and exfiltrated CI secrets from thousands of repositories that referenced it by tag. Pinning third-party actions to a full 40-character commit SHA makes the reference immutable, so upstream retagging cannot change what your pipeline executes. This evidences SOC 2 CC6.8 (prevention and detection of unauthorized or malicious software) and supports CC8.1 (change management: only authorized, reviewed changes enter the build pipeline). AI-generated and vibe-coded apps fail this almost universally, because every tutorial, template, and AI-scaffolded workflow writes @v4 style tags: the SHA-pinning convention only shows up when someone has been burned by supply chain attacks, and coding assistants reproduce the tutorial form.
Severity rationale
High because a mutable tag reference lets an upstream retag execute arbitrary code inside CI with access to checkout contents and all job-scoped secrets, the exact mechanism of CVE-2025-30066.
Remediation
For every third-party action in .github/workflows/*.yml, replace the mutable tag with the full 40-character commit SHA of the release you intend, keeping the version as a trailing comment so humans can still read it:
uses: tj-actions/changed-files@2f7c5bfce28377bc069a65ba478de0a74aa0ca32 # v46.0.1
Resolve the SHA with git ls-remote https://github.com/<owner>/<repo> refs/tags/<tag> or from the action's Releases page. Then add a .github/dependabot.yml entry with package-ecosystem: github-actions so Dependabot proposes SHA bumps when new releases ship, keeping pins current without reintroducing mutable refs.
Detection
- ID:
third-party-actions-pinned - Severity:
high - What to look for: Enumerate every CI workflow file:
.github/workflows/*.ymland.github/workflows/*.yaml(also composite actions under.github/actions/**/action.ymlthat themselves haveuses:steps). Extract everyuses:line and quote it verbatim as you classify it — the classification below is only valid against quoted lines, not remembered ones. Classify each referenced action: first-party means the owner segment isactions/orgithub/(GitHub-maintained, tag-protected, so major-version tags likeactions/checkout@v4are accepted by convention); everything else is third-party. For third-party references, the ref after@must be a full 40-character hex commit SHA (the trailing# vX.Y.Zcomment is the standard convention and is fine). Ignoreuses:values that start with./(local path, no remote trust) ordocker://with a digest (@sha256:...counts as pinned; a bare image tag does not). Non-GitHub equivalents: GitLabinclude: remote:orinclude: project:refs in.gitlab-ci.ymlshould reference a specific commit SHA (ref:with a SHA, not a branch); CircleCIorbs:in.circleci/config.ymlshould use exact versions (orb@1.2.3), never@volatile. Report those the same way. - Pass criteria: At least one workflow file exists, at least one third-party action is referenced, and every third-party
uses:reference is pinned to a full 40-character commit SHA (or an immutabledocker://...@sha256:digest). Count the third-party refs and the SHA-pinned refs separately and report N of M — pass only when N equals M. First-partyactions/*andgithub/*references may use tags. Do NOT pass on the presence of a# vX.Y.Ztrailing comment alone: verify the ref between@and the comment is itself 40 hex characters. - Fail criteria: Any third-party action referenced by a mutable ref:
@v3,@v4.2.1,@main,@master,@latest, any branch name, or a truncated/short SHA (fewer than 40 hex chars, still ambiguous and re-mineable). Sneaky variants that still fail: a repo where most actions are SHA-pinned but one workflow (often a stale AI-scaffoldeddeploy.yml) uses tags; a SHA pin whose trailing comment exists but the ref itself is a tag (@v3 # pinned); a commented-out SHA-pinned line next to a live tag reference; pins only in.github/actions/composites while top-level workflows use tags. One mutable third-party ref anywhere fails the check. - Skip (N/A) when: No CI workflow files exist (
.github/workflows/,.gitlab-ci.yml,.circleci/config.ymlall absent), OR workflows exist but reference no third-party actions (onlyactions/*/github/*, local./paths, or plainrun:steps). Quote which:"No CI workflow files found (.github/workflows/, .gitlab-ci.yml, .circleci/config.yml all absent)"or"Workflows at <paths> use only first-party actions/* and run: steps — no third-party actions referenced". - Before evaluating, quote: Every third-party
uses:line verbatim with its file path (workflow file + action ref). If passing, these quoted lines are the evidence that each ref is a 40-char SHA; if failing, quote at least the offending lines. Cap at 15 lines and state the total count if more. - Report even on pass:
"<N> third-party action refs across <M> workflow files, all pinned to full commit SHAs; first-party actions/* on tags (accepted)". - Detail on fail:
"3 of 7 third-party actions use mutable tags: tj-actions/changed-files@v44 (.github/workflows/ci.yml), pnpm/action-setup@v2 (.github/workflows/ci.yml), amondnet/vercel-action@master (.github/workflows/deploy.yml) — retagging any of these upstream executes attacker code with this repo's CI secrets"or"docker://node:20 in .github/workflows/test.yml is a mutable image tag, not a sha256 digest; all other third-party actions SHA-pinned". - Remediation: In each
.github/workflows/*.yml, replace every third-party tag ref with the release's full commit SHA plus a version comment, e.g.uses: tj-actions/changed-files@2f7c5bfce28377bc069a65ba478de0a74aa0ca32 # v46.0.1(resolve viagit ls-remote https://github.com/<owner>/<repo> refs/tags/<tag>). Addpackage-ecosystem: github-actionsto.github/dependabot.ymlso pins get bump PRs instead of drifting stale. Broader supply-chain coverage lives in thedependency-supply-chainaudit.
External references
- soc2:2017 · CC6.8 — Prevention and detection of unauthorized or malicious software
- iso-27001:2022 · A.5.21 — Managing information security in the ICT supply chain
- nist:rev5 · SR-3 — Supply Chain Controls and Processes
Taxons
History
- 2026-07-03·v1.0.0·Initial soc2-readiness v1 authoring — third-party CI actions pinned to full commit SHAs evidences SOC 2 CC6.8 malicious-software prevention, motivated by the March 2025 tj-actions/changed-files retag compromise (CVE-2025-30066).·by soc2-readiness-v1-build