Skip to main content
FieldValue
CategoryCICD-SEC-3
SeverityMEDIUM
OWASPCICD-SEC-3: Dependency Chain Abuse
Auto-fix✗ (digest resolution requires a registry round-trip; pin manually)

What the check does

Flags container images referenced by a mutable tag instead of an immutable @sha256: digest, across the three places a workflow pulls an image:
  • a job-level container: (scalar image:tag or a mapping with image:)
  • a services.<name>.image
  • a step uses: docker://image:tag
Images whose value is a pure expression (${{ ... }}) are skipped — the reference isn’t knowable statically.

Why it matters

A tag such as :latest or :18 is a moving pointer. The registry (or an attacker who compromises it, or the publishing account) can repoint that tag to a different image between runs. Your workflow then pulls and executes code you never reviewed — often with repository secrets and a writable token in scope. This is the container-image analog of CICD-SEC-3 unpinned actions. Pinning by digest makes the reference content-addressed: the bytes can’t change without the digest changing.

Vulnerable example

jobs:
  build:
    runs-on: ubuntu-latest
    container: node:18                 # ← mutable tag
    services:
      db:
        image: postgres:16            # ← mutable tag
    steps:
      - uses: docker://alpine:3.18    # ← mutable tag

Safe alternative

jobs:
  build:
    runs-on: ubuntu-latest
    container: node@sha256:a1b2c3...  # digest, optionally with a tag comment
    services:
      db:
        image: postgres@sha256:d4e5f6...
    steps:
      - uses: docker://alpine@sha256:7890ab...
Resolve a tag to its digest with docker buildx imagetools inspect <image>:<tag> (or crane digest), and keep the digest current with Dependabot or Renovate, both of which update digest-pinned images.