Git, CI/CD & GitOpsLXVII · Dependency PinningCIPinning
Action and plugin pinning — GitHub Actions, GitLab CI, Jenkins plugins
What you'll learn
- Pin GitHub Actions, GitLab CI include templates, and Jenkins plugins to content addresses
- Resolve a tag or branch tip to a full SHA at pin time and verify the length
- Establish a re-pin workflow that treats Dependabot bumps as supply-chain changes
- Configure Dependabot for action updates and review the diff before merge
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
CI actions, GitLab include templates, and Jenkins plugins are all pulled from external registries at run time. A workflow that references a third-party action by tag is a workflow whose bytes are chosen by the action’s publisher at job-start. Pinning binds the runner to bytes the publisher cannot move. The syntax differs across systems; the discipline is the same: bind the reference to a content address, route the upgrade through review, and treat the re-pin as a supply-chain change.
GitHub Actions: pin to a full 40-character SHA
GitHub Actions accepts three reference forms: a tag, a branch, and a SHA. Only the SHA is a content address.
# Mutable - tag can move
- uses: actions/checkout@v4
# Mutable - branch tip moves
- uses: actions/checkout@main
# Immutable - SHA is a content hash
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
The runner accepts abbreviated SHAs, but the documentation recommends the full 40-character form. Two reasons: collision resistance (a 7-char SHA has roughly 268 million possible values; a 40-char SHA has 2^160) and audit clarity (a 40-char SHA is unambiguous in a code review and in a security incident).
# Resolve a tag to a full SHA at pin time
ACTIONS_CHECKOUT_SHA=$(git ls-remote https://github.com/actions/checkout.git refs/tags/v4 | awk '{print $1}')
echo -n "${ACTIONS_CHECKOUT_SHA}" | wc -c
# 40
The resolved SHA is what goes into the workflow file. The PR that introduces the pin contains the SHA, the upstream release notes, and the review notes; the SHA is the workflow’s promise to the runner to run exactly those bytes.
GitLab CI: SHA-pinned includes
GitLab CI’s include keyword pulls templates from remote
repositories. The reference form is the project path and a ref;
the ref can be a branch, a tag, or a SHA.
# Mutable - tag can move
include:
- project: 'my-group/ci-templates'
ref: v2.1
file: 'templates/deploy.yml'
# Immutable - SHA is a content hash
include:
- project: 'my-group/ci-templates'
ref: 8a3f9d2c1b4e7f6a5d8c9b0e3f4a7d8e9c0b1a2f
file: 'templates/deploy.yml'
The GitLab runner resolves the ref at pipeline start. A tag or
branch reference can resolve to different bytes between two
pipelines; a SHA reference cannot.
Jenkins: version-pinned plugins
Jenkins plugins are pulled from the Jenkins Update Center. The declarative-pipeline syntax accepts a version; the version is the closest equivalent to a SHA in Jenkins’s model.
// Pinned plugin version - safe
plugins {
id 'org.jenkins-ci.plugins.workflow' version '596.v8a_66e795d513'
id 'git' version '5.2.1'
}
The Jenkins plugin manifest is the lock file. The Update Center serves a versioned artifact; the version is the content address. A pipeline that does not pin the version is a pipeline whose plugin bytes are chosen by the Update Center at job-start.
The re-pin workflow
The re-pin workflow is the same five-step ceremony as the original pin, repeated every time the team upgrades an action, a template, or a plugin:
- Identify the upgrade. Read the upstream release notes, the upstream changelog, and the upstream security advisories.
- Resolve the new content address. Resolve the new tag or branch tip to a full 40-character SHA, or to the new plugin version.
- Review the diff between the old bytes and the new bytes. The review is the human link; Dependabot cannot perform it.
- Update the pin in a pull request. The PR contains the new SHA, the old SHA, the review notes, and a link to the upstream release.
- Subscribe to upstream advisories. Enable security alerts on the action’s repository, the template’s project, and the plugin’s tracker.
flowchart LR
A["Old SHA"] --> B["Diff"]
C["New SHA"] --> B
B --> D["Review notes"]
D --> E["PR merge"]
E --> F["Pinned workflow"]
Dependabot automates step 2; the team still performs steps 3 and 4. A Dependabot PR merged without review is the same supply-chain hole as a tag reference: the bytes changed under the team’s audit trail without a human link to the change.
Production discipline
- Pin every action to a full 40-character SHA. No tags. No branches. No abbreviated SHAs.
- Pin every GitLab include to a SHA. No branch refs. No tag refs.
- Pin every Jenkins plugin by version. The plugin manifest is the lock file.
- Treat Dependabot bumps as supply-chain changes. Review the diff between the old SHA and the new SHA before merge.
- Verify the SHA length at pin time.
echo -n "$ACTIONS_CHECKOUT_SHA" | wc -cmust return40.
Cross-course references
- Git, CI/CD & GitOps — Part LXVI-05 (Pinning to Commit SHA) establishes the action-pin discipline; this lesson applies it across CI systems.
- Git, CI/CD & GitOps — Part LXVII-01 (Pinning Discipline) defines the discipline that motivates every concrete pin.
- Git, CI/CD & GitOps — Part LXVI-06 (Allowlisting) covers the policy layer pinning leaves open.
Quiz
Knowledge check · 4 questions
Q1. Why does GitHub's documentation recommend pinning an action to a full 40-character SHA rather than an abbreviated 7-character SHA?
Q2. Merging a Dependabot SHA-bump PR without review is the same supply-chain hole as referencing an action by tag.
Q3. Name the five steps of the re-pin workflow, in order.
Q4. Identify the gap in the team's action-pinning practice and the rule that closes it.
Team T has 60 workflows. 12 workflows reference third-party actions by tag (`@v2`, `@v3`). The team's `dependabot.yml` configures Dependabot for actions but the Dependabot PRs are auto-merged by a bot because the team treats SHA bumps as routine. A new engineer submits a PR that pins `actions/checkout` to a 7-character abbreviated SHA (`@abc1234`). The team's CODEOWNERS file does not require review for `.github/workflows/`.
Passing score: 75%. Answers are checked in this browser.