Skip to main content
RunBook Academy

Git, CI/CD & GitOpsLXVII · Dependency PinningCIPinning

Action and plugin pinning — GitHub Actions, GitLab CI, Jenkins plugins

Advanced⏱ ~26 mingit

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

Not yet marked complete on this device.

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:

  1. Identify the upgrade. Read the upstream release notes, the upstream changelog, and the upstream security advisories.
  2. Resolve the new content address. Resolve the new tag or branch tip to a full 40-character SHA, or to the new plugin version.
  3. Review the diff between the old bytes and the new bytes. The review is the human link; Dependabot cannot perform it.
  4. 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.
  5. 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

  1. Pin every action to a full 40-character SHA. No tags. No branches. No abbreviated SHAs.
  2. Pin every GitLab include to a SHA. No branch refs. No tag refs.
  3. Pin every Jenkins plugin by version. The plugin manifest is the lock file.
  4. Treat Dependabot bumps as supply-chain changes. Review the diff between the old SHA and the new SHA before merge.
  5. Verify the SHA length at pin time. echo -n "$ACTIONS_CHECKOUT_SHA" | wc -c must return 40.

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

  1. Q1. Why does GitHub's documentation recommend pinning an action to a full 40-character SHA rather than an abbreviated 7-character SHA?

  2. Q2. Merging a Dependabot SHA-bump PR without review is the same supply-chain hole as referencing an action by tag.

  3. Q3. Name the five steps of the re-pin workflow, in order.

  4. 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.