Supply chain attack risk from unpinned Docker image tags

High Risk infrastructure-security
dockercontainer-securityimage-tagssupply-chaindeployment-securityimmutable-tagsdockerfile

What it is

Docker containers using mutable image tags like 'latest' or unspecified tags instead of pinned, immutable references create unpredictable deployments where the actual container image can change over time without explicit updates. Mutable tags can pull different image versions across environments, making deployments non-deterministic and vulnerable to supply chain attacks where malicious images could be pushed with the same tag.

# VULNERABLE: Dockerfile with unpinned tags
FROM node  # No tag, defaults to 'latest'
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

# VULNERABLE: Multi-stage with latest
FROM node:latest AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM alpine:latest
RUN apk add nodejs npm
COPY --from=builder /app .
CMD ["node", "server.js"]

# VULNERABLE: Kubernetes with mutable tag
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: app
        image: myapp:latest  # Mutable tag
        ports:
        - containerPort: 3000
# SECURE: Dockerfile with pinned digest
FROM node:18.18.2-alpine3.18@sha256:87b1d16d77535c72075d5971b84b2e77944e34dc3f2b0a2fa0ce8e05d0e2e4ab
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

# SECURE: Multi-stage with pinned digests
FROM node:18.18.2-alpine3.18@sha256:87b1d16d77535c72075d5971b84b2e77944e34dc3f2b0a2fa0ce8e05d0e2e4ab AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .

FROM alpine:3.18.4@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978
RUN apk add --no-cache nodejs=18.18.2-r0
WORKDIR /app
COPY --from=builder /app .
CMD ["node", "server.js"]

# SECURE: Kubernetes with digest
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: app
        image: myapp:v1.2.3@sha256:2f5b3c6e4d8a9b7c1e0f3a5d8b2c4e6f8a0b2d4f6e8c0a2b4d6f8e0c2a4b6d8f
        imagePullPolicy: Always
        ports:
        - containerPort: 3000

💡 Why This Fix Works

The vulnerable configurations use mutable tags like 'latest' or omit tags entirely, allowing the pulled image to change over time without explicit updates. This creates non-deterministic builds vulnerable to supply chain attacks. The secure versions pin images to specific versions with SHA256 digests, ensuring reproducible builds and preventing unexpected image changes, making it impossible for a malicious actor to replace the image without changing the digest.

Why it happens

Dockerfile FROM instructions specify image:latest or omit tags entirely (which defaults to :latest). Developers write FROM node, FROM python, FROM ubuntu without version tags for convenience during development. The 'latest' tag is mutable and can point to different image versions over time as new releases are published. Applications that build successfully in development may break in production when 'latest' points to a new major version with breaking changes, or worse, pull a compromised image if an attacker gains access to the image repository.

Root causes

Dockerfiles Using 'latest' Tag or Missing Image Tags

Dockerfile FROM instructions specify image:latest or omit tags entirely (which defaults to :latest). Developers write FROM node, FROM python, FROM ubuntu without version tags for convenience during development. The 'latest' tag is mutable and can point to different image versions over time as new releases are published. Applications that build successfully in development may break in production when 'latest' points to a new major version with breaking changes, or worse, pull a compromised image if an attacker gains access to the image repository.

Base Images Without Version Specifications

Using minimal base images like FROM alpine, FROM debian, FROM ubuntu without specifying exact versions (alpine:3.18.4, debian:12.2, ubuntu:22.04). While these images are commonly used for their small size, unversioned references create non-deterministic builds where different teams or CI/CD runs may pull different underlying OS versions. Security patches, library updates, or system tool changes in newer base image releases can introduce unexpected runtime behavior or compatibility issues.

Kubernetes Deployments with Mutable Image References

Kubernetes Deployment, StatefulSet, or Pod manifests specify container images using mutable tags in spec.containers[].image field (e.g., image: myapp:latest or image: myapp:dev). When Kubernetes pulls these images, different nodes may cache different versions, creating inconsistent application behavior across the cluster. ReplicaSets can run different code versions simultaneously, and rollback becomes impossible since previous 'latest' versions are overwritten and unrecoverable.

Missing Digest References in Container Manifests

Container deployment configurations (Docker Compose, Kubernetes manifests, Helm charts) reference images by tag alone without including SHA256 digest references. Image tags can be overwritten in container registries, whether accidentally by CI/CD systems pushing to the same tag, or maliciously by attackers who compromise registry credentials. Without digest pinning (image@sha256:...), there's no cryptographic verification that the pulled image matches the originally intended image, enabling supply chain attacks.

Prioritizing Convenience Over Build Reproducibility

Development teams prioritize rapid iteration and convenience over reproducible builds by using flexible image tags. Organizations lack policies requiring pinned images, have no automated tools to update pinned versions, and don't implement admission control to prevent unpinned images in production. Teams view image pinning as maintenance overhead without understanding supply chain security risks. Documentation and tutorials often show unpinned examples, perpetuating the practice across the industry.

Fixes

1

Pin Images to Specific Version Tags

Update all Dockerfile FROM instructions and container manifests to use specific, semantic version tags instead of 'latest' or omitted tags. Use full version specifications like FROM node:18.18.2-alpine3.18, FROM python:3.11.6-slim-bookworm, FROM nginx:1.25.3-alpine. Specify exact versions for all base images, build tools, and runtime images. Version tags provide predictable, reproducible builds while remaining human-readable compared to digest-only references. Document the chosen versions in CHANGELOG or README and establish a regular schedule for reviewing and updating pinned versions.

2

Use Immutable SHA256 Digest References

Pin container images using cryptographic SHA256 digests in addition to or instead of version tags: FROM node:18.18.2-alpine3.18@sha256:87b1d16d77535c72075d5971b84b2e77944e34dc3f2b0a2fa0ce8e05d0e2e4ab. Obtain digests using docker pull <image:tag> followed by docker inspect --format='{{index .RepoDigests 0}}' <image:tag>. Digest references provide cryptographic verification that prevents tag substitution attacks and ensures byte-for-byte identical images across all environments. Use both tag and digest (image:tag@digest) for best practice combining human readability with security.

3

Implement CI/CD Image Pinning and Scanning Workflows

Create CI/CD pipeline stages that: (1) scan container images for vulnerabilities using Trivy, Snyk, or Anchore, (2) automatically extract and pin SHA256 digests for images that pass security scans, (3) update Dockerfiles and Kubernetes manifests with pinned digests, (4) commit pinned references to version control. Use tools like docker buildx imagetools inspect to extract digests. Implement quality gates that fail builds containing unpinned images or images with high-severity vulnerabilities. Generate SBOM (Software Bill of Materials) for all images to track supply chain components.

4

Deploy Kubernetes Admission Controllers to Enforce Image Policies

Implement Kubernetes admission controllers using Open Policy Agent (OPA) Gatekeeper, Kyverno, or native validating webhooks to reject Pod creation requests with unpinned image tags. Create policies that: require all images to include SHA256 digests, block 'latest' tag usage, enforce allowed registry domains, and require image signatures (using tools like Sigstore/Cosign). Example Gatekeeper policy: deny containers where image does not match pattern '*@sha256:*'. Configure exceptions for development namespaces while enforcing strict policies in production.

5

Automate Image Updates with Dependabot or Renovate

Configure automated dependency update tools like Dependabot, Renovate Bot, or WhiteSource Renovate to monitor Dockerfiles and container manifests for outdated base images. These tools automatically create pull requests when new versions or digests are available, including changelogs and security patch information. Configure update schedules (weekly, monthly), enable auto-merge for patch versions after CI passes, and group related updates. Set up Slack/email notifications for major version updates requiring manual review. Automation ensures pinned images stay current with security patches without manual tracking overhead.

Detect This Vulnerability in Your Code

Sourcery automatically identifies supply chain attack risk from unpinned docker image tags and many other security issues in your codebase.