Why this matters
GitLab CI lets a pipeline pull configuration from outside the repository. An include: entry can load a file from another project, a CI/CD component from the catalog, or a file from any URL. Whatever those sources contain becomes part of your pipeline, running with your variables and your deploy credentials.
If the include points at a branch or a tag, the code it loads can change without anyone touching your repository. Whoever controls the other project can push new code to that branch, or move the tag, and your next pipeline runs it. This is the same weakness that was exploited against GitHub Actions in the tj-actions/changed-files compromise of March 2025, where version tags were rewritten to point at code that leaked secrets.
A full commit SHA cannot be moved. Pinning your includes to one means the configuration you reviewed is exactly what runs.
What good looks like
include:
# A file from another project, pinned to a commit.
- project: my-group/ci-templates
ref: 9f3c2b7d1e4a6f8c0b2d4e6f8a0c2e4f6a8b0c2d # v3.1.0
file: /templates/php.yml
# A CI/CD component, pinned to a commit.
- component: gitlab.example.com/my-group/components/php-test@b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0 # 1.4.0
inputs:
php_version: '8.5'
# Files in this repository and GitLab's own templates need no pinning.
- local: /.gitlab/ci/build.yml
- template: Security/SAST.gitlab-ci.yml
How to do it
Pin project includes
- Open the project you include from and find the commit behind the branch or tag you currently use.
- Copy its full 40-character SHA.
- Set
ref:to that SHA. Add a comment with the tag name so readers know which version it is.
Pin components
A component reference ends with @ and a version. Replace the version with the full SHA of the commit that release was made from. You can find it on the component project's tags page.
Replace remote URL includes
An include: remote: entry loads a file from a URL, and a URL has no way to pin a specific revision. Move the file into a project you control and include it with project: and a pinned ref:, or copy it into your own repository and use local:.
Keep pins up to date
Pinned includes do not update themselves. Renovate supports GitLab and will open merge requests that move a pinned SHA when a new version is published, so you still get updates, now as reviewable changes.
Things to watch for
ref: mainorref: v1. Both are moving targets. Only a full commit SHA is fixed.~lateston components. This always resolves to the newest release, whatever it contains.- A pin with no version comment. Future readers cannot tell what a bare SHA is. Leave a comment with the tag.
- Custom CI config paths. If your project sets a custom CI/CD configuration file, the includes to pin live there rather than in
.gitlab-ci.yml.