glsec

Rules / GL001

GL001 Mutable image tag

Severity: error (version-less variant tags: warn)
OWASP: CICD-SEC-3 – Dependency Chain Abuse

Risk

Using latest, a branch name, or no tag at all means the image pulled by the runner can change between pipeline runs without any code change. A compromised or hijacked registry tag silently introduces malicious code into your build environment.

What is flagged

SHA256 digest pins (@sha256:...) are always safe and never flagged.

Trigger example

build:
  image: node:latest        # mutable tag
  script:
    - ./build.sh

test:
  image: alpine             # no tag — defaults to latest
  services:
    - docker:dind           # version-less variant tag (warn)
  script:
    - ./test.sh

Safe alternative

build:
  image: node:20.11.0       # pinned version
  script:
    - ./build.sh

test:
  image: alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b
  services:
    - docker:27.3.1-dind    # versioned variant
  script:
    - ./test.sh

Notes

A tag is treated as version-less when it contains no digit. Version-qualified variants (docker:27-dind, python:3.11-slim, node:20.11.0-alpine, ubuntu:24.04) pass through. They are reported as warn rather than error. They float just like latest, but a variant tag at least fixes the image flavour, and docker:dind in particular appears in a large share of pipelines.

A tag containing a variable ($CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA, builder:$TAG-alpine) is not flagged, since its value is unknown statically.

An image reference that is a single variable (image: $MY_IMAGE or ${MY_IMAGE}, also in services:) is resolved against the values the same file assigns to it, and each value is checked like an inline image:

  1. every parallel:matrix value of that name in the job, each reported separately;
  2. for jobs without such a matrix value, or matrix entries that don't set the name, the job's own variables:;
  3. failing that, the top-level variables:, unless inherit:variables is false or a list that leaves the name out.

The message names both, e.g. image "$BASE_IMAGE" (= "node:latest") uses mutable tag "latest". Top-level variables are not used for hidden jobs (.template), whose users may override them, or for jobs that inherit through extends:, which glsec does not resolve. YAML merge keys (<<:) are resolved. A variable that is not defined in the file, or whose value contains another $, is not flagged, since its value is unknown statically. Variables that are never used in image: or services: (for example a docker push target) are not checked. GL007 still reports the same line, because a pipeline variable can override the value.

The VM images of GitLab-hosted macOS runners (image: macos-14-xcode-15) are selected by name, cannot carry a tag, and are not flagged.

Pinning an image prevents tag mutation but does not verify whether the pinned version contains known vulnerabilities. Use a dedicated container scanner such as trivy as a separate CI job to check for CVEs in pinned images.

Documentation for glsec 1.23.0. Source of this page · Rule implementation