Git, CI/CD & GitOpsLXVI · Third-Party Actions and PluginsDefence
Pinning to commit SHA — the only safe reference
What you'll learn
- Identify the three reference forms (tag, branch, SHA) and the trust property of each
- Write a pinned reference to a full 40-character SHA and verify its length
- Resolve a tag to its commit SHA and record the resolution as part of the pin
- Establish a re-pin workflow that ties action upgrades to deliberate pull-request review
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
Pinning to a full 40-character commit SHA turns the action reference from a pointer into a content address. The runner downloads exactly the bytes your team reviewed at pin time; the maintainer cannot move the SHA, cannot retroactively change the commit, and cannot force your workflow to execute a different version. This is the only safe form of action reference.
The three reference forms
GitHub Actions accepts three forms:
# Mutable reference - tag can move
- uses: actions/checkout@v4
# Mutable reference - branch tip moves
- uses: actions/checkout@main
# Immutable reference - SHA is content hash
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
A tag resolves at job start; the tag can move between runs. A branch tip moves on every commit. A commit SHA does not move.
flowchart LR
A["Tag v4"] --> B["Pointer"]
B --> C["Commit A1"]
B -. "force-push" .-> D["Commit A2"]
E["SHA b4ffde65..."] --> F["Bytes B1"]
F -. "immutable" .-> G["Same bytes forever"]
Why a full 40-character SHA
The runner accepts abbreviated SHAs, but the documentation recommends the full 40-character form. Two reasons: collision resistance (a 7-char SHA has ~268M values; a 40-char SHA has 2^160) and audit clarity (a 40-char SHA is unambiguous).
# Resolve a tag to a full SHA locally
git ls-remote https://github.com/actions/checkout.git refs/tags/v4
# -> ${ACTIONS_CHECKOUT_SHA} refs/tags/v4
# Confirm the SHA is exactly 40 hex characters
echo -n "${ACTIONS_CHECKOUT_SHA}" | wc -c
# -> 40
The pinning workflow
Pinning is a deliberate operation performed under review. Five steps:
- Identify the action. Confirm maintainer, repo, and intended version.
- Resolve the version. Resolve the tag or branch tip to its commit SHA.
- Review the action at the SHA. Read
action.yml,index.jsorDockerfile, andpackage.json. Confirm the action pins its own dependencies and the maintainer is responsive. - Pin the workflow. Update the workflow file with the SHA. The PR contains the pin, the resolved SHA, the review notes, and a link to the upstream release.
- Subscribe to advisories. Configure GitHub Watch on the action’s repository.
# Pinned reference - safe
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
# Pinned reference - safe, composite action
- uses: github/codeql-action/analyze@ff0a06e83cb2de871e5bfc9e9058eb2a83dff1c7
Re-pinning as a deliberate upgrade
A pin is a freeze. The freeze must end deliberately. Three signals trigger a re-pin: security advisory, upstream API change, or routine refresh. Dependabot can automate the resolution step — it opens a PR updating the pin — but the team still reviews the PR. The automatic part is the SHA resolution; the review part is not.
What pinning does not close
Pinning does not close:
- The abandoned-action risk. Pinned bytes still depend on the same vulnerable transitive packages (LXVI-04).
- The transitive dependency drift. The action’s own
node_modulesmay be unpinned. - The history-rewrite attack. Force-pushing changes the SHA a team is following.
Pinning is necessary. Pinning is not sufficient. The allowlist covered in the next lesson is the policy layer that closes the gaps pinning leaves open.
Production discipline
- Pin every third-party action to a full 40-character SHA. No exceptions. No tags. No branches.
- Verify the SHA length.
echo -n "$ACTIONS_CHECKOUT_SHA" | wc -cmust return40. - Resolve the SHA at pin time. Document the tag, the upstream release, and the resolved SHA in the PR.
- Review the action at the SHA before pinning.
- Re-pin deliberately. A new pin is a new pull request.
Cross-course references
- Git, CI/CD & GitOps — Part LXVI-02 (Tag-Swap Attack) establishes the mutable-reference risk pinning closes.
- Git, CI/CD & GitOps — Part LXV-05 (Artifact Trust) covers the content-addressing model pinning relies on.
- Git, CI/CD & GitOps — Part LXVI-06 (Allowlisting) covers the policy layer pinning leaves open.
Quiz
Knowledge check · 4 questions
Q1. What property of a commit SHA makes it a safe reference for a third-party action?
Q2. Pinning to a SHA fully protects a consumer workflow against every supply-chain risk introduced by third-party actions.
Q3. Name the five steps of the pinning workflow, in order.
Q4. Identify the gap in the team's pinning practice and the rule that closes it.
Team T maintains a Terraform monorepo with 60 workflows. 12 workflows reference third-party actions by tag (`@v2`, `@v3`). The policy 'pin every third-party action to a SHA' is documented in the runbook but not enforced by branch protection. A Dependabot PR was merged two months ago without review because the changes were 'just SHA bumps'. A new engineer submitted a PR that adds a third-party action pinned to a 7-character abbreviated SHA (`@abc1234`).
Passing score: 75%. Answers are checked in this browser.