Docker audit fix

How to Configure Docker Image Signature Verification

Image signature verification proves who published an image and that the registry did not alter it. Docker Content Trust was retired in June 2026, so use Sigstore cosign: sign in CI with cosign sign and gate deployments on cosign verify.

Hiren KalariyaLast reviewed: Oct 12, 2026Check image-signature-verification
Medium
severity
No
needs root or sudo
4
results it can return
5 of 8
checks in this audit

Every threshold on this page is transcribed from the docker-security-image-vulnerabilities audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 8 Image Vulnerabilities checks, or how the audit runs.

Medium severityNo sudo needed

What this check reads

The check reads exactly two things out of the audit's own shell: the environment variable DOCKER_CONTENT_TRUST, which defaults to 0 when unset, and whether a cosign binary is on PATH. A value of 1 is the only PASS. A cosign binary with the variable unset is one WARN and neither signal present is the other, while a missing docker binary short-circuits the whole check to SKIP before either is read. No image, signature, registry or admission policy is inspected at any point.

When it applies

Runs whenever the docker binary exists; it needs no daemon access and no root. It checks only two things: whether the environment variable DOCKER_CONTENT_TRUST is 1 in the audit's own shell, and whether a cosign binary is on PATH. It never inspects an image, a signature, or a policy. That makes the result a statement about the audit environment rather than about the host's images: DOCKER_CONTENT_TRUST=1 exported for one shell says nothing about how images were actually pulled, and an installed cosign says nothing about whether anything is verified with it.

Known accuracy issue: the PASS branch rewards Docker Content Trust, which Docker has retired - the announcement came in July 2025 and the notary.docker.io service was fully withdrawn in June 2026. Setting DOCKER_CONTENT_TRUST=1 today breaks pulls of Docker Official Images rather than securing them. Treat a PASS from this branch as a finding in its own right, and prefer Sigstore cosign or Notation. This is the known issue flagged at the top of this page.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
SKIPDocker not installedNothing was assessed. Without the docker binary the check stopped before it read the environment at all.
PASS`DOCKER_CONTENT_TRUST=1`The variable is set to 1 in the audit shell. Read this as a finding rather than a pass: Docker Content Trust is retired, and the setting now breaks pulls of official images instead of securing them.
WARNcosign present, DCT not setA cosign binary exists on the host, but the check cannot tell whether anything is actually verified with it.
WARNNothingNeither signal was found, so nothing on this host claims to check who published the images it runs.

Why it matters

Signature verification proves who published an image and that it was not altered in transit or in the registry. CIS Docker 4.5 historically said "Ensure Content trust for Docker is Enabled"; OWASP Docker Cheat Sheet Rule #13; NIST SP 800-190 4.1.5 (untrusted images); OWASP Top 10 A08:2021 Software and Data Integrity Failures.

Why it fails, and when it is wrong

The substance below was verified in August 2026; this check has drifted from the current state of image signing.

  • Docker Content Trust has been retired. Docker announced retirement in July 2025, stopped DCT for Docker Official Images from 8 August 2025 (signing certs expired), and completed retirement of the notary.docker.io service in June 2026. Hosts with DOCKER_CONTENT_TRUST=1 fail to pull official images. The PASS branch therefore rewards a setting that now breaks deployments and provides no security.
  • Docker's own CIS guidance now notes that Docker Hardened Images are signed with Cosign instead of DCT.
  • Recommended replacement: Sigstore cosign (cosign verify --certificate-identity ... --certificate-oidc-issuer ... <image>) or Notary Project's Notation, with policy enforced in CI/CD or an admission controller. The check should detect cosign/notation availability and, ideally, whether running images carry verifiable signatures.
  • The check also only inspects the audit shell's environment, not the deploy user's or CI's.

How to fix it

What to do today, given that the check's own advice is out of date:

Unset DOCKER_CONTENT_TRUST everywhere

unset DOCKER_CONTENT_TRUST
grep -rn DOCKER_CONTENT_TRUST ~/.bashrc ~/.profile /etc/environment /etc/profile.d 2>/dev/null

Remove it from CI variables as well. This turns the check's PASS into a WARN, which is the more accurate result.

Verify upstream images against a pinned identity

Where the publisher signs (Docker Hardened Images, Chainguard, many images built in GitHub Actions), verify against the identity you expect, not against any identity at all.

cosign verify <registry>/<image>:<tag> \
  --certificate-identity-regexp '^https://github.com/<org>/<repo>/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Sign your own images in CI

cosign sign --yes <registry>/<image>@sha256:<digest>

Keyless signing ties the signature to the workflow's OIDC identity, so there is no private key to store. Sign the digest, not a tag, because a tag can be moved after signing. Notary Project's Notation is the alternative.

Make deployment fail closed

Run cosign verify in the step that gates deployment and stop on a non-zero exit. A verification whose result is ignored, or that runs after the image is already live, provides nothing. Test the gate with a deliberately unsigned image.

Verify the fix

# What the check reads (and why it proves little):
echo "DOCKER_CONTENT_TRUST=${DOCKER_CONTENT_TRUST:-unset}"
command -v cosign notation

# What actually verifies an image - a signature check against a pinned identity.
# A regexp of '.*' would accept a signature from any GitHub workflow anywhere:
cosign verify <registry>/<image>:<tag> \
  --certificate-identity-regexp '^https://github.com/<org>/<repo>/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
# expected: "Verification for ... The following checks were performed"

# Prove it fails closed on an unsigned image:
cosign verify <registry>/unsigned-image:tag     # expected: no matching signatures

Debugging

Do not take this at face value. DCT is retired; with the notary service gone, that variable now causes docker pull of official images to fail. Unset it and adopt cosign instead. The check has not been updated for this.

The advice in the message is out of date for the same reason. If you are verifying with cosign verify in your deploy pipeline, you are in better shape than a PASS here; record the finding as a known script issue.

The accurate finding of the three. Adopt cosign: sign at build time (cosign sign) and verify in the pipeline or at admission (cosign verify) before anything is deployed.

A cosign verify whose result is ignored, or run after the image is already running, provides nothing. Wire it into the step that gates deployment, and test it against a deliberately unsigned image as above.

A valid signature only says someone signed it. Pin the expected identity and issuer (--certificate-identity, --certificate-oidc-issuer), otherwise any signature from anyone passes.

Sources

How the script reads this

Next

Re-run the Image Vulnerabilities audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 8 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 8 Image Vulnerabilities fixes
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Windows

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals