Rules / GL084
GL084 CI variable interpolation in an include: target
Severity: error for $CI_COMMIT_REF_NAME, warn for any other variable
OWASP: CICD-SEC-4 – Poisoned Pipeline Execution
What glsec checks
A CI variable used to build an include: target, in either the $VAR or ${VAR} spelling. The fields inspected are local:, project:, ref:, file: (including a list of files), and the path part of a component: reference before the @.
GL007 flags a variable in an image: reference because the variable decides which container the job runs in. This is the same argument one level up: the variable decides which configuration the pipeline runs.
Why it matters
GitLab evaluates include: before anything else, and documents exactly which variables resolve there: project, group and instance variables, CI_PROJECT_*, trigger variables, scheduled pipeline variables, manual pipeline run variables, CI_PIPELINE_SOURCE, CI_PIPELINE_TRIGGERED, and $CI_COMMIT_REF_NAME. Variables from the file's own variables: block do not resolve.
That list splits the cases:
$CI_COMMIT_REF_NAMEresolves, and its value is the branch or tag name. Anyone who can push a ref picks it.ref: $CI_COMMIT_REF_NAMEon a project include pulls the template from whatever ref in the other project matches the pusher's branch name. This is theerrorcase.- A custom variable takes its value from a project, group or instance variable, a trigger, a schedule, or the manual run form. Manual pipeline variables sit above project, group and instance variables in the precedence order, so anyone who can start a pipeline supplies the value regardless of the default in project settings. This is the
warncase.
Either way the included file is chosen when the pipeline starts, by someone who is not the reviewer of the .gitlab-ci.yml, and everything the include contributes then runs: jobs, before_script, default:.
Trigger example
include:
- local: /ci/$TEMPLATE_NAME.yml # warn
- local: /ci/$CI_COMMIT_REF_NAME.yml # error
- project: $TEMPLATE_PROJECT # warn
ref: 3a1f9c2b7e5d4a6f8c0b1d2e3f4a5b6c7d8e9f01
file: /templates/$TEMPLATE_FILE.yml # warn
- project: my-group/ci-templates
ref: $CI_COMMIT_REF_NAME # error
file: /jobs/deploy.yml
What is not flagged
Predefined variables whose value the GitLab instance sets, rather than a pipeline caller:
include:
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/scan@1.2.3 # GitLab's own documented idiom
- project: $CI_PROJECT_NAMESPACE/ci-templates
ref: 9f8e7d6c5b4a39281706f5e4d3c2b1a098765432
file: /jobs/test.yml
That covers CI_PROJECT_*, CI_SERVER_*, CI_API_*, CI_PIPELINE_SOURCE and CI_PIPELINE_TRIGGERED.
Safe alternative
Name the file and pin the ref:
include:
- project: my-group/ci-templates
ref: 3a1f9c2b7e5d4a6f8c0b1d2e3f4a5b6c7d8e9f01
file: /jobs/deploy.yml
If the pipeline genuinely needs to switch between configurations, do it with include:rules on fixed paths rather than by building a path from a variable:
include:
- local: /ci/deploy-prod.yml
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- local: /ci/deploy-review.yml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Every reachable configuration is then visible in the file and reviewable.
Notes
remote:is not inspected. GL003 already reports every remote include as an error, so a second finding on the same line adds nothing.- The version after
@in acomponent:reference belongs to GL041, which checks that it is a pinned semver tag or SHA. CI_DEFAULT_BRANCHis treated as server-controlled and not flagged. It does not actually resolve in an include section, so using it there is a correctness bug rather than a security one, and no pipeline caller can set its value.template:takes a GitLab-shipped path andinputs:are passed to the included configuration rather than selecting it. Neither is inspected.- One finding per field. When a value mixes variables,
$CI_COMMIT_REF_NAMEis the one reported, because it carries the higher severity.
See also
- GL003 — include with a mutable or missing
ref - GL007 — CI variable interpolation in an
image:reference - GL041 —
include: component:without a pinned version - GL075 — include source not on the opt-in allowlist
Documentation for glsec 1.23.0. Source of this page · Rule implementation