# Third-party CI actions are pinned to full commit SHAs, not mutable tags

- **Pattern:** `ab-002630` (`soc2-readiness.boundary-and-supply-chain.third-party-actions-pinned`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-07-03
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002630
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002630)

## 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:

```yaml
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/*.yml` and `.github/workflows/*.yaml` (also composite actions under `.github/actions/**/action.yml` that themselves have `uses:` steps). Extract every `uses:` 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 is `actions/` or `github/` (GitHub-maintained, tag-protected, so major-version tags like `actions/checkout@v4` are 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.Z` comment is the standard convention and is fine). Ignore `uses:` values that start with `./` (local path, no remote trust) or `docker://` with a digest (`@sha256:...` counts as pinned; a bare image tag does not). Non-GitHub equivalents: GitLab `include: remote:` or `include: project:` refs in `.gitlab-ci.yml` should reference a specific commit SHA (`ref:` with a SHA, not a branch); CircleCI `orbs:` in `.circleci/config.yml` should 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 immutable `docker://...@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-party `actions/*` and `github/*` references may use tags. Do NOT pass on the presence of a `# vX.Y.Z` trailing 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-scaffolded `deploy.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.yml` all absent), OR workflows exist but reference no third-party actions (only `actions/*`/`github/*`, local `./` paths, or plain `run:` 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 via `git ls-remote https://github.com/<owner>/<repo> refs/tags/<tag>`). Add `package-ecosystem: github-actions` to `.github/dependabot.yml` so pins get bump PRs instead of drifting stale. Broader supply-chain coverage lives in the `dependency-supply-chain` audit.

## External references

- soc2 CC6.8
- iso-27001 A.5.21
- nist SR-3

Taxons: supply-chain, change-management

HTML version: https://auditbuffet.com/patterns/ab-002630
