Skip to content
Security Notes

Trivy in CI: container scanning without the noise

By Elias Lankinen12 min read

I have solid, well-dated sourcing and four verified images. Writing the post now.

The Wall of Red

A developer opens a pull request to bump a base image from python:3.11-slim to a patched build. The CI pipeline runs, and a few minutes later the security stage comes back with a verdict: 214 vulnerabilities, 39 of them marked CRITICAL. The merge button turns an accusatory shade of red. The developer scrolls. Most of the findings are in operating-system libraries they have never called, several have no available fix, and a handful are flagged CRITICAL for a denial-of-service bug in a parser the container never touches. Somewhere in that list there might be a genuinely dangerous, actively exploited flaw. There is no obvious way to tell which one. This is the paradox of container scanning in 2026. The tooling has become excellent and nearly free, yet the output has become a source of fatigue rather than clarity. Only a small fraction of catalogued vulnerabilities are ever exploited in the wild, with most estimates landing between two and seven percent according to EPSS research summarised by Splunk. A scanner that reports everything with equal urgency is technically correct and practically useless. The interesting problem is not how to find vulnerabilities. It is how to find the ones that matter without drowning the ones that do not.

Source: Mohamed Hassan, Wikimedia Commons (CC BY-SA 4.0)
Source: Mohamed Hassan, Wikimedia Commons (CC BY-SA 4.0)

What Trivy actually does

Trivy, pronounced "tri" as in trigger plus "vy" as in envy, is an open-source scanner published by Aqua Security. It began as one engineer's side project and grew into the most-starred security scanner on GitHub, with roughly 36,500 stars and more than 500 contributors, and it became a CNCF project along the way. It is the default scanner baked into Harbor, the CNCF container registry, and it ships as a first-class integration for GitHub Actions, GitLab CI, and AWS Security Hub. Its appeal in continuous-integration pipelines, the automated build-and-test systems that run on every code change, is mundane and decisive: it is a single binary with no database server to run and no middleware to babysit. You point it at a container image, a filesystem, a Git repository, or a running Kubernetes cluster, and it reports back on vulnerabilities, misconfigurations, leaked secrets, and license issues. The vulnerability scan works by decomposing an image into its constituent packages and matching each one against advisory databases. Trivy is deliberate about where it looks. For operating-system packages it consults only vendor-specific sources, such as Red Hat's OVAL feed, the Debian Security Tracker, and the Alpine secdb, according to the Trivy documentation. For application dependencies it draws on ecosystem databases like the GitHub Advisory Database, the Go Vulnerability Database, and the Ruby Advisory Database. This split matters more than it first appears, and it is the root of a great deal of the noise.

Source: Igor Ovsyannykov, Wikimedia Commons (CC0).jpg)
Source: Igor Ovsyannykov, Wikimedia Commons (CC0)

Where the noise comes from

The single biggest source of phantom findings is the gap between a public vulnerability database and what a specific Linux distribution has actually done about it. When a bug is found in an upstream project, the National Vulnerability Database records it against that project's version numbers. But distributions like Red Hat and Debian frequently backport the fix, meaning they patch the flaw while keeping the old version string. A naive scanner sees openssl 1.1.1 matched against a CVE that says "affects 1.1.1" and cries foul, even though the distribution patched that exact build months ago. Trivy avoids this trap by trusting vendor advisories over the raw NVD entry for OS packages, which is why it produces relatively few false positives on Alpine and RHEL. It also prefers the vendor's own severity rating over the NVD's CVSS score, on the reasonable theory that Red Hat understands the risk of a Red Hat package better than a central database does. The second source of noise is the sheer volume of unfixed vulnerabilities. A base image can carry dozens of known flaws for which no patch yet exists anywhere. Reporting them is honest, but it is rarely actionable: there is nothing to upgrade to. A developer cannot fix what the upstream maintainer has not fixed. The third source is severity inflation. The Common Vulnerability Scoring System, or CVSS, rates theoretical worst-case impact under ideal conditions for an attacker. It says nothing about whether the vulnerable code path is reachable in your image, whether the service is exposed, or whether anyone has ever written a working exploit. A CVSS 9.8 in a library your application loads but never invokes is, in practice, a CVSS of nearly zero. The score cannot know that. This is the misconception worth naming directly: a CRITICAL rating is a statement about a vulnerability's ceiling, not about your actual exposure. Treating the two as the same thing is what generates the wall of red.

Turning down the volume

The good news is that most of the noise can be removed with configuration rather than judgement calls, and the flags involved are worth knowing precisely. The blunt first move is severity filtering. Passing --severity HIGH,CRITICAL tells Trivy to report only findings at or above a chosen level, which collapses a 214-item list dramatically. It is crude, because a HIGH you can exploit beats a CRITICAL you cannot, but it is a reasonable starting gate for a pipeline that has to fail or pass. More surgical is --ignore-unfixed, which suppresses vulnerabilities that have no vendor patch. This is a shorthand for a richer mechanism, --ignore-status, which lets you exclude findings by their remediation state: fixed, affected, will_not_fix, fix_deferred, or end_of_life, per the filtering documentation. Filtering out will_not_fix and affected items focuses the report on work a developer can actually do today, at the cost of hiding risk that has no remedy but is still real. That is a genuine tradeoff, not a free win, and it should be a deliberate choice rather than a default nobody remembers setting. Then there is --detection-priority, which defaults to precise. In precise mode Trivy declines to flag OS-managed packages on the strength of upstream advisories alone and skips packages whose versions it cannot pin down, minimising false positives. The alternative, comprehensive, casts a wider net and accepts more noise in exchange for catching things precise mode would miss. Most teams should leave it on precise and reach for comprehensive only during a focused audit. There is one gotcha that undermines all of this. By default, Trivy exits with status code 0 even when it finds critical vulnerabilities. In a CI pipeline, a zero exit code means success. If you forget to pass --exit-code 1, your scan will dutifully print a screen of CRITICALs and then let the pipeline go green and merge anyway. The scanner is doing exactly what you told it to do, which is report; making the build fail on findings is a separate, explicit instruction. A great many "why did that vulnerable image ship?" post-mortems end at this single missing flag.

Source: Micke, Wikimedia Commons (CC BY-SA 3.0)
Source: Micke, Wikimedia Commons (CC BY-SA 3.0)

From ignore lists to VEX

Some findings survive every filter and still do not apply to you. Maybe you have assessed a CVE and concluded the vulnerable function is never reached. The old way to silence it is a .trivyignore file: a plain text list of vulnerability IDs, one per line, optionally with an expiry date in the form CVE-2019-14697 exp:2027-01-01. The trouble with a bare ignore list is that it rots. An entry with no comment and no expiry becomes exactly the place where real risk hides, because nobody remembers why it is there or whether it still holds. Trivy's newer .trivyignore.yaml format addresses this by letting each entry carry structure: an expired_at date, a statement explaining the reasoning, and paths or purls that scope the exception to specific files or packages rather than silencing a CVE everywhere at once. Auditing that file quarterly, and treating an expired justification as a finding in its own right, is what keeps a suppression list from quietly becoming a liability. The more durable answer is VEX, short for Vulnerability Exploitability eXchange. A VEX document is a machine-readable statement about whether a product is actually affected by a given vulnerability, and if not, why. Instead of a raw ignore entry, a VEX statement records a status such as not_affected together with a standardised justification like vulnerable_code_not_in_execute_path. Trivy can consume OpenVEX, CycloneDX, and CSAF VEX documents and filter its output accordingly. The difference between an ignore list and VEX is portability of reasoning. VMware's Tanzu team, for example, publishes VEX data with its application catalog so that downstream users can automatically suppress the false positives the vendor has already investigated, rather than each team re-litigating the same CVE in isolation. A .trivyignore file says "trust me." A VEX statement says "here is the claim, here is the justification, and any tool can check it." As software bills of materials become a compliance expectation, that structured, transferable justification is the format that survives an audit.

Prioritise by exploitability, not severity

Filtering removes what does not apply. Prioritisation orders what remains, and this is where the "without the noise" ambition either succeeds or fails. The first reference point is the Known Exploited Vulnerabilities catalog maintained by the United States Cybersecurity and Infrastructure Security Agency, or CISA. The KEV catalog is a curated list of vulnerabilities with confirmed, observed exploitation in the wild. It is deliberately conservative: a CVE only lands there when there is evidence attackers are actually using it. It grew from 1,239 entries at the end of 2024 to 1,484 at the end of 2025, a roughly 20 percent increase, with CISA adding new entries several times a week. If a finding in your image appears in the KEV catalog, it stops being theoretical. That is your top of the queue. The second reference point is EPSS, the Exploit Prediction Scoring System, maintained by FIRST, the global forum of incident response teams. EPSS is a machine-learning model that assigns every CVE a probability between 0 and 1 that it will be exploited in the next 30 days. Where CVSS asks "how bad could this be," EPSS asks "how likely is anyone to actually try." The two answers often diverge sharply, and the divergence is the whole point: a CVSS 9.8 with an EPSS score of 0.001 is a bug that looks terrifying and that essentially nobody is exploiting. Stacking these gives a defensible order. Confirmed exploitation, from KEV, comes first. High predicted probability, from EPSS, comes next. Raw CVSS severity, the thing scanners shout about by default, comes last, as a tiebreaker rather than the headline. This inversion is the single most effective cure for alert fatigue, and it reflects a broader industry shift, described by Palo Alto Networks, away from severity-driven patching and toward likelihood-and-context-driven prioritisation. Runtime context, such as whether a package is actually loaded and whether the workload is internet-facing, narrows the field even further, though most of that lives outside the scanner itself.

Source: Cybersecurity and Infrastructure Security Agency, Wikimedia Commons (public domain)
Source: Cybersecurity and Infrastructure Security Agency, Wikimedia Commons (public domain)

The scanner is also attack surface

There is a darker lesson about container scanning in CI, and it arrived in March 2026 when Trivy itself became the weapon. The aquasecurity/trivy-action is the wrapper that lets a GitHub Actions workflow invoke Trivy in a single line. Most teams reference it by a version tag, for example aquasecurity/trivy-action@0.28.0. The catch, which almost nobody thinks about, is that a Git tag is mutable. Anyone with write access to the repository can force-push a tag to point at different code, and the version pages, release notes, and metadata all continue to look untouched. That is exactly what happened. An attacker with repository privileges force-pushed the release tags to point at a poisoned entrypoint.sh that prepended roughly 105 lines of malicious code ahead of the real scanner logic, as CrowdStrike reconstructed it. The exact count varies slightly between analyses: Socket, reported by The Hacker News, counted 75 of 76 tags compromised, while CrowdStrike put it at 76 of 77, with only version 0.35.0 left clean. The precise number is disputed; the scope, nearly every published version, is not. The payload was a credential stealer. On GitHub-hosted runners it scraped process memory for secrets; on self-hosted runners it harvested SSH keys, cloud credentials for AWS, Google Cloud, and Azure, Kubernetes configs, Docker credentials, database passwords, Terraform variables, and even cryptocurrency wallet keys. It encrypted the haul with AES-256 and shipped it to a typosquatted domain, scan.aquasecurtiy[.]org, note the transposed letters. If that failed and a GitHub token was available, it created a public repository and uploaded the stolen data there instead. Then, crucially, it ran the real Trivy scan so the workflow succeeded and nothing looked amiss. A related compromise of the Trivy binary version 0.69.4 dropped a second-stage script that polled a command-and-control server roughly every 50 minutes. The irony is total. A tool teams adopted to protect their pipelines became the mechanism for looting them. The defensive lesson is concrete and applies far beyond Trivy: pin third-party GitHub Actions to a full commit SHA rather than a version tag. aquasecurity/trivy-action@<40-character-hash> cannot be retroactively repointed, because the hash is the content. Aqua's remediation guidance was to upgrade to the clean 0.35.0 action, rotate every pipeline secret, and pin by SHA going forward. The episode is a reminder that your CI supply chain is production infrastructure, and every action you invoke runs with access to your secrets.

What to watch

The trajectory of container scanning is away from detection, which is now a solved and commoditised problem, and toward context. Trivy already finds essentially everything; the frontier is teaching it, and the pipeline around it, to say less. VEX turns one team's investigation into another team's silence. EPSS and KEV turn a severity number into a probability of harm. Runtime and reachability analysis promise to prune findings for code that never executes. The open question is whether that context can be made trustworthy enough to act on automatically. A VEX statement is only as good as the analysis behind it, and an EPSS score is a prediction, not a guarantee. There is a real risk that context-based suppression becomes the new backported-fix problem: a convenient reason to hide a finding that turns out, later, to have mattered. The March 2026 compromise sharpens the point. The scanner in your pipeline is not a neutral observer. It is code you run with your keys in reach, and the discipline you bring to what it reports has to extend to how you invoke it in the first place. The noise is worth cutting. Just be sure you are cutting noise, and not the one signal you needed to hear.

Sources

  1. Aqua Security, Trivy Open Source Vulnerability Scanner, 2026.
  2. Trivy Documentation, Vulnerability Scanning, 2026.
  3. Trivy Documentation, Filtering, 2026.
  4. Trivy, Project homepage, 2026.
  5. VMware Tanzu, Reduce Noise from False Positives in your Trivy CVE report with VEX, 2024.
  6. Cybersecurity and Infrastructure Security Agency, Known Exploited Vulnerabilities Catalog, 2026.
  7. FIRST, Exploit Prediction Scoring System (EPSS), 2026.
  8. Splunk, Exploit Prediction Scoring System (EPSS): How It Works and Why It Matters, 2026.
  9. Palo Alto Networks, Reframing Vulnerability Management in an Age of Overload, 2026.
  10. OpenSSF, From Noise to Signal: Using Runtime Context to Win the Vulnerability Management Battle, 2026.
  11. CrowdStrike, From Scanner to Stealer: Inside the trivy-action Supply Chain Compromise, 2026.
  12. The Hacker News, Trivy Security Scanner GitHub Actions Breached, 75 Tags Hijacked to Steal CI/CD Secrets, 2026.
  13. Aqua Security, aquasecurity/trivy-action, 2026.