> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pipefort.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CICD-SEC-3 — Unpinned container image

> Container images pinned by a mutable tag can be repointed by the registry. Pin by digest.

| Field    | Value                                                                                                                              |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Category | `CICD-SEC-3`                                                                                                                       |
| Severity | **MEDIUM**                                                                                                                         |
| OWASP    | [CICD-SEC-3: Dependency Chain Abuse](https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-03-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](/rules/cicd-sec-3).

Pinning by digest makes the reference content-addressed: the bytes can't change
without the digest changing.

## Vulnerable example

```yaml theme={null}
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

```yaml theme={null}
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.
