A threat actor broke into the release pipeline of one of the most widely used open-source security tools on the planet, and the tool at the center of it was itself a vulnerability scanner. On March 19, 2026, attackers used compromised credentials to push a malicious build of Trivy, Aqua Security’s open-source container scanner, then force-pushed 76 of the 77 version tags in the official trivy-action GitHub Action to point at credential-stealing malware. Anyone who ran a pinned-by-tag Trivy scan in a CI/CD pipeline that day risked handing over cloud credentials, SSH keys, and Docker configs to an attacker instead of getting a vulnerability report. This tutorial walks through how to install, run, and automate Trivy the right way in 2026, including the specific steps that would have stopped that attack cold.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
Why Container Vulnerability Scanning Became Urgent in 2026
Container image scanning stopped being optional the moment container registries became the default distribution channel for software. Every base image pulled from Docker Hub, every language dependency baked into a layer, and every misconfigured Dockerfile is a potential entry point, and attackers know it. The container vulnerability scanning market is projected to grow from roughly $1.24 billion in 2026 to $2.87 billion by 2033, a 12.3% compound annual growth rate, according to Data Insights Market. That growth is not abstract. It tracks a real shift: security teams that used to scan images once a quarter now scan on every commit, every pull request, and every deploy.
Trivy, maintained by Aqua Security, remains the default open-source choice for most engineering teams because it scans a wide surface, container images, filesystems, git repositories, Kubernetes clusters, and infrastructure-as-code templates, from one binary. Aqua Security holds an estimated 6-9% revenue share of the broader container security market, with Wiz at 5-7% and Snyk at 3-5%, according to Market Research Future’s 2026 container security report. Those numbers describe commercial platforms built around scanning, not raw adoption, and Trivy’s open-source install base is far larger than any paid tier because it costs nothing to run.
Then came the incident that makes this tutorial different from a standard how-to. On March 19, 2026, a threat actor compromised credentials tied to the Trivy project and published a malicious Trivy v0.69.4 release across GitHub Releases, GHCR, Docker Hub, ECR Public, and the project’s own deb and rpm repositories, according to GitHub’s security advisory GHSA-69fq-xp46-6×23, tracked as CVE-2026-33634. The same actor force-pushed 76 of 77 version tags in aquasecurity/trivy-action to a credential-stealing payload and replaced all seven tags in aquasecurity/setup-trivy with malicious commits. Microsoft’s security team called it “a sophisticated CI/CD-focused supply chain attack” in a March 24, 2026 blog post. Every step in this guide assumes you understand that a scanning tool is also an attack surface, and treats version pinning and verification as a first-class step, not an afterthought.
The blast radius went beyond a narrow window of bad downloads. Because trivy-action is embedded in thousands of public and private GitHub workflows, and because the malicious code activated the moment a workflow checked out the action, the compromise behaved less like a single tainted download and more like a self-propagating supply-chain event. Reporting from The Hacker News described the breach as reaching across build pipelines that had no direct relationship to Aqua Security beyond depending on one shared GitHub Action. That is the structural risk of the modern software supply chain: a single compromised credential at one vendor can reach thousands of downstream organizations within hours, long before most security teams even know an incident is underway.
Prerequisites: Tools, Versions, and Permissions You Will Need
Before starting, gather the following. None of it is exotic, but getting versions right matters more here than in a typical tutorial given the incident history above.
- A Linux, macOS, or WSL2 environment with a shell (bash or zsh) and
curlinstalled. - Docker Engine 27.x or later, or Podman 5.x, for pulling and building container images locally.
- Trivy v0.71.2 (released June 19, 2026) or a version released after Trivy v0.70.0 (April 17, 2026), which was the first release published after the March 2026 supply-chain incident was fully remediated. Do not install anything from the affected window.
- Git 2.4x or later, for scanning source repositories and Infrastructure-as-Code templates.
- A GitHub account and repository if you plan to wire scanning into GitHub Actions.
- kubectl and Helm 3.x if you intend to deploy the Trivy Operator to a Kubernetes cluster.
- Roughly 500 MB of free disk space for the vulnerability database that Trivy downloads and caches locally.
- Outbound network access to GitHub Container Registry (GHCR), since Trivy pulls its vulnerability database from
ghcr.io/aquasecurity/trivy-dbby default.
You do not need a paid Aqua Security account for anything in this guide. Every command below uses the free, open-source Trivy CLI. If your organization later wants centralized dashboards, policy enforcement across teams, or the Trivy Operator’s admission-control features tied into a commercial console, that upgrade path exists, but it is not a prerequisite for scanning your first image today.
Step 1: Confirm You Are Not Running a Compromised Trivy Build
If your team already had Trivy installed before reading this, check what you are actually running before you scan anything else. The malicious release window ran from roughly 18:24 UTC on March 19, 2026 to 01:36 UTC on March 23, 2026 for affected Docker Hub tags, according to Docker’s incident writeup. The compromised trivy-action GitHub Action window was narrower, about 12 hours, per StepSecurity’s analysis of the breach.
# Check the exact Trivy binary version currently installed
trivy --version
# Check which trivy-action tag your workflows reference
grep -r "aquasecurity/trivy-action" .github/workflows/
# Check which setup-trivy tag your workflows reference
grep -r "aquasecurity/setup-trivy" .github/workflows/
The table below lists the versions confirmed compromised versus the versions confirmed safe, based on Aqua Security’s own advisory and GitHub’s security advisory GHSA-69fq-xp46-6×23.
| Component | Compromised / Affected | Confirmed Safe | Exposure Window |
|---|---|---|---|
| Trivy binary | v0.69.4 | v0.69.2, v0.69.3, and every release from v0.70.0 onward | ~3 hours (GitHub-hosted channels), longer on some mirrors |
| trivy-action | 76 of 77 version tags, force-pushed | v0.35.0 (verify by commit SHA, not tag alone) | ~12 hours |
| setup-trivy | All 7 version tags | v0.2.6 | Same incident window |
| Docker Hub aquasec/trivy | Tags 0.69.4, 0.69.5, 0.69.6, and latest | Any tag pulled after March 23, 2026, 01:36 UTC | March 19 18:24 UTC to March 23 01:36 UTC |
If your CI logs show a run during any of these windows using the affected tags, treat every secret that pipeline had access to as compromised: cloud credentials, SSH keys, registry tokens, and Docker configuration files, exactly as Docker’s own advisory recommends for affected pulls. Rotate first, investigate second. This is also a good moment to check your broader supply chain risk posture beyond just Trivy, since the same force-push technique works against any GitHub Action pinned by a mutable tag rather than an immutable commit SHA.
Step 2: Install Trivy With a Pinned, Verified Version
Skip the “curl the install script and pipe to bash” pattern that most Trivy tutorials still show. It works, but it also means you are trusting whatever the script resolves as “latest” at execution time, which is precisely the trust model the March 2026 attack exploited. Pin an exact version and verify the checksum instead.
# Pin to a specific, known-good release (Linux amd64 example)
TRIVY_VERSION="0.71.2"
curl -LO "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
curl -LO "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_checksums.txt"
# Verify the checksum before extracting
sha256sum --ignore-missing -c "trivy_${TRIVY_VERSION}_checksums.txt"
# Extract only after the checksum check passes
tar -xzf "trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" trivy
sudo mv trivy /usr/local/bin/trivy
trivy --version
On macOS, Homebrew works fine for local development, but pin the formula version explicitly rather than always pulling whatever brew upgrade resolves to on a given day, since Homebrew formulas can and do get updated mid-workday. For any machine that touches production credentials, the manual checksum-verified install above is worth the extra ninety seconds. This single habit, verify before you trust, is the same lesson that applies to supply-chain worms hitting package registries like npm: the compromise rarely announces itself, it just quietly ships in the next routine update.
Step 3: Run Your First Trivy Container Image Scan
With a verified Trivy binary installed, scanning your first image takes one command. Trivy downloads its vulnerability database on first run, roughly 500 MB, and caches it locally so subsequent scans are fast.
# Scan a public image for known vulnerabilities
trivy image nginx:1.27-alpine
# Scan a locally built image before you push it anywhere
docker build -t myapp:latest .
trivy image myapp:latest
# Output as JSON for downstream processing
trivy image --format json --output results.json myapp:latest
A typical scan output looks like this, showing the target, the vulnerability count by severity, and specific CVE identifiers with fixed versions where one exists:
myapp:latest (alpine 3.20.3)
========================================
Total: 14 (UNKNOWN: 0, LOW: 6, MEDIUM: 5, HIGH: 2, CRITICAL: 1)
Library | Vulnerability | Severity | Status | Installed | Fixed
openssl | CVE-2026-4XXX | CRITICAL | fixed | 3.2.1-r0 | 3.2.2-r0
libcurl | CVE-2026-2XXX | HIGH | fixed | 8.9.0-r1 | 8.9.1-r0
The “Status” column matters as much as severity. A CRITICAL finding with no fixed version yet is a risk to document and monitor, not something you can patch your way out of today. A HIGH finding with a fixed version available and unapplied is the one that should block your next deploy.
Step 4: Read the Trivy Report and Prioritize by Severity
Raw vulnerability counts are close to useless without context. A base image can easily surface forty or fifty LOW and MEDIUM findings, most inherited from operating system packages nobody on your team touches directly. Trivy’s default severity buckets, UNKNOWN, LOW, MEDIUM, HIGH, and CRITICAL, map to CVSS score ranges, and the practical move is to triage top-down rather than trying to clear every finding to zero.
Start by filtering to only HIGH and CRITICAL findings, since those are the ones with realistic exploitation paths and the ones auditors and compliance frameworks actually ask about:
# Show only HIGH and CRITICAL severity findings
trivy image --severity HIGH,CRITICAL myapp:latest
# Show only vulnerabilities that have a fix available
trivy image --severity HIGH,CRITICAL --ignore-unfixed myapp:latest
The --ignore-unfixed flag is one of the most underused options in Trivy. It strips out every finding where no patched version exists yet, which is noise you cannot act on today anyway. Combining severity filtering with unfixed-vulnerability suppression typically cuts a raw finding list by 60-80% and leaves a list your team can actually work through in a sprint instead of a backlog nobody opens again.
Step 5: Fail Builds Automatically on Critical and High CVEs
Scanning without gating accomplishes almost nothing beyond generating a report someone reads once. The real value comes from wiring Trivy into your build pipeline so it can block a merge or a deploy when it finds something serious. Trivy’s --exit-code flag makes this straightforward to script.
# Exit with code 1 if any CRITICAL vulnerability is found (fails CI)
trivy image --severity CRITICAL --exit-code 1 --ignore-unfixed myapp:latest
# Exit code 0 always, but still print HIGH/CRITICAL for visibility
trivy image --severity HIGH,CRITICAL --exit-code 0 myapp:latest
A common rollout pattern is staged enforcement: start with --exit-code 0 so the scan runs and reports without blocking anything, let the team see two or three weeks of findings, then flip to --exit-code 1 on CRITICAL only, and only later extend the gate to HIGH severity once the backlog of pre-existing findings is under control. Flipping straight to a hard gate on day one against an image with years of accumulated dependencies is the fastest way to get the security gate disabled by an annoyed engineering team within a week.
Step 6: Scan Filesystems, Git Repositories, and IaC Templates
Container images are only one artifact type Trivy handles. The same binary scans local filesystems, git repositories (including remote ones by URL), and infrastructure-as-code templates for misconfigurations, which matters because a perfectly patched container running on a misconfigured Terraform-provisioned S3 bucket or an overly permissive Kubernetes RBAC role is still an exposed system.
# Scan a local project directory (filesystem mode)
trivy fs --severity HIGH,CRITICAL .
# Scan a remote git repository directly by URL
trivy repo https://github.com/your-org/your-app
# Scan Terraform, CloudFormation, Kubernetes manifests, and Dockerfiles for misconfigurations
trivy config --severity HIGH,CRITICAL ./infra/
trivy config checks against built-in policy rules covering common misconfiguration classes: containers running as root, missing resource limits, world-readable secrets in Kubernetes manifests, overly broad security group rules in Terraform, and S3 buckets without encryption or public-access blocks. If your organization is running Kubernetes on AWS, pairing this misconfiguration scan with proper EKS autoscaling and node configuration closes a gap that image scanning alone never touches, since a scaling policy that provisions nodes with excessive IAM permissions is a misconfiguration problem, not a vulnerability one.
Step 7: Turn On Secret Scanning to Catch Leaked Credentials
Secret scanning runs by default alongside vulnerability and misconfiguration scanning in modern Trivy versions, but it is worth understanding what it actually catches: hardcoded AWS access keys, private key files accidentally copied into an image layer, API tokens left in environment variable defaults, and database connection strings baked into a Dockerfile ENV instruction.
# Run vulnerability + secret scanning together (default behavior)
trivy image myapp:latest
# Run secret scanning only, useful for a fast pre-commit check
trivy fs --scanners secret .
# Use a custom secret-detection ruleset for internal token formats
trivy fs --scanners secret --secret-config trivy-secret.yaml .
A GitGuardian analysis of the March 2026 Trivy incident made a point worth internalizing here: secret exposure from a compromised CI/CD tool spreads faster than a typical code vulnerability because the stolen credentials grant immediate, working access, no exploit development required. A leaked AWS key found by a secret scanner before a merge is a five-minute fix. The same key discovered by an attacker in a compromised pipeline log is an incident.
Step 8: Generate a Software Bill of Materials (SBOM)
An SBOM is an inventory of every package, library, and dependency inside an artifact, and it has gone from a nice-to-have to a procurement requirement for organizations selling into regulated industries or the federal government. Trivy generates SBOMs in both CycloneDX and SPDX formats natively.
# Generate a CycloneDX SBOM for a container image
trivy image --format cyclonedx --output sbom.cdx.json myapp:latest
# Generate an SPDX SBOM instead
trivy image --format spdx-json --output sbom.spdx.json myapp:latest
# Scan an existing SBOM file for vulnerabilities (useful for third-party artifacts)
trivy sbom sbom.cdx.json
Keep generated SBOMs alongside your build artifacts, not just in a scan report that gets overwritten on the next run. When a new CVE drops for a library you have never heard of, having a searchable archive of SBOMs means you can answer “are we affected” in minutes by grepping historical SBOMs instead of re-scanning every image you have ever shipped.
Step 9: Check Open-Source License Compliance
License scanning gets far less attention than vulnerability scanning, but it catches a different category of risk: a transitive dependency licensed under AGPL or GPL pulled into a proprietary product, which is a legal exposure rather than a security one. Trivy classifies detected licenses and can flag ones that fall outside an allowed list.
# Scan for license issues, flagging restrictive/forbidden licenses
trivy image --scanners license --license-full myapp:latest
The --license-full flag runs a deeper classification pass on the license text itself rather than relying only on package metadata, which catches cases where a package’s declared license does not match the actual license file bundled inside it, a mismatch that happens more often than most engineering teams assume.
Step 10: Wire Trivy Into GitHub Actions Without Repeating 2026’s Mistake
This is the step that the March 2026 incident makes non-negotiable. Most Trivy GitHub Actions tutorials show pinning by a version tag like @0.35.0. Tags are mutable. Anyone with write access to the action’s repository, including an attacker with stolen credentials, can force-push a tag to point at different code without the version number ever changing. Pin by commit SHA instead.
name: Container Security Scan
on: [pull_request]
permissions:
contents: read
security-events: write
jobs:
trivy-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
# Pinned by commit SHA, not by mutable tag
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@57a97c7e37b3d5e6d0f9c67b0f6c7c8d5f4a3b21
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
Pinning by SHA instead of tag is the single change that would have neutralized the March 2026 attack for any team that had already adopted it, since the malicious commits still required a new SHA that would not match what was already recorded in the workflow file. Combine that with GitHub’s Dependabot version-update alerts on your Actions dependencies so you get a pull request, not a silent auto-update, whenever a pinned action has a new release worth reviewing. This same SHA-pinning discipline belongs in every workflow that pulls third-party actions, not just security scanners, a lesson that also applies directly to the npm ecosystem after incidents like the ChainDrop npm supply-chain worm.
Step 11: Deploy Trivy Operator for Continuous Kubernetes Scanning
A one-time scan at build time only tells you what your image looked like the day you built it. New CVEs get disclosed against packages already running in production every week, which is why continuous scanning inside the cluster matters. The Trivy Operator watches Kubernetes workloads and automatically scans running images, generating VulnerabilityReport custom resources you can query with kubectl.
# Add the Aqua Security Helm repository
helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm repo update
# Install Trivy Operator into its own namespace
helm install trivy-operator aqua/trivy-operator \
--namespace trivy-system \
--create-namespace \
--set="trivy.ignoreUnfixed=true"
# List vulnerability reports generated for running workloads
kubectl get vulnerabilityreports --all-namespaces
# Inspect a specific report in detail
kubectl describe vulnerabilityreport -n
This closes the gap between “scanned at build time” and “actually secure right now,” because a cluster running for eight months accumulates newly disclosed vulnerabilities against images that never changed. If your cluster already runs on EKS with autoscaling handled through Karpenter, the operator scans nodes and workloads as they scale up and down without any extra configuration, since it watches the Kubernetes API rather than a fixed node list. Teams that have already gone through broader Kubernetes security hardening will find the Trivy Operator slots in as one more control alongside RBAC restrictions, network policies, and pod security standards, rather than replacing any of them.
Step 12: Automate Reporting, Alerts, and Remediation Tickets
Scan results that live only in a CI log get read once, if that. The last step in the setup is routing findings to wherever your team actually works, whether that is Slack, a ticketing system, or a dashboard someone checks daily.
# Convert Trivy JSON output into a simple Slack-friendly summary
trivy image --format json myapp:latest | \
jq -r '.Results[].Vulnerabilities[]? | select(.Severity=="CRITICAL") |
"\(.VulnerabilityID): \(.PkgName) \(.InstalledVersion) -> \(.FixedVersion // "no fix yet")"'
# Post a scan summary to a Slack webhook
CRITICAL_COUNT=$(trivy image --format json myapp:latest | \
jq '[.Results[].Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length')
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"Trivy scan for myapp:latest found ${CRITICAL_COUNT} CRITICAL vulnerabilities.\"}" \
"$SLACK_WEBHOOK_URL"
For teams already running a broader vulnerability management workflow, feed Trivy’s JSON output into whatever ticketing automation already tracks remediation SLAs elsewhere, rather than standing up a second parallel tracking system just for container findings. Consistency in how findings get triaged matters more than which specific tool renders the dashboard, a principle covered in more depth in a dedicated vulnerability management program guide.
Common Pitfalls When Scanning Containers With Trivy
Most teams that adopt Trivy hit the same handful of mistakes in the first month. Watching for these ahead of time saves real debugging time later.
- Pinning GitHub Actions by tag instead of commit SHA. As the March 2026 incident showed, a tag is not a guarantee the code behind it has not changed. Pin by SHA for anything touching CI/CD credentials.
- Scanning only the final image, never the base image separately. A vulnerability introduced by an upstream base image (like
node:20-alpine) will keep reappearing in every downstream build until the base image itself gets patched or swapped. - Ignoring the vulnerability database cache location in CI. Without caching the ~500 MB database between CI runs, every single build re-downloads it, adding minutes to build time and load to GHCR unnecessarily.
- Setting a hard exit-code gate on day one against a legacy image. A codebase with years of accumulated dependencies can surface hundreds of findings instantly, and a sudden hard gate on all of them gets the check disabled by frustrated engineers within days.
- Treating “0 vulnerabilities found” as “the image is secure.” Trivy only reports what is in its vulnerability database. A zero-day with no CVE assigned yet, or a business-logic flaw in your own application code, will not show up in a dependency scan no matter how clean the report looks.
- Forgetting to scan IaC and Kubernetes manifests alongside images. A perfectly scanned image deployed with a Kubernetes Pod spec that runs as root and mounts the host filesystem is still an exposed workload.
Trivy vs Grype vs Docker Scout vs Snyk: How the Tools Compare
Trivy is not the only container scanner worth knowing, and picking the right tool (or combination of tools) depends on whether you need a free CLI, native Docker Desktop integration, or a full commercial platform with prioritization and fix automation built in.
| Tool | Maintainer | Open Source | Scan Scope | Pricing |
|---|---|---|---|---|
| Trivy | Aqua Security | Yes, fully free CLI | Images, filesystems, git repos, IaC, Kubernetes, SBOM, secrets, licenses | Free (paid Aqua platform available for enterprise dashboards) |
| Grype | Anchore | Yes, fully free CLI | Images and filesystems, SBOM-driven scanning | Free (paid Anchore Enterprise available) |
| Docker Scout | Docker, Inc. | No, built into Docker Desktop/CLI | Images, with policy checks tied to Docker Hub | Free tier included with Docker; paid team tiers for org-wide policy |
| Snyk Container | Snyk | No, SaaS with CLI | Images, base image recommendations, developer-first CI/CD integration | Free tier; Team plan from $98/mo, per Snyk’s 2026 pricing |
In practice, plenty of teams run more than one of these, not out of redundancy paranoia but because each tool’s vulnerability database updates on a slightly different cadence and pulls from different sources, so cross-referencing catches the rare case where one database lags behind on a specific CVE. Aqua Security itself, Trivy’s maintainer, sits in a similar space to Wiz and Microsoft Defender for Cloud when it comes to full commercial platforms, a comparison covered in more detail in the Wiz vs Prisma Cloud vs Defender for Cloud breakdown if you are evaluating a paid CNAPP rather than a standalone open-source scanner.
Troubleshooting Trivy Scans: Common Errors and Fixes
These are the errors and unexpected behaviors that show up most often once Trivy is running in real pipelines rather than a local test.
- “failed to download vulnerability DB” error. Usually a firewall or proxy blocking outbound access to
ghcr.io. Confirm the CI runner or corporate network allows outbound HTTPS to GHCR, or host a mirrored copy of the vulnerability DB internally for air-gapped environments. - Scan takes far longer in CI than locally. The DB is re-downloading every run because the cache directory (
~/.cache/trivy) is not persisted between CI jobs. Add it to your CI cache configuration. - Same image, different results on two machines. The vulnerability database updates continuously. Two scans run hours apart against an identical image can legitimately return different findings as new CVEs get published. Pin the DB version with
--skip-db-updateif you need reproducible results for a specific audit. - “unknown flag” errors after upgrading Trivy. Flag names occasionally change between major versions. Check the release notes for the specific version you upgraded to before assuming a scripting bug.
- Scan reports vulnerabilities in a language your app does not use. Trivy scans every layer, including build-stage layers in a multi-stage Dockerfile if they were not properly discarded. Confirm your final
FROMstage only carries runtime dependencies, not the full build toolchain. - Rate-limited pulling from Docker Hub during scans. Anonymous Docker Hub pulls are rate-limited. Authenticate with a Docker Hub account, or better, pull from a private registry mirror for CI workloads.
- Secret scanning flags false positives on test fixtures. Add a
.trivyignorefile excluding known test fixture paths, or use inline#trivy:ignorecomments for specific lines you have verified are safe. - Trivy Operator shows no VulnerabilityReports after install. Check that the operator’s service account has RBAC permission to list pods across the target namespaces, and confirm workloads are actually running (the operator scans live pods, not just deployment manifests).
Advanced Tips for Production-Grade Scanning Pipelines
Once the basics are running reliably, a few refinements separate a scanning setup that generates noise from one that actually reduces risk. Use a .trivyignore file to suppress specific CVEs your team has formally accepted (with an expiration date noted in a comment, so accepted risks get periodically re-reviewed rather than silently ignored forever). Run trivy image --list-all-pkgs when auditing a specific incident, since it lists every package Trivy detected, patched or not, which is useful for confirming exactly what shipped in a given build. For multi-architecture builds, scan each architecture variant separately, since ARM64 and AMD64 builds of the same image can pull slightly different base layers and carry different vulnerabilities. And if your organization already runs a commercial CSPM or CNAPP platform, check whether it can ingest Trivy’s SARIF or JSON output directly rather than duplicating scan coverage with a second paid tool doing the same job.
Finally, treat the vulnerability-database update cadence as part of your threat model, not an implementation detail. Trivy pulls fresh CVE data multiple times a day. A scan that passed cleanly this morning can fail this afternoon purely because a new CVE got published, with no code change on your side at all. Build monitoring, not just gating, into the pipeline: a scheduled nightly re-scan of already-deployed images catches this drift even when nobody pushes new code.
One more habit worth adopting from the March 2026 incident directly: separate the credentials your scanning pipeline uses from the credentials your deployment pipeline uses. Investigators found that the attackers behind the Trivy compromise leveraged access from a prior, incompletely remediated incident rather than breaking in fresh, according to Microsoft’s writeup. A scanning job that only needs read access to pull an image should never hold write access to a production registry or a cloud account. Scope every CI token down to the minimum the job actually needs, and rotate those tokens on a schedule instead of leaving them valid indefinitely. A compromised scanner with narrow permissions is an inconvenience. A compromised scanner with broad permissions is the headline.
Complete Working Project: A Hardened Image-Scanning Pipeline
Putting every step together, here is a minimal but production-ready project layout for a repository that builds a container image, scans it, generates an SBOM, and blocks the merge on CRITICAL findings.
your-project/
├── Dockerfile
├── .trivyignore
├── trivy-secret.yaml
└── .github/
└── workflows/
└── security-scan.yml
The workflow file combines everything covered above into one pipeline: build, verify the Trivy action by SHA, scan for vulnerabilities and secrets, generate an SBOM as a build artifact, and gate the merge on CRITICAL severity only, with HIGH severity reported but non-blocking during the rollout period.
name: Hardened Container Scan
on: [pull_request]
permissions:
contents: read
security-events: write
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Vulnerability + secret scan (blocking on CRITICAL)
uses: aquasecurity/trivy-action@57a97c7e37b3d5e6d0f9c67b0f6c7c8d5f4a3b21
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'table'
severity: 'CRITICAL'
ignore-unfixed: true
exit-code: '1'
- name: Vulnerability scan (reporting only, HIGH)
if: always()
uses: aquasecurity/trivy-action@57a97c7e37b3d5e6d0f9c67b0f6c7c8d5f4a3b21
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-high.sarif'
severity: 'HIGH'
exit-code: '0'
- name: Upload SARIF to Security tab
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-high.sarif'
- name: Generate SBOM artifact
run: trivy image --format cyclonedx --output sbom.cdx.json myapp:${{ github.sha }}
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.cdx.json
This setup gives a team everything needed for a real production rollout: a hard block on the most dangerous findings, visibility into everything else through GitHub’s Security tab, an audit-ready SBOM attached to every build, and, critically, every third-party action pinned by commit SHA so the pipeline itself cannot be silently redirected the way the March 2026 attack redirected thousands of others.
Frequently Asked Questions
Is Trivy still safe to use after the March 2026 supply-chain attack?
Yes, provided you run a version released after Trivy v0.70.0 (April 17, 2026) or a manually verified build of the confirmed-safe v0.69.2/v0.69.3 releases, and pin any GitHub Actions by commit SHA rather than by tag. Aqua Security stated it removed all malicious artifacts from affected registries and channels following the incident.
What is the difference between Trivy and Grype?
Both are free, open-source CLI scanners. Trivy scans a broader range of artifact types out of the box, including Kubernetes clusters, IaC templates, secrets, and licenses, while Grype (maintained by Anchore) focuses more narrowly on image and filesystem vulnerability scanning built around SBOM generation with Anchore’s companion tool, Syft.
Do I need a paid Aqua Security account to use Trivy?
No. The Trivy CLI, the vulnerability database, secret scanning, license scanning, SBOM generation, and the Trivy Operator for Kubernetes are all free and open source. A paid Aqua Security platform exists for organizations that want centralized dashboards, policy enforcement, and admission control across many teams, but nothing in this tutorial requires it.
How often should I re-scan container images that are already deployed?
Daily, at minimum, for anything internet-facing. New CVEs get published against existing packages continuously, so an image that scanned clean at build time can become vulnerable weeks later with no code change on your part. This is exactly what the Trivy Operator automates for running Kubernetes workloads.
Can Trivy scan images that are not in a public registry?
Yes. Trivy scans locally built images (via the Docker or containerd daemon), images in private registries with authentication configured, and images passed as a tarball with trivy image --input, so air-gapped or private-registry environments are fully supported.
Why does my Trivy scan show different vulnerability counts each time?
Trivy’s vulnerability database updates multiple times a day as new CVEs get published and disclosed. Running the same scan hours apart against an unchanged image can surface new findings purely from database updates. Use --skip-db-update with a pinned database snapshot if you need reproducible results for a specific compliance audit.
Should I pin GitHub Actions by version tag or commit SHA?
Commit SHA. Version tags are mutable and can be force-pushed to point at different code, which is exactly the mechanism attackers used in the March 2026 Trivy ecosystem compromise to redirect 76 of 77 tags in trivy-action to a credential-stealing payload. A commit SHA cannot be silently redirected the same way.
Does Trivy replace the need for runtime security tools like Falco?
No. Trivy scans static artifacts, images, filesystems, IaC, and configuration, for known vulnerabilities and misconfigurations. It does not monitor running process behavior. A runtime security tool that watches kernel syscalls, commonly built on eBPF, catches a different category of threat: unexpected behavior inside an already-running container, such as an unauthorized shell spawning inside a production pod.


