Safeguard
Software Supply Chain•

How do you pin includes, components and images in GitLab CI?

Pin GitLab CI includes and components to a commit SHA or exact release, pin images by digest, and treat moving references like latest as unreviewed code.

Safeguard Security Team
4 min read

To pin third-party code in GitLab CI, reference every include, CI/CD component and container image by an immutable identifier: a full commit SHA or exact release tag for includes and components, and a digest for images. A branch name, ~latest or a mutable tag means someone else can change your pipeline without a pull request.

What counts as third-party code in a GitLab pipeline?

More than people expect. A .gitlab-ci.yml can pull in:

  • include: entries from other projects, remote URLs and templates.
  • CI/CD components, referenced as gitlab.com/namespace/project/component@version.
  • Container images named in image: and services:.
  • Scripts downloaded by curl inside script: blocks.

Each runs with the job's variables and tokens. Whatever the included YAML says to do, your runner does, with your secrets in reach.

How do versions work for components?

GitLab's component documentation lists the version forms in order of priority: a commit SHA, a tag such as 1.0.0, a branch name, then the catalog-only forms ~latest and partial semantic versions such as 1 or 1.2. The docs state that ~latest always gets the newest release and may include breaking changes, and they recommend a release or Git SHA when you need to fetch the same revision every time.

So the ranking for safety is clear. A full SHA is immutable. An exact tag is stable unless the owner moves it, so for components outside your control a SHA is stronger. Partial versions like 1.2 trust the maintainer to publish only safe patch releases. ~latest and branch names trust them with every future commit.

What does an unpinned include cost?

A constructed example. A team includes a shared lint template from another project with ref: main. The template's owner merges a change that adds a step uploading $CI_JOB_TOKEN and protected variables to a debugging endpoint. Every pipeline in every consuming project now leaks, within minutes, without any of them changing a line.

The owner might be malicious, compromised or simply careless. The outcome is identical.

How do you pin images?

Tags such as node:20 move. A digest, written as image: name@sha256:..., does not. Resolve the digest once, record it, and let an automated update tool open a merge request when a new base image appears, so a human reviews each move. Prefer images from a registry you control, mirrored from upstream after review.

How do you pin with include and keep it maintainable?

For project includes, set ref: to a full SHA, and add a comment naming the human-readable version it matches. For remote includes, host the file in a repository you control and pin that. Avoid include:remote pointing at a URL whose content can change.

Pinning has a cost: you must update. Schedule it. Automated dependency update bots or a monthly review ticket both work. A pin that never moves becomes its own risk, because security fixes stop arriving.

Limit blast radius too. Use protected variables, scope the job token to the projects it needs, and run third-party jobs in a stage that has no deployment credentials.

What to do

  1. Search your repositories for include:, component@, image: and curl in scripts.
  2. Replace branches, ~latest and mutable tags with SHAs, exact tags or digests.
  3. Mirror upstream components and images into a namespace you control.
  4. Add a merge request check that fails on unpinned references.
  5. Set a recurring update job that opens reviewed merge requests for each pin.
  6. Reduce job token scope and keep deploy secrets out of stages that run third-party code.
  7. Record the resolved include tree in build logs so audits can see what ran.

Safeguard's policies and gates can enforce rules like these in pull requests, and the CLI can scan the repository for pipeline weaknesses.

Quick check

Is an exact tag as safe as a commit SHA?

Almost, but not equal. A tag can be deleted and recreated by someone with write access. A full SHA cannot be moved.

Do GitLab CI/CD components make pinning unnecessary?

No. Components make versioning easier, but you still choose which version string you reference, and the docs say moving targets can bring breaking or unreviewed changes.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.