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 | When | What it means |
|---|---|---|
| SKIP | Docker not installed | Nothing 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. |
| WARN | cosign present, DCT not set | A cosign binary exists on the host, but the check cannot tell whether anything is actually verified with it. |
| WARN | Nothing | Neither 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=1fail 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/nullRemove 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.comSign 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 signaturesDebugging
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
- Docker blog: Docker Content Trust, Retirement and Migration Guidance (June 2026)
- Docker blog: Retiring Docker Content Trust (July 2025)
- Docker docs: CIS Benchmark and Docker Hardened Images (cosign instead of DCT)
- Sigstore cosign: Verifying
- cosign repository
- Notary Project / Notation
- OWASP Docker Security Cheat Sheet (Rule #13)
- OWASP Top 10 A08:2021 Software and Data Integrity Failures
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