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:andservices:. - Scripts downloaded by
curlinsidescript: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
- Search your repositories for
include:,component@,image:andcurlin scripts. - Replace branches,
~latestand mutable tags with SHAs, exact tags or digests. - Mirror upstream components and images into a namespace you control.
- Add a merge request check that fails on unpinned references.
- Set a recurring update job that opens reviewed merge requests for each pin.
- Reduce job token scope and keep deploy secrets out of stages that run third-party code.
- 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
- GitLab Docs: CI/CD components
- GitLab Docs: Use CI/CD configuration from other files
- OWASP CI/CD Security Cheat Sheet
Last reviewed: 18 October 2026.