Container Supply-Chain Security: Trivy vs Grype Scanning and Cosign Signing (2026)

Container Supply-Chain Security: Trivy vs Grype Scanning and Cosign Signing (2026)

Container Image Scanning: Trivy vs Grype and Cosign Signing in 2026

In March 2026 the most widely used open-source scanner in the container world became the delivery vehicle for credential-stealing malware. According to the project’s own advisory (GHSA-69fq-xp46-6×23, CVE-2026-33634), an attacker published a malicious Trivy v0.69.4 release and force-pushed 76 of the 77 version tags of the trivy-action GitHub Action. Pipelines that ran “the security step” handed their secrets to the thief. The lesson is not that Trivy is bad. It is that container image scanning is a control that must itself be pinned, verified and signed, or it becomes the weakest link.

Most teams still treat scanning as a single checkbox: run a scanner, fail on CRITICAL, move on. That misses three questions that decide whether the control works. How does the scanner decide an image is vulnerable, and why do two scanners disagree on the same image? What proves that the image running in the cluster is the one that was scanned? And what happens when the scanner, its database or its CI action is compromised?

This post compares Trivy and Grype at the mechanism level, then wires both into a pipeline that signs images with Cosign using Sigstore keyless identity and enforces the result at admission time. You will leave with a decision matrix, a runnable GitHub Actions workflow (flagged as untested), worked numbers for triage, and a list of failure modes.

What this covers: the context and threat model, how scanners really work, a Trivy vs Grype decision matrix, Cosign keyless signing and verification, admission enforcement, trade-offs, a checklist, and an FAQ.

Context and Background

Supply-chain attacks on build systems changed the baseline. The 2020 SolarWinds build compromise and the 2021 Codecov uploader tampering showed that attackers go after the tooling around the code. Vulnerability scanning answers a different question from provenance: scanning says “does this artifact contain known-vulnerable components”, while provenance says “who built this artifact, from what, and has it changed since”. A mature pipeline needs both, and they should be bound together through a signature on the image digest.

The incumbents in open-source scanning are Aqua Security’s Trivy and Anchore’s Grype (paired with the Syft SBOM generator). Trivy is a single binary that scans container images, filesystems, git repositories, Kubernetes clusters and infrastructure-as-code, and it covers vulnerabilities, misconfigurations, secrets and licenses. Grype is deliberately narrower: it matches packages against vulnerability data and relies on Syft, or any SBOM you hand it, to discover the packages. Commercial tools (Snyk, Wiz, Prisma Cloud and others) sit on top of similar ideas with extra reachability analysis and policy UIs.

On the signing side, Sigstore is the open-source project under the Linux Foundation’s OpenSSF that provides Cosign (signing and verification), Fulcio (a certificate authority that issues short-lived certificates bound to an OIDC identity) and Rekor (an append-only transparency log). Sigstore’s own announcement of Cosign 3 states that the new bundle format, trusted-root and signing-config behaviours become defaults in v3, with legacy behaviour slated for removal in v4. See the Sigstore blog post on Cosign v3 for the migration detail; this post assumes you are on a Cosign 3.x client and tells you where flags differ in 2.x. I could not fetch the current patch version numbers at publish time, so check each project’s releases page and pin to a specific tag or digest.

Scanning and signing are covered in more depth from the provenance angle in our posts on SLSA and Sigstore supply-chain architecture and on SBOM and VEX with CycloneDX vs SPDX. Read those if you need the attestation formats and the triage vocabulary; this post concentrates on the scanner choice and the end-to-end pipeline. For the formal control framework, NIST’s Secure Software Development Framework (SP 800-218) is the reference that auditors will map you to.

Why this matters for industrial and IoT fleets

The same pattern applies to edge gateways, robot controllers and digital-twin services that ship as containers. A fleet of thousands of edge nodes cannot be re-imaged on a Friday, so the question “which running images contain this CVE” must be answerable from stored SBOMs in minutes, not by re-scanning every node. Signing and admission policy also matter more at the edge, where nodes may pull from a local mirror and a tampered image could sit near a safety-relevant control loop. The architectural lesson from control engineering is similar to what we discuss for model predictive control in industrial digital twins: constraints must be enforced at the point of actuation, not just checked upstream.

Reference Architecture: Scan, Sign, Attest, Verify

Direct answer: a trustworthy container pipeline builds an image once, identifies it by digest, generates an SBOM, scans the SBOM and image against a pinned vulnerability database, gates on policy, then signs the digest with Cosign and attaches the scan and SBOM as attestations. The cluster’s admission controller later verifies the signature identity and attestations before any pod runs.

Container image scanning and signing pipeline from build to admission verification

Figure 1: End-to-end pipeline. Scanning decides whether an image may be signed; signing and admission decide whether it may run.

The diagram shows a single direction of trust. Nothing is signed until the policy gate passes, and nothing runs until the signature and attestations verify. The long-description version: the build job pushes an image to a registry and records its sha256 digest; the SBOM and scan steps operate on that digest, never on a mutable tag; a pass leads to a Cosign signature stored as an OCI artifact next to the image; and the admission controller re-checks everything at deploy time.

Principle 1: identify by digest, never by tag

A tag such as myapp:1.4.2 is a mutable pointer. Between the scan and the deploy, someone can push a different image under the same tag. The digest (sha256:...) is a hash of the image manifest, so it names exactly one set of layers. Every step in the pipeline above should receive the digest as its input and emit it as its output. The Trivy compromise illustrates the same principle on the tooling side: the advisory says images referenced by digest were not affected, while unpinned latest usage during the exposure window was.

Principle 2: separate the detector from the verifier

Scanning is a statement about the past: at scan time, with database version X, this image matched these advisories. New CVEs appear daily, so an image that passed on Monday may fail on Friday without a single byte changing. The signature should therefore carry a time-bounded claim (“scanned clean against DB build T”), and the cluster should treat old scan attestations as stale. This is the most common design flaw: teams sign once and treat the signature as a perpetual safety certificate.

Principle 3: make the identity, not the key, the trust anchor

Traditional image signing used long-lived private keys, which have to be stored, rotated and eventually leak. Sigstore keyless signing replaces the key with a workflow identity. The CI job proves who it is with an OIDC token, Fulcio issues a certificate valid for minutes, and the verifier checks that the certificate’s identity and issuer match policy. The mechanism is detailed in the Cosign section below.

Where the SBOM fits

Generating the SBOM once at build time (with Syft, or Trivy’s --format cyclonedx) and scanning that file has two benefits. First, you scan exactly what was inventoried, so scan results are reproducible from the stored artifact. Second, you can rescan the stored SBOM every night against the fresh database without pulling the image again, which is how large fleets answer “are we affected” at scale. Grype accepts sbom:./sbom.json as an input source, and Trivy accepts trivy sbom. Both therefore support an inventory-first workflow, as covered in the earlier SBOM and VEX post linked above.

How a Container Vulnerability Scanner Actually Decides

A container vulnerability scanner is a pipeline of five stages: unpack layers, catalog packages, match against a database, apply distribution-specific fix status, and filter. Differences in any stage explain why Trivy and Grype report different counts for the same image, and why neither count is “the truth”.

Container vulnerability scanner internals from layers to report

Figure 2: Stages inside a container vulnerability scanner. Disagreements between tools almost always come from the catalog, match or filter stages.

Stage 1 and 2: unpack and catalog

The scanner pulls the image manifest, then reads each layer tarball and merges them into a virtual filesystem view. It then looks for package evidence. For operating-system packages that means the dpkg status file (/var/lib/dpkg/status), the RPM database, or the apk installed database. For language ecosystems it means lock files and manifests such as package-lock.json, go.mod, requirements.txt, pom.xml, or the metadata embedded in compiled binaries (Go binaries carry module build info; Java archives carry pom.properties).

Here is the first source of divergence. Trivy by default scans OS packages and language dependencies found as files; Syft (feeding Grype) has a broader set of catalogers and can detect, for example, software inferred from binaries or from JAR contents. A scanner that does not catalog a package cannot report on it. An empty result is therefore not evidence of safety; it can mean the cataloger did not recognise your layout. Distroless and scratch images, statically linked Go binaries and vendored C libraries are the classic blind spots.

Stage 3: match against the database

Each cataloged package is converted into identifiers such as a package URL (purl) or a CPE (Common Platform Enumeration) string and looked up in a vulnerability database. Trivy builds its database from several upstream feeds, including distribution security trackers and GitHub Security Advisories, and distributes it as an OCI artifact from a container registry. Grype builds its own database from similar upstream feeds and distributes it through its own listing. Both update frequently, and both download the database at scan time unless you cache or mirror it.

The matching strategy matters. Distribution trackers (Debian, Ubuntu, Red Hat, Alpine) are authoritative for packages the distribution ships: they say, for each CVE and each package version, whether it is affected, fixed in a given version, or not going to be fixed. CPE-based matching against the NVD (National Vulnerability Database) is broader but noisier, because CPE names are imprecise and frequently produce false positives, for example matching a library by name when the distribution has already backported the fix.

Stage 4: distribution fix status and backports

This is where most false-positive arguments live. Enterprise distributions backport security fixes without changing the upstream version number. A package labelled openssl 1.1.1k on Red Hat may contain patches that the upstream 1.1.1k lacks. A scanner that compares version strings against upstream ranges flags it wrongly; a scanner that consults the distribution tracker does not. The practical rule: for OS packages, prefer results derived from the distribution’s tracker, and treat generic CPE matches as hints that need verification.

Fix status also yields the most useful filter in practice. The CVE statuses “affected”, “fixed”, “will_not_fix”, “end_of_life” and “under_investigation” appear in Trivy’s output model; a pipeline that gates only on vulnerabilities with an available fix (--ignore-unfixed in Trivy; --only-fixed in Grype) turns the gate into something developers can act on.

Stage 5: VEX, ignore rules and severity

Finally the scanner applies suppressions. Both tools support VEX (Vulnerability Exploitability eXchange) documents, in which a vendor or you assert that a given CVE is “not_affected” for a product because, say, the vulnerable function is never called. Both also support local ignore files (.trivyignore, .grype.yaml). Severity is a mapped field, usually the vendor’s rating when available and CVSS (Common Vulnerability Scoring System) otherwise. CVSS base scores do not measure exploitation likelihood; for prioritisation, add EPSS (Exploit Prediction Scoring System) probability and the CISA KEV (Known Exploited Vulnerabilities) catalog as extra signals. Grype’s reports include EPSS and KEV fields in recent releases, and its risk score combines severity, EPSS and KEV; verify the field names in your installed version with grype -o json before building policy on them.

A worked triage example (illustrative numbers)

The numbers below are illustrative, not measured benchmarks; they show how the filter stages change a raw report. Suppose an image built on a general-purpose Debian base with a Node.js app yields 240 raw findings.

Step Findings remaining Why it drops
Raw scanner output 240 All severities, all statuses
Remove “will_not_fix” and “not affected” per distro tracker 150 Debian marks many low-impact CVEs as minor or unimportant
Keep only HIGH and CRITICAL 22 Severity threshold
Keep only those with a fix available 9 --ignore-unfixed / --only-fixed
Apply VEX for unreachable code 6 Documented not_affected statements

A gate that fails on 240 findings gets disabled within a week. A gate that fails on 6 actionable findings, each with an upgrade path, gets fixed. The counts are invented for illustration, but the shape (a roughly order-of-magnitude reduction from raw to actionable) is typical of what practitioners report. The point is that the gate definition is a design decision, not a default.

Trivy vs Grype: Head-to-Head

Trivy is the broader, all-in-one tool; Grype is the focused matcher that pairs with Syft and fits SBOM-first workflows. Neither is universally more accurate. Choose on workflow fit, then validate on your own images, because database and cataloger differences produce different results per image.

Scope and design

Dimension Trivy Grype (with Syft)
Primary job Multi-scanner: vulnerabilities, misconfiguration, secrets, licenses Vulnerability matching only
SBOM generation Built in (CycloneDX, SPDX) Separate tool, Syft
SBOM as input trivy sbom grype sbom:file
Targets Images, filesystems, repos, Kubernetes, IaC, VMs Images, directories, archives, SBOMs
Database delivery OCI artifact, mirrorable Own listing, mirrorable
Offline use Supported with a pre-fetched DB and --skip-db-update Supported by pointing at a local DB; auto-update can be disabled
Fix-only filter --ignore-unfixed --only-fixed
Gate flag --exit-code 1 --severity HIGH,CRITICAL --fail-on high
VEX --vex option VEX support via --vex in current releases (verify in your version)
Output table, json, sarif, cyclonedx, spdx, template table, json, sarif, cyclonedx, template
Licence Apache 2.0 Apache 2.0

Where the results differ

Published comparisons repeatedly find that the two tools overlap heavily on OS-package findings from distribution trackers and diverge on language-ecosystem packages and on binary detection. I did not run a controlled benchmark for this post and will not quote counts as if I had. If you want numbers, run both against a fixed set of image digests on the same day with fresh databases, diff the results on (CVE, package, version) triples, then manually adjudicate a sample of disagreements. A methodology note: scanner comparisons that use only a popular base image such as alpine or debian flatter both tools, because those paths are the best-supported. Use your own application images, including at least one Go service, one Java service and one Python service.

Operational differences that matter more than accuracy

Speed and database size. Both tools download a database of a few hundred megabytes at most on first run; the practical cost is CI cold starts. Cache the database directory between runs (~/.cache/trivy, ~/.cache/grype) or mirror it into an internal registry. Rate limiting on the public registry is a real failure mode for busy CI fleets; Trivy’s documentation describes mirroring its database to a private registry for exactly this reason.

Breadth of findings. If you also want Dockerfile and Kubernetes manifest misconfiguration checks and secret detection from the same binary, Trivy removes tools. If you already generate SBOMs as a first-class artifact and want to rescan them nightly, Grype’s input model is a natural fit, and Syft’s SBOM quality is a strong reason to pick the pair.

Security posture of the tool itself. This is the dimension the March 2026 incident forces into the decision. Any scanner runs with access to your CI environment and possibly your registry credentials. Whichever you pick, you must pin the binary by checksum or digest, verify its signature, and run it with the minimum token scope (more on this below).

Decision matrix

Your situation Pick Reason
Want one tool for images, IaC and secrets Trivy Single binary, several scanners
SBOM-first pipeline with nightly rescans Grype + Syft Clean SBOM-in, findings-out model
Strict air-gapped environment Either Both can run from a mirrored database; test your update procedure
Need defence in depth against scanner blind spots Run both on critical images Disagreements expose cataloger gaps
Small team, limited CI minutes Trivy Fewer moving parts
Compliance wants an SBOM artifact stored Either, plus Syft or Trivy SBOM output Both emit CycloneDX

Running both is cheaper than it sounds. Scanning a stored SBOM with Grype takes seconds, and the union of two independent matchers is a cheap hedge against a single database’s gaps. The cost is triage noise, so only block on the intersection or on findings with a fix, and treat the symmetric difference as an investigation queue.

Cosign Image Signing and Sigstore Keyless Signing in Depth

Sigstore keyless signing replaces a stored private key with a short-lived certificate tied to an OIDC identity, and records the event in a public transparency log. The verifier then checks identity, issuer and log inclusion instead of checking against a key you must guard. This removes the key-management burden and, importantly, ties each signature to a specific workflow.

Sigstore keyless signing sequence between CI, OIDC issuer, Fulcio, Rekor and the registry

Figure 3: Cosign keyless signing. The CI job exchanges an OIDC token for a certificate that lives for minutes, signs the digest, and logs the event.

The signing sequence step by step

  1. Identity token. In GitHub Actions, a job with permissions: id-token: write can request an OIDC token from GitHub’s issuer (https://token.actions.githubusercontent.com). The token’s claims include the repository, the workflow reference and the commit.
  2. Ephemeral key pair. Cosign generates a key pair in memory. The private key never touches disk.
  3. Certificate issuance. Cosign sends the public key and the token to Fulcio, which validates the token and issues an X.509 certificate whose Subject Alternative Name carries the workflow identity. Per Sigstore’s documentation the certificate is valid for ten minutes.
  4. Signature and upload. Cosign signs the image digest and stores the signature in the registry as an OCI artifact referencing the digest. With the Cosign 3 bundle format, the signature, certificate and transparency-log proof travel together in one bundle, which enables offline verification.
  5. Transparency log. Cosign records the signature entry in Rekor, which returns a signed entry timestamp. Because the certificate has expired by the time anyone verifies, the log’s timestamp is what proves the signature was made while the certificate was valid.

The security argument is subtle. A leaked ephemeral key is useless after ten minutes. An attacker who wants to forge a signature needs to get Fulcio to issue a certificate for your identity, which requires a valid OIDC token from your CI provider, which requires running code in your workflow. That moves the attack surface from “steal a file” to “compromise the workflow”, which is still dangerous but is a more auditable and more bounded target.

Signing in Cosign 3

Cosign 3 changed defaults. Per the Sigstore announcement, the new bundle format, trusted root and signing config behaviours become the default in v3, and legacy functionality is planned for removal in v4. In practice, if your pipeline passes the older flags explicitly, test on a branch before bumping the client. The signing command itself is short:

# Sign by digest, never by tag. Untested snippet: verify flags against your installed cosign version.
cosign sign --yes "ghcr.io/acme/orders@sha256:${DIGEST}"

The --yes flag skips the interactive confirmation that the signing event will be written to a public log. That public part has consequences: the repository name and workflow identity appear in Rekor, which is visible to anyone. For private projects, evaluate whether that metadata leak is acceptable, or run a private Sigstore deployment.

Attestations: binding scan results to the image

A bare signature says only “this identity vouches for this digest”. An attestation adds a typed claim, using the in-toto statement format wrapped in a DSSE (Dead Simple Signing Envelope) signature. Cosign can attach a vulnerability scan result or an SBOM as an attestation:

# Create the scan report, then attach it as a typed attestation.
trivy image --format cosign-vuln --output vuln.json "$IMAGE@$DIGEST"
cosign attest --yes --type vuln --predicate vuln.json "$IMAGE@$DIGEST"

# Attach the SBOM as a CycloneDX attestation.
cosign attest --yes --type cyclonedx --predicate sbom.cdx.json "$IMAGE@$DIGEST"

The cosign-vuln Trivy format and the vuln predicate type exist in the Cosign and Trivy documentation; confirm the exact format names for your versions, as these are the kind of surface that changes between majors. The value of the attestation is that the cluster can later ask “was this digest scanned, by whom, and when”, and the answer is cryptographically bound to the image.

Verification: pin the identity, not just “any valid signature”

Verification is where most deployments fail quietly. cosign verify with no identity constraint would accept a signature from anyone who can obtain a Fulcio certificate, including a stranger’s GitHub workflow. You must constrain both the certificate identity and the OIDC issuer:

cosign verify \
  --certificate-identity "https://github.com/acme/orders/.github/workflows/release.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  "ghcr.io/acme/orders@sha256:${DIGEST}"

For flexibility, --certificate-identity-regexp accepts a pattern, but a loose pattern such as .*acme.* undoes the protection. Pin the exact workflow file and ref. Pinning the branch ref has a trade-off: release workflows triggered from tags have a different @refs/tags/... identity, so decide up front which ref form your policy accepts.

Wiring It Together: A Reference Pipeline

The following GitHub Actions workflow builds, generates an SBOM, scans with both tools, gates, signs and attests. It is a teaching sketch. I have not executed it end to end for this post, so treat it as untested and validate versions and flags in a scratch repository first.

name: release-image
on:
  push:
    tags: ["v*"]
permissions:
  contents: read
  packages: write
  id-token: write       # required for keyless signing
  attestations: write
env:
  IMAGE: ghcr.io/${{ github.repository }}
jobs:
  build-scan-sign:
    runs-on: ubuntu-latest
    steps:
      # Pin every third-party action to a full commit SHA, not a tag.
      - uses: actions/checkout@<FULL_SHA>
      - uses: docker/login-action@<FULL_SHA>
        with: { registry: ghcr.io, username: "${{ github.actor }}", password: "${{ secrets.GITHUB_TOKEN }}" }
      - id: build
        uses: docker/build-push-action@<FULL_SHA>
        with: { push: true, tags: "${{ env.IMAGE }}:${{ github.ref_name }}" }
      - name: Install pinned scanners
        run: |
          # Download releases by explicit version, then verify checksum/signature
          # before executing. Versions come from a reviewed file in the repo.
          ./ci/install-tools.sh   # your script: curl + sha256sum -c + cosign verify-blob
      - name: SBOM
        run: syft "$IMAGE@${{ steps.build.outputs.digest }}" -o cyclonedx-json=sbom.cdx.json
      - name: Scan with Grype from SBOM
        run: grype sbom:sbom.cdx.json --only-fixed --fail-on high
      - name: Scan with Trivy
        run: |
          trivy image --ignore-unfixed --severity HIGH,CRITICAL --exit-code 1 \
            "$IMAGE@${{ steps.build.outputs.digest }}"
      - uses: sigstore/cosign-installer@<FULL_SHA>
      - name: Sign and attest
        run: |
          D="$IMAGE@${{ steps.build.outputs.digest }}"
          cosign sign --yes "$D"
          trivy image --format cosign-vuln -o vuln.json "$D"
          cosign attest --yes --type vuln --predicate vuln.json "$D"
          cosign attest --yes --type cyclonedx --predicate sbom.cdx.json "$D"

Three details deserve explanation. First, the SBOM is generated from the pushed digest, so what is inventoried is what is deployed. Second, both gates run before signing; a failure stops the job and the image never receives a signature, so admission will reject it. Third, the placeholder <FULL_SHA> is deliberate: after March 2026, mutable tags on a security action are an accepted risk only if you can stomach the consequences. The Trivy advisory itself recommends pinning actions to full commit SHAs.

Worked number: the freshness window

Suppose the scan attestation records the vulnerability database build time. If your policy is “scan attestation must be younger than 7 days”, a service that deploys monthly will be rejected unless a nightly job re-scans the stored SBOM and issues a fresh attestation on the same digest. The cost of that job for 200 services is 200 scans per night. If each SBOM rescan takes about 5 seconds (an assumption for illustration, since runtime depends on SBOM size and cache state), the nightly total is roughly 1,000 seconds, about 17 minutes of single-runner time. That is cheap compared with re-pulling and unpacking 200 images, which is the argument for SBOM-first rescanning.

Enforcing at Admission

Signing without enforcement is a ceremony. The control only has effect when the cluster refuses what fails verification.

Admission controller decision flow for signed container images

Figure 4: Admission decision flow. Every branch that fails ends in rejection; the pod is admitted only when digest, identity, log proof and scan freshness all pass.

Choosing an enforcement point

Kubernetes admission controllers intercept pod creation. Open-source options include the Sigstore policy-controller (which defines a ClusterImagePolicy resource), Kyverno (with verifyImages rules), and Ratify with OPA Gatekeeper. They share the same logic shown in the diagram: require the image to be referenced by digest (or resolve the tag to a digest and mutate the pod), fetch signatures and attestations, check certificate identity and issuer against policy, check log inclusion, and then evaluate attestation content.

A representative Kyverno rule looks like this (untested sketch; consult the Kyverno documentation for the schema in your version):

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: Enforce
  webhookTimeoutSeconds: 30
  rules:
    - name: verify-acme-images
      match:
        any:
          - resources: { kinds: ["Pod"] }
      verifyImages:
        - imageReferences: ["ghcr.io/acme/*"]
          mutateDigest: true
          attestors:
            - entries:
                - keyless:
                    issuer: "https://token.actions.githubusercontent.com"
                    subject: "https://github.com/acme/*/.github/workflows/release.yml@refs/tags/*"
                    rekor: { url: "https://rekor.sigstore.dev" }

Note the subject wildcard: it permits any repository in the acme organisation but pins the workflow file and the tag form. Whether that is too loose depends on who can create workflows named release.yml in your org; an attacker with write access to any repo in the organisation could produce a valid signature. Tighter policies enumerate repositories.

Failure behaviour: fail closed or fail open

Admission webhooks are a cluster dependency. If the verifier is down or cannot reach Rekor or the registry, the policy’s failure mode decides what happens: with failurePolicy: Fail, pod creation blocks (safe, but a verifier outage can stop scaling during an incident); with Ignore, unsigned pods slip through silently. For a production cluster I recommend failing closed with a highly available verifier, a short timeout, a cached trusted root, and offline bundle verification so Rekor availability is not on the critical path. The Cosign 3 bundle format helps here because the log proof travels with the signature.

Trade-offs, Gotchas, and What Goes Wrong

The scanner is part of your attack surface. The March 2026 Trivy incident, as described in the project advisory, combined a malicious release, rewritten action tags and, three days later, malicious images on Docker Hub. Exposure windows ranged from roughly three hours (the binary) to about twelve (the action), yet any pipeline that ran during them is treated as having exposed its secrets. The defences are mundane and effective: pin by digest or commit SHA, verify signatures on downloaded tools, give the scanning job read-only tokens, and keep long-lived credentials out of any job that runs third-party code. The advisory also notes that credential rotation after an earlier disclosure was not atomic, which let the attacker retain access. Rotation must be a single coordinated operation.

A signature is not a safety verdict. Cosign proves who signed what; it says nothing about whether the build was clean. If an attacker controls your release workflow, they obtain valid signatures for malicious images. This is why SLSA-style provenance, hardened runners, and branch protection around the workflow file matter, and why the identity in your policy should be as specific as possible.

Stale scans. Scanning at build time freezes knowledge at one instant. A critical CVE published tomorrow does not invalidate today’s signature. Compensate with scheduled rescans of stored SBOMs, runtime inventory (which images are actually running), and a freshness window in admission policy.

Scanner disagreement and false confidence. An empty report can mean “no vulnerabilities” or “nothing cataloged”. Sanity-check the SBOM: if a Go service image produces zero packages, you are not scanning it. Static binaries, packed executables, and software copied into the image outside a package manager are common misses.

Backport and unfixed noise. Teams that gate on raw CVSS HIGH fail on findings with no available fix and then add blanket ignores, which defeats the control. Gate on fixable findings, record accepted risks with an expiry date, and use VEX statements for justified not_affected claims.

Public transparency log exposure. Keyless signing against the public Rekor instance publishes your signing identity and repository names. For regulated or secret projects, use a private Sigstore instance or key-based signing with a KMS.

Mutable base images and rebuilds. A rebuild tomorrow can pull a newer base layer and produce a different digest from the same source commit. Reproducibility is not guaranteed unless you pin the base image digest. That makes “which commit produced this digest” a provenance question that signatures alone do not answer.

Version drift in Cosign. The jump from 2.x to 3.x changes defaults around bundles and trusted roots, and the Sigstore announcement says more flags will go in v4. Pin the Cosign version in CI and in the verifier, and test signing and verification across the exact client versions that will interact, since a signature produced in one format must be readable by the admission controller’s library.

Practical Recommendations

Start with the controls that remove the most risk per hour of effort. Pin and verify the tooling first, because every other control runs on top of it. Then make digests the unit of identity, gate on fixable HIGH and CRITICAL findings, sign by digest with a pinned workflow identity, and enforce at admission in audit mode before switching to enforce.

For a team choosing between the two scanners, my opinion is this: use Trivy if you want a single tool covering images, IaC and secrets and have limited platform engineering capacity; use Syft plus Grype if SBOMs are your system of record and you will rescan stored inventories. For high-value images, run both and treat disagreements as a signal to improve your SBOM, not as noise to suppress. That recommendation is a judgement based on tool design, not on a benchmark I ran.

A phased rollout works well. In week one, scan in report-only mode and measure the raw-to-actionable ratio. In week two, enable the fix-available gate on new builds. In week three, add signing and attestations. In week four, deploy admission policy in audit mode, review the rejections, then enforce. Keep an exception process with expiry dates so teams do not route around the control.

Checklist

  • Pin scanner binaries, GitHub Actions and base images by digest or full commit SHA; verify tool signatures before running them.
  • Give scan jobs minimal tokens; keep registry-push and signing permissions in a separate job.
  • Generate an SBOM at build time from the pushed digest and store it.
  • Gate on fixable HIGH and CRITICAL findings; record exceptions with expiry.
  • Sign the digest with Cosign keyless; attach scan and SBOM attestations.
  • Verify with exact certificate identity and issuer, never a loose regexp.
  • Rescan stored SBOMs nightly and reissue scan attestations inside the freshness window.
  • Enforce at admission, fail closed, cache the trusted root, and monitor verifier health.
  • Rehearse the incident runbook: which secrets rotate, which images to quarantine, how to list running digests.

Frequently Asked Questions

Is Trivy or Grype better for container image scanning?

Neither is universally better. Trivy bundles vulnerability, misconfiguration, secret and license scanning in one binary, which suits small teams. Grype focuses on vulnerability matching and pairs with Syft for SBOM generation, which suits SBOM-first workflows. Their results overlap strongly on distribution packages and diverge on language packages and binaries. Test both against your own images on the same day with fresh databases, and adjudicate a sample of differences before choosing or standardising.

Was Trivy compromised, and should I stop using it?

According to the project’s advisory GHSA-69fq-xp46-6×23, a malicious v0.69.4 release, rewritten trivy-action and setup-trivy tags, and malicious v0.69.5 and v0.69.6 Docker Hub images were published in March 2026 using stolen credentials. The advisory lists safe versions and remediation. Whether to keep using it is your risk call, but the lesson applies to any tool: pin by digest, verify signatures, and rotate secrets from any pipeline that ran a bad version.

How does Sigstore keyless signing work without a private key?

Cosign generates a throwaway key pair, proves the CI job’s identity to Fulcio with an OIDC token, and receives a certificate that is valid for about ten minutes. It signs the image digest and records the event in the Rekor transparency log. Verifiers check the certificate identity, the issuer and the log proof, so no long-lived key exists to steal or rotate.

What should I verify when checking a Cosign signature?

Verify three things: the certificate identity (the workflow that signed), the OIDC issuer, and transparency-log inclusion. Always pass --certificate-identity and --certificate-oidc-issuer, or their regexp variants with tight patterns. Without them, any valid Sigstore signature passes, including one made by a stranger. Also verify against a digest, not a tag, and check attestation contents and freshness where policy requires them.

Does signing an image mean it is free of vulnerabilities?

No. A signature proves provenance of the digest, not its content. Vulnerabilities disclosed after signing do not invalidate it. Combine signatures with attached scan attestations, a freshness window, regular SBOM rescans and runtime inventory so you can find affected running images when a new CVE appears. If the signing workflow itself is compromised, malicious images can carry valid signatures, so protect that workflow like a production system.

Do I need Rekor, and what if it is unavailable?

The transparency log gives public, tamper-evident evidence of signing time and identity use. With Cosign 3 bundles, the log proof is stored with the signature, so verification can happen offline against a trusted root and Rekor downtime need not block deployments. For private or air-gapped environments, run a private Sigstore stack or use KMS-backed keys and accept a different trust model.

Further Reading

By Riju — about

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *