This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Vulnerability Scanning

Learn how to scan container images, filesystems, and SBOMs for known software vulnerabilities.

Vulnerability scanning is the automated process of proactively identifying security weaknesses and known exploits within software and systems. This is crucial because it helps developers and organizations find and fix potential security holes before malicious actors can discover and exploit them, thus protecting data and maintaining system integrity.

Grype is an open-source vulnerability scanner specifically designed to analyze container images and filesystems. It works by comparing the software components it finds against a database of known vulnerabilities, providing a report of potential risks so they can be addressed.

1 - Getting Started

Use Grype to scan your container images, directories, or archives for known vulnerabilities.

What is Vulnerability Scanning?

Vulnerability scanning is the process of identifying known security vulnerabilities in software packages and dependencies.

  • For developers, it helps catch security issues early in development, before they reach production.

  • For organizations, it’s essential for maintaining security posture and meeting compliance requirements.

Grype is a CLI tool for scanning container images, filesystems, and SBOMs for known vulnerabilities.

Installation

Grype is provided as a single compiled executable and requires no external dependencies to run. Run the command for your platform to download the latest release.

curl -sSfL https://get.anchore.io/grype | sudo sh -s -- -b /usr/local/bin
brew install grype
nuget install Anchore.Grype

Check out installation guide for full list of official and community-maintained packaging options.

Scan a container image for vulnerabilities

Run grype against a small container image. Grype will download the latest vulnerability database and output simple human-readable table of packages that are vulnerable:

grype alpine:latest
 ✔ Loaded image alpine:latest
 ✔ Parsed image sha256:8d591b0b7dea080ea3be9e12ae563eebf9…
 ✔ Cataloged contents 058c92d86112aa6f641b01ed238a07a3885…
   ├── ✔ Packages                        [15 packages]
   ├── ✔ File metadata                   [82 locations]
   ├── ✔ File digests                    [82 files]
   └── ✔ Executables                     [17 executables]
 ✔ Scanned for vulnerabilities     [6 vulnerability matches]
   ├── by severity: 0 critical, 0 high, 0 medium, 6 low, 0 negligible
   └── by status:   0 fixed, 6 not-fixed, 0 ignored
NAME           INSTALLED   FIXED-IN  TYPE  VULNERABILITY   SEVERITY
busybox        1.37.0-r12            apk   CVE-2024-58251  Low
busybox        1.37.0-r12            apk   CVE-2025-46394  Low
busybox-binsh  1.37.0-r12            apk   CVE-2024-58251  Low
busybox-binsh  1.37.0-r12            apk   CVE-2025-46394  Low
ssl_client     1.37.0-r12            apk   CVE-2024-58251  Low
ssl_client     1.37.0-r12            apk   CVE-2025-46394  Low

Scan an existing SBOM for vulnerabilities

Grype can scan container images directly, but it can also scan an existing SBOM document.

grype alpine_latest-spdx.json

Create a vulnerability report in JSON format

The JSON-formatted output from Grype can be processed or visualized by other tools.

Create the vulnerability report using the --output flag:

grype alpine:latest --output json | jq . > vuln_report.json

While the JSON is piped to the file, you’ll see progress on stderr:

 ✔ Pulled image
 ✔ Loaded image alpine:latest
 ✔ Parsed image sha256:8d591b0b7dea080ea3be9e12ae563eebf9869168ffced1cb25b2470a3d9fe15e
 ✔ Cataloged contents 058c92d86112aa6f641b01ed238a07a3885b8c0815de3e423e5c5f789c398b45
   ├── ✔ Packages                        [15 packages]
   ├── ✔ File digests                    [82 files]
   ├── ✔ Executables                     [17 executables]
   └── ✔ File metadata                   [82 locations]
 ✔ Scanned for vulnerabilities     [6 vulnerability matches]
   ├── by severity: 0 critical, 0 high, 0 medium, 6 low, 0 negligible
   └── by status:   0 fixed, 6 not-fixed, 0 ignored

FAQ

Does Grype need internet access?

Only for downloading container images and the vulnerability database. After the initial database download, scanning works offline until you update the database.

What about private container registries?

Grype supports authentication for private registries. See Private Registries.

Can I use Grype in CI/CD pipelines?

Absolutely! Grype is designed for automation. Scan images or SBOMs during builds and fail pipelines based on severity thresholds.

What data does Grype send externally?

Nothing. Grype runs entirely locally and doesn’t send any data to external services.

Next steps

Now that you’ve scanned for vulnerabilities, here are additional resources:

2 - Supported Scan Targets

Explore the different scan targets Grype supports including container images, directories, SBOMs, and individual packages

Grype can scan a variety of scan targets including container images, directories, files, archives, SBOMs, and individual packages. In most cases, you can simply point Grype at what you want to analyze and it will automatically detect and scan it correctly.

Scan a container image from your local daemon or a remote registry:

grype alpine:latest

Scan a directory or file:

grype /path/to/project

Scan an SBOM:

grype sbom.json

To explicitly specify the scan target type, use the --from flag:

--from ARGDescription
dockerUse images from the Docker daemon
podmanUse images from the Podman daemon
containerdUse images from the Containerd daemon
docker-archiveUse a tarball from disk for archives created from docker save
oci-archiveUse a tarball from disk for OCI archives
oci-dirRead directly from a path on disk for OCI layout directories
singularityRead directly from a Singularity Image Format (SIF) container file on disk
dirRead directly from a path on disk (any directory)
fileRead directly from a path on disk (any single file)
registryPull image directly from a registry (bypass any container runtimes)
sbomRead SBOM from file (supports Syft JSON, SPDX, CycloneDX formats)
purlScan individual packages via Package URL identifiers

Instead of using the --from flag explicitly, you can instead:

  • provide no hint and let Grype automatically detect the scan target type implicitly based on the input provided

  • provide the scan target type as a URI scheme in the target argument (e.g., docker:alpine:latest, oci-archive:/path/to/image.tar, dir:/path/to/dir)

Scan target-specific behaviors

With each kind of scan target, there are specific behaviors and defaults to be aware of.

For scan target capabilities that are inherited from Syft, please see the SBOM scan targets documentation:

For scan targets that are uniquely supported by Grype, see the sections below.

SBOM Scan Targets

You can scan pre-generated SBOMs instead of scanning the scan target directly. This approach offers several benefits:

  • Faster scans since package cataloging is already complete
  • Ability to cache and reuse SBOMs
  • Standardized vulnerability scanning across different tools

Scan an SBOM file

Grype scans SBOM files in multiple formats. You can provide an explicit sbom: prefix or just provide the file path:

Explicit SBOM prefix:

grype sbom:sbom.json

Implicit detection:

grype sbom.json

Grype automatically detects the SBOM format. Supported formats include:

  • Syft JSON
  • SPDX JSON, XML, and tag-value
  • CycloneDX JSON and XML

Use the explicit sbom: prefix when the file path might be ambiguous or when you want to be clear about the input type.

Scan an SBOM from stdin

You can pipe SBOM output directly from Syft or other SBOM generation tools:

Syft → Grype pipeline:

syft alpine:latest -o json | grype

Read SBOM from file via stdin:

Grype detects stdin input automatically when no command-line argument is provided and stdin is piped:

cat sbom.json | grype

Package scan targets

You can scan specific packages without scanning an entire image or directory. This is useful for:

  • Testing whether a specific package has vulnerabilities
  • Lightweight vulnerability checks
  • Compliance scanning for specific dependencies

Grype supports two formats for individual package scanning: Package URLs (PURLs) and Common Platform Enumerations (CPEs). When Grype receives input, it checks for PURL format first, then CPE format, before trying other scan target types.

Scan Package URLs (PURLs)

Package URLs (PURLs) provide a standardized way to identify software packages.

A PURL has this format:

pkg:<type>/<namespace>/<name>@<version>?<qualifiers>#<subpath>

Grype can take purls from the CLI or from a file. For instance, to scan the python library urllib3 (version 1.26.7):

grype pkg:pypi/urllib3@1.26.7

You’ll see vulnerabilities for that specific package:

NAME     INSTALLED  FIXED IN  TYPE    VULNERABILITY        SEVERITY  EPSS           RISK
urllib3  1.26.7     1.26.17   python  GHSA-v845-jxx5-vc9f  High      0.9% (74th)    0.6
urllib3  1.26.7     1.26.19   python  GHSA-34jh-p97f-mpxf  Medium    0.1% (35th)    < 0.1
urllib3  1.26.7     1.26.18   python  GHSA-g4mx-q9vg-27p4  Medium    < 0.1% (15th)  < 0.1
urllib3  1.26.7     2.5.0     python  GHSA-pq67-6m6q-mj2v  Medium    < 0.1% (4th)   < 0.1

For operating system packages (apk, deb, rpm), use the distro qualifier to specify the distribution:

grype "pkg:apk/alpine/openssl@3.1.5-r0?distro=alpine-3.19"
grype "pkg:deb/debian/openssl@1.1.1w-0+deb11u1?distro=debian-11"
grype "pkg:rpm/redhat/openssl@1.0.2k-19.el7?distro=rhel-7"

You can specify distribution information with the --distro flag instead:

grype "pkg:rpm/redhat/openssl@1.0.2k-19.el7?arch=x86_64" --distro rhel:7

Without either the distro qualifier or the --distro flag hint, Grype may not find distribution-specific vulnerabilities.

Other qualifiers include:

  • upstream: The upstream package name or version. Vulnerability information tends to be tracked with the source or origin package instead of the installed package itself (e.g. libcrypto might be installed but the pacakge it was built from is openssl which is where vulnerabilities are attributed to)
  • epoch: The epoch value for RPM packages. This is necessary when the package in question has changed the methodology for versioning (e.g., switching from date-based versions to semantic versions) and the epoch is used to indicate that change.

You can scan multiple packages from a file. The file contains one PURL per line:

# contents of packages.txt follow, which must be a text file with one PURL per line

pkg:npm/lodash@4.17.20
pkg:pypi/requests@2.25.1
pkg:maven/org.apache.commons/commons-lang3@3.12.0
grype ./packages.txt

Grype scans all the packages in the file:

NAME           INSTALLED  FIXED IN  TYPE          VULNERABILITY        SEVERITY
lodash         4.17.20    4.17.21   npm           GHSA-35jh-r3h4-6jhm  High
requests       2.25.1     2.31.0    python        GHSA-j8r2-6x86-q33q  Medium
commons-lang3  3.12.0     3.18.0    java-archive  GHSA-j288-q9x7-2f5v  Medium
...

Scan Common Platform Enumerations (CPEs)

Common Platform Enumeration (CPE) is an older identification format for software and hardware. You can scan using CPE format:

grype "cpe:2.3:a:apache:log4j:2.14.1:*:*:*:*:*:*:*"

Grype supports multiple CPE formats:

# CPE 2.2 format (WFN URI binding)
grype "cpe:/a:apache:log4j:2.14.1"

# CPE 2.3 format (string binding)
grype "cpe:2.3:a:apache:log4j:2.14.1:*:*:*:*:*:*:*"

Use CPE when:

  • You’re working with legacy systems that use CPE identifiers
  • You need to test for vulnerabilities in a specific CVE that references a CPE
  • PURL format is not available for your package type

For most modern scanning workflows, PURL format is preferred because it provides better precision and ecosystem-specific information.

Next steps

Additional resources:

3 - Supported package ecosystems

Learn how Grype selects vulnerability data for different package types and what level of accuracy to expect

Grype automatically selects the right vulnerability data source based on the package type and distribution information in your SBOM. This guide explains how Grype chooses which vulnerability feed to use and what level of accuracy to expect.

How Grype chooses vulnerability data

Grype selects vulnerability feeds based on package type:

  • OS packages (apk, deb, rpm, portage, alpm) use vulnerability data sourced from distribution-specific security feeds.
  • Language packages (npm, PyPI, Maven, Go modules, etc.) use GitHub Security Advisories.
  • Other packages (binaries, Homebrew, Jenkins plugins, etc.) fall back to CPE matching against the NVD.

OS packages

When Grype scans an OS package, it uses vulnerability data sourced from distribution security feeds. Distribution maintainers curate these feeds and provide authoritative information about vulnerabilities affecting specific distribution versions.

For example, when you scan Debian 10, Grype looks for vulnerabilities affecting Debian 10 packages:

$ grype debian:10
NAME          INSTALLED           FIXED IN     TYPE  VULNERABILITY   SEVERITY
libgcrypt20   1.8.4-5+deb10u1     (won't fix)  deb   CVE-2021-33560  High
bash          5.0-4                            deb   CVE-2019-18276  Negligible
libidn2-0     2.0.5-1+deb10u1     (won't fix)  deb   CVE-2019-12290  High

OS distributions

Grype supports major Linux distributions with dedicated vulnerability feeds, including Alpine, Debian, Ubuntu, RHEL, SUSE, and many others. Some distributions have mature security tracking programs that report both fixed and unfixed vulnerabilities, providing comprehensive coverage.

Derivative distributions automatically use their parent distribution’s vulnerability feed. Grype maps derivative distributions to their upstream source using the ID_LIKE field from /etc/os-release. For example, Rocky Linux and AlmaLinux use the RHEL vulnerability feed, while Raspbian uses Debian’s feed.

When scanning Rocky Linux, Grype uses Red Hat security data:

$ grype rockylinux:9 -o json | jq '.matches[0].matchDetails[0].searchedBy.distro'
{
  "type": "rockylinux",
  "version": "9.3"
}

The distro type shows rockylinux, but Grype searches the RHEL vulnerability feed automatically. You don’t need to configure this mapping –it happens transparently based on the distribution’s ID_LIKE field.

Language packages

Language packages use vulnerability data from GitHub Security Advisories (GHSA). GitHub maintains security advisories for major package ecosystems, sourced from package maintainers, security researchers, and automated scanning.

When you scan a JavaScript package, Grype searches GHSA for npm advisories:

$ grype node:18-alpine
NAME         INSTALLED  FIXED IN  TYPE  VULNERABILITY         SEVERITY
cross-spawn  7.0.3      7.0.5     npm   GHSA-3xgq-45jj-v275   High

Supported language ecosystems

Grype supports these language ecosystems through GHSA:

  • Python (PyPI) - Python packages
  • JavaScript (npm) - Node.js packages
  • Java (Maven) - Java archives
  • Go (modules) - Go modules
  • PHP (Composer) - PHP packages
  • .NET (NuGet) - .NET packages
  • Dart (Pub) - Dart and Flutter packages
  • Ruby (RubyGems) - Ruby gems
  • Rust (Crates) - Rust crates
  • Swift - Swift packages
  • GitHub Actions - GitHub Actions workflow dependencies

For language packages, Grype searches GHSA by package name and version, applying ecosystem-specific version comparison rules to determine if your package version falls within the vulnerable range.

In addition to language packages, Bitnami packages are searched against Bitnami’s vulnerability feed in a similar manner.

Other packages

Packages without dedicated feeds use CPE fallback matching

Packages using CPE matching

These package types rely on Common Platform Enumeration (CPE) matching against the National Vulnerability Database (NVD):

  • Binary executables
  • Homebrew packages
  • Jenkins plugins
  • Conda packages
  • WordPress plugins

CPE matching constructs a CPE string from the package name and version, then searches the NVD for matching vulnerability entries.

Understanding CPE match accuracy

CPE matching has important limitations:

  • May produce false positives - CPEs often do not distinguish between package ecosystems. For example, the PyPI package docker (a Python library for talking to the Docker daemon) can match vulnerabilities for Docker the container runtime because they share similar CPE identifiers.
  • May miss vulnerabilities - Not all vulnerabilities have CPE entries in the NVD.
  • Requires CPE metadata - Packages must have CPE information for matching to work.

You should verify CPE matches against the actual vulnerability details to confirm they apply to your specific package. Here’s a CPE match example:

{
  "matchDetails": [
    {
      "type": "cpe-match",
      "searchedBy": {
        "cpes": ["cpe:2.3:a:zlib:zlib:1.2.11:*:*:*:*:*:*:*"]
      },
      "found": {
        "versionConstraint": "<= 1.2.12 (unknown)"
      }
    }
  ]
}

Notice the version constraint shows (unknown) format rather than ecosystem-specific semantics, and the match type is cpe-match instead of exact-direct-match.

For more details on interpreting match types, confidence levels, and result reliability, see Understanding Grype results.

Next steps

Additional resources:

4 - Understanding Grype results

Learn how to read and interpret Grype’s vulnerability scan output, including match types, confidence levels, and result reliability

This guide explains how to read and interpret Grype’s vulnerability scan output. You’ll learn what different match types mean, how to assess result reliability, and how to filter results based on confidence levels.

Output formats

Grype supports several output formats for scan results:

  • Table (default) - Human-readable columnar output for terminal viewing
  • JSON - Complete structured data with all match details
  • SARIF - Standard format for tool integration and CI/CD pipelines
  • Template - Custom output using Go templates

This guide focuses on table and JSON formats, which you’ll use most often for understanding scan results.

Reading table output

The table format is Grype’s default output. When you run grype <image>, you see a table displaying one row per unique vulnerability match, with deduplication of identical rows.

Table columns

The table displays eight standard columns, with an optional ninth column for annotations:

  • NAME - The package name
  • INSTALLED - The version of the package
  • FIXED-IN - The version that fixes the vulnerability (shows (won't fix) if the vendor won’t fix it, or empty if no fix is available). See Filter by fix availability to filter results based on fix states
  • TYPE - Package type (apk, deb, rpm, npm, python, java-archive, etc.)
  • VULNERABILITY - The vulnerability identifier (see below)
  • SEVERITY - Vulnerability severity rating (Critical, High, Medium, Low, Negligible, Unknown)
  • EPSS - Exploit Prediction Scoring System score and percentile showing the probability of exploitation
  • RISK - Calculated risk score combining CVSS, EPSS, and other severity metrics into a single numeric value (0.0 to 10.0)
  • Annotations (conditional) - Additional context like KEV (Known Exploited Vulnerability), suppressed status, or distribution version when scanning multi-distro images

Here’s what a typical scan looks like:

NAME          INSTALLED  FIXED-IN     TYPE          VULNERABILITY   SEVERITY  EPSS          RISK
log4j-core    2.4.0      2.12.2       java-archive  CVE-2021-44228  Critical  94.4% (99th)  100.0  (kev)
log4j-core    2.4.0      2.12.2       java-archive  CVE-2021-45046  Critical  94.3% (99th)  99.0   (kev)
apk-tools     2.10.6-r0  2.10.7-r0    apk           CVE-2021-36159  Critical  12% (85th)    8.5
libcrypto1.1  1.1.1k-r0               apk           CVE-2021-3711   Critical  9% (78th)     9.1
libcrypto1.1  1.1.1k-r0  (won't fix)  apk           CVE-2021-3712   High      5% (62nd)     7.2

The Annotations column appears conditionally to provide additional context:

  • KEV or (kev) - Indicates the vulnerability is in CISA’s Known Exploited Vulnerabilities catalog
  • suppressed or suppressed by VEX - Shown when using --show-suppressed flag (see View filtered results)
  • Distribution version (e.g., ubuntu:20.04) - Shown when scan results include matches from multiple different distributions

Understanding vulnerability IDs

The VULNERABILITY column displays different types of identifiers depending on the data source:

  • CVE IDs (e.g., CVE-2024-1234) - Common Vulnerabilities and Exposures identifiers used by most Linux distributions (Alpine, Debian, Ubuntu, RHEL, SUSE) and the NVD
  • GHSA IDs (e.g., GHSA-xxxx-xxxx-xxxx) - GitHub Security Advisory identifiers for language ecosystem packages
  • ALAS IDs (e.g., ALAS-2023-1234) - Amazon Linux Security Advisory identifiers
  • ELSA IDs (e.g., ELSA-2023-12205) - Oracle Enterprise Linux Security Advisory identifiers

By default, Grype displays the vulnerability ID from the original data source. For example, an Alpine package might show CVE-2024-1234 while a GitHub Advisory for the same issue shows GHSA-abcd-1234-efgh. Use the --by-cve flag to normalize results to CVE identifiers:

grype <image> --by-cve

This flag replaces non-CVE vulnerability IDs with their related CVE ID when available, uses CVE metadata instead of the original advisory metadata, and makes it easier to correlate vulnerabilities across different data sources.

Compare the two approaches:

# Default output - shows GitHub Advisory ID
$ grype node:18
NAME     INSTALLED  FIXED-IN  TYPE  VULNERABILITY        SEVERITY
lodash   4.17.20    4.17.21   npm   GHSA-35jh-r3h4-6jhm  High

# With --by-cve - converts to CVE
$ grype node:18 --by-cve
NAME     INSTALLED  FIXED-IN  TYPE  VULNERABILITY   SEVERITY
lodash   4.17.20    4.17.21   npm   CVE-2021-23337  High

Sorting results

By default, Grype sorts vulnerability results by risk score, which combines multiple factors to help you prioritize remediation efforts. Understanding how sorting works and when to use alternative methods helps you build effective security workflows.

Why risk-based sorting works best

The default risk score takes a holistic approach by combining:

  • Threat (likelihood of exploitation) - Based on EPSS (Exploit Prediction Scoring System) scores or presence in CISA’s Known Exploited Vulnerabilities (KEV) catalog
  • Impact (potential damage) - Based on CVSS scores and severity ratings from multiple sources
  • Context (exploitation evidence) - Additional weight for vulnerabilities with known ransomware campaigns

This multi-factor approach aligns with security best practices recommended by the EPSS project, which emphasizes that “CVSS is a useful tool for capturing the fundamental properties of a vulnerability, but it needs to be used in combination with data-driven threat information, like EPSS.”

Risk-based sorting helps you focus on vulnerabilities that are both likely to be exploited AND have significant business impact, optimizing your remediation efficiency.

Why single-metric sorting can be misleading

While Grype offers several sorting options via the --sort-by flag, using single metrics can lead to inefficient prioritization:

Severity-only sorting (--sort-by severity) focuses solely on potential impact:

  • You may waste effort patching Critical severity vulnerabilities that are unlikely to ever be exploited in the wild
  • No consideration for whether attackers are actively targeting the vulnerability
  • Ignores real-world threat intelligence

EPSS-only sorting (--sort-by epss) focuses solely on exploitation likelihood:

  • You may prioritize vulnerabilities with high exploitation probability but low business impact
  • EPSS is not a risk score – it only addresses the threat component, not the complete risk picture
  • Missing context like asset criticality, network exposure, or available compensating controls

The EPSS documentation explicitly states that EPSS scores should be combined with severity information to make informed prioritization decisions, which is exactly what Grype’s risk score does.

Understanding EPSS in Grype

EPSS (Exploit Prediction Scoring System) is a data-driven scoring model that estimates the probability a vulnerability will be exploited in the next 30 days. Grype displays EPSS data in the table output showing both the raw score and percentile, such as 94.4% (99th), which means:

  • 94.4% - The raw EPSS score indicating a 94.4% probability of exploitation within 30 days
  • 99th - The percentile rank, meaning this score is higher than 99% of all EPSS scores

EPSS percentiles help normalize the heavily skewed distribution of EPSS scores, making it easier to set thresholds. For example, a vulnerability in the 90th percentile is more concerning than one in the 50th percentile, even if the raw likelihood values appear to be similar.

Grype incorporates EPSS as the threat component of its risk calculation. When a vulnerability appears in the KEV catalog, Grype automatically treats it as maximum threat (overriding EPSS) since observed exploitation is more significant than predicted exploitation.

For more details on EPSS methodology and interpretation, see the EPSS model documentation.

When to use alternative sorting methods

While risk-based sorting is recommended for most remediation workflows, alternative sorting methods serve specific use cases:

Sort by KEV status (--sort-by kev):

  • When you need to comply with regulatory requirements like CISA BOD 22-01
  • For incident response scenarios focusing on actively exploited vulnerabilities

Sort by severity (--sort-by severity):

  • When organizational SLAs or compliance frameworks specify severity-based remediation timeframes (e.g., “patch all Critical within 7 days”)

Sort by EPSS (--sort-by epss):

  • For threat landscape analysis and security research

Sort by package (--sort-by package):

  • When organizing remediation work by team ownership (different teams maintain different packages)
  • For coordinating updates across multiple instances of the same package

Sort by vulnerability ID (--sort-by vulnerability):

  • When tracking specific CVE campaigns across your environment
  • For correlating findings with external threat intelligence reports

For most security and remediation workflows, stick with the default risk-based sorting. It provides the best balance of threat intelligence and impact assessment to help you prioritize effectively.

Next steps

Additional resources:

5 - Working with JSON

Learn how to work with Grype’s native JSON format

Grype’s native JSON output format provides a comprehensive representation of vulnerability scan results, including detailed information about each vulnerability, how it was matched, and the affected packages. This guide explains the structure of the JSON output and how to interpret its contents effectively.

Data shapes

The JSON output contains a top-level matches array. Each match has this structure:

{
  "matches": [
    {
      "vulnerability": { ... },
      "relatedVulnerabilities": [ ... ],
      "matchDetails": [ ... ],
      "artifact": { ... }
    }
  ]
}

Ultimately, matches are the core results of a Grype scan. Matches are composed of:

  • vulnerability - Primary vulnerability information
  • matchDetails - How Grype found the match
  • artifact - The package/artifact that was matched against the vulnerability

Vulnerability fields

The vulnerability object contains the primary vulnerability information:

  • id (string) - The vulnerability identifier (CVE, GHSA, ALAS, ELSA, etc.)
  • dataSource (string) - URL to the vulnerability record in the data feed
  • namespace (string) - The data source namespace (e.g., alpine:distro:alpine:3.10, debian:distro:debian:10, github:language:javascript, nvd:cpe)
  • severity (string) - Severity rating from the data source
  • urls (array) - Reference URLs for the vulnerability
  • description (string) - Human-readable vulnerability description
  • cvss (array) - CVSS score information from various sources
  • fix (object) - Fix information including available versions and fix state (fixed, not-fixed, wont-fix, unknown). See Understanding fix states for details
  • advisories (array) - Related security advisories (where RHSAs appear)
  • risk (float64) - Calculated risk score combining CVSS, EPSS, and other severity metrics

A typical vulnerability object looks like:

{
  "vulnerability": {
    "id": "CVE-2021-36159",
    "dataSource": "https://security.alpinelinux.org/vuln/CVE-2021-36159",
    "namespace": "alpine:distro:alpine:3.10",
    "severity": "Critical",
    "urls": [],
    "fix": {
      "versions": ["2.10.7-r0"],
      "state": "fixed"
    },
    "advisories": [],
    "risk": 0.92
  }
}

Match detail fields

The matchDetails array contains information about how Grype found the match. Each detail object includes:

  • type (string) - Match type: exact-direct-match, exact-indirect-match, or cpe-match
  • matcher (string) - The matcher that produced this result (e.g., apk-matcher, github-matcher, stock-matcher)
  • searchedBy (object) - The specific attributes used to search (package name, version, etc.)
  • found (object) - The specific attributes in the vulnerability data that matched
  • fix (object) - Fix details specific to this match (may differ from vulnerability-level fix)

Here’s what matchDetails looks like:

{
  "matchDetails": [
    {
      "type": "exact-direct-match",
      "matcher": "apk-matcher",
      "searchedBy": {
        "distro": {
          "type": "alpine",
          "version": "3.10.9"
        },
        "package": {
          "name": "apk-tools",
          "version": "2.10.6-r0"
        },
        "namespace": "alpine:distro:alpine:3.10"
      },
      "found": {
        "vulnerabilityID": "CVE-2021-36159",
        "versionConstraint": "< 2.10.7-r0 (apk)"
      }
    }
  ]
}

Understanding match types

Grype determines how it matched a package to a vulnerability based on the available data sources. The match type indicates how the match was made:

  • exact-direct-match means the package name matched directly in a dedicated vulnerability feed. Grype searched the feed using the package name from your scan and found a matching vulnerability entry.

  • exact-indirect-match means the source package name matched in a dedicated vulnerability feed. This occurs when you scan a binary package (e.g., libcrypto1.1) but the feed tracks vulnerabilities under the source package (e.g., openssl). Grype searches the feed using the source package name and maps the results to the binary package.

  • cpe-match means Grype used Common Platform Enumeration (CPE) matching as a fallback when no exact match was found in ecosystem-specific feeds. CPE matching relies on CPE identifiers derived from package metadata and is less precise.

You can loosely think of the match type as a proxy for confidence level in the match, where exact-direct-match has the highest confidence, followed by exact-indirect-match, and finally cpe-match.

A cpe-match means Grype used Common Platform Enumeration (CPE) matching as a fallback.

CPE matching occurs when:

  • No exact package match exists in ecosystem-specific feeds
  • Grype falls back to the NVD database
  • The match is based on CPE identifiers derived from package metadata

This match type has lower confidence because:

  • CPE matching is generic and not package-ecosystem aware
  • Package naming may not match CPE naming conventions exactly
  • Version ranges may be broader or less precise

Understanding version constraints

The found.versionConstraint field shows the version range (found on the vulnerability) which the package version was found to be within (thus, the package is affected by the vulnerability). The format indicates the constraint type and the comparison logic used:

  • < 1.2.3 (apk) - Alpine package version constraint using apk version comparison
  • < 1.2.3 (deb) - Debian package version constraint using dpkg version comparison
  • < 1.2.3 (rpm) - RPM package version constraint using rpm version comparison
  • < 1.2.3 (python) - Python package version constraint using PEP 440 comparison
  • < 1.2.3 (semantic) - Semantic versioning constraint using semver comparison
  • < 1.2.3 (unknown) - Unknown version format (lower reliability)

The constraint type tells you how Grype compared versions. Ecosystem-specific formats (apk, deb, rpm) use that ecosystem’s version comparison rules, which handle epoch numbers, release tags, and other format-specific details correctly. Generic formats like unknown may have less precise matching.

Filtering and querying results

Use jq to filter and analyze JSON output based on match type, severity, or data source.

Filter by match type

Show only high-confidence exact matches:

grype <image> -o json | jq '.matches[] | select(.matchDetails[0].type == "exact-direct-match")'

Exclude CPE matches:

grype <image> -o json | jq '.matches[] | select(.matchDetails[0].type != "cpe-match")'

Filter by data source

Show only matches from Alpine security data:

grype <image> -o json | jq '.matches[] | select(.vulnerability.namespace | startswith("alpine:"))'

Show only GitHub Security Advisories:

grype <image> -o json | jq '.matches[] | select(.vulnerability.namespace | startswith("github:"))'

Filter by severity

Show only Critical and High severity vulnerabilities:

grype <image> -o json | jq '.matches[] | select(.vulnerability.severity == "Critical" or .vulnerability.severity == "High")'

Combine filters

Show Critical/High severity vulnerabilities with exact matches only:

grype <image> -o json | jq '.matches[] | select(
  (.vulnerability.severity == "Critical" or .vulnerability.severity == "High") and
  (.matchDetails[0].type == "exact-direct-match" or .matchDetails[0].type == "exact-indirect-match")
)'

Count matches by type

grype <image> -o json | jq '[.matches[].matchDetails[0].type] | group_by(.) | map({type: .[0], count: length})'

Understanding a match

Each match in JSON output contains information about how Grype found the vulnerability and links to the original sources. This lets you examine what Grype looked at and verify the match yourself.

Reference URLs

The vulnerability object includes reference URLs from the vulnerability data:

grype <image> -o json | jq '.matches[].vulnerability | {id, dataSource, urls}'
  • dataSource - URL to the vulnerability record in Grype’s data feed
  • urls - Reference URLs from the original vulnerability disclosure (CVE details, vendor advisories, etc.)

These URLs point to the original vulnerability information that Grype used.

What Grype searched for

The matchDetails[].searchedBy field shows what Grype looked at when searching for vulnerabilities:

grype <image> -o json | jq '.matches[].matchDetails[].searchedBy'

For distro packages, this shows the distro, package name, and version. For CPE matches, this shows the CPE strings Grype constructed. This lets you see exactly what Grype queried.

What Grype found

The matchDetails[].found field shows what matched in the vulnerability data:

grype <image> -o json | jq '.matches[].matchDetails[] | {found, type}'

This shows the vulnerability ID and version constraint that matched, along with the match type. Comparing searchedBy and found shows how Grype connected your package to the vulnerability.

Next steps

Additional resources:

6 - Filter scan results

Control which vulnerabilities Grype reports using filtering flags, configuration rules, and VEX documents

Learn how to control which vulnerabilities Grype reports using filtering flags and configuration options.

Set failure thresholds

Use the --fail-on flag to control Grype’s exit code based on vulnerability severity. This can be helpful for integrating Grype into CI/CD pipelines.

The --fail-on flag (alias: -f) sets a severity threshold. When scanning completes, Grype exits with code 2 if it found vulnerabilities at or above the specified severity:

grype alpine:3.10 --fail-on high

You’ll see vulnerabilities at or above the threshold:

NAME          INSTALLED  FIXED IN   TYPE  VULNERABILITY   SEVERITY  EPSS           RISK
zlib          1.2.11-r1             apk   CVE-2022-37434  Critical  92.7% (99th)   87.1
libcrypto1.1  1.1.1k-r0             apk   CVE-2023-0286   High      89.1% (99th)   66.4
libssl1.1     1.1.1k-r0             apk   CVE-2023-0286   High      89.1% (99th)   66.4
...
[0026] ERROR discovered vulnerabilities at or above the severity threshold

# Exit code: 2

Valid severity values, from lowest to highest:

negligible < low < medium < high < critical

When you set a threshold, Grype fails if it finds vulnerabilities at that severity or higher. For example, --fail-on high fails on both high and critical vulnerabilities.

Filter by fix availability

Grype provides flags to filter vulnerabilities based on whether fixes are available.

Show only vulnerabilities with fixes available

The --only-fixed flag filters scan results to show only vulnerabilities that have fixes available:

grype alpine:latest --only-fixed

This flag filters out vulnerabilities with these fix states:

  • not-fixed - No fix is available yet
  • wont-fix - Maintainers won’t fix this vulnerability
  • unknown - No fix state information is available

This is useful when you want to focus on actionable vulnerabilities that you can remediate by updating packages.

Show only vulnerabilities without fixes available

The --only-notfixed flag filters scan results to show only vulnerabilities that do not have fixes available:

grype alpine:3.10 --only-notfixed

These vulnerabilities don’t have fixes available yet:

NAME          INSTALLED  TYPE  VULNERABILITY   SEVERITY  EPSS           RISK
zlib          1.2.11-r1  apk   CVE-2022-37434  Critical  92.7% (99th)   87.1
libcrypto1.1  1.1.1k-r0  apk   CVE-2023-0286   High      89.1% (99th)   66.4
libssl1.1     1.1.1k-r0  apk   CVE-2023-0286   High      89.1% (99th)   66.4
libcrypto1.1  1.1.1k-r0  apk   CVE-2023-2650   Medium    92.0% (99th)   52.9
libssl1.1     1.1.1k-r0  apk   CVE-2023-2650   Medium    92.0% (99th)   52.9
...

This flag filters out vulnerabilities with fix state fixed. Notice the FIXED-IN column is empty for these vulnerabilities.

This is useful when you want to identify vulnerabilities that require alternative mitigation strategies, such as:

  • Accepting the risk
  • Implementing compensating controls
  • Waiting for a fix to become available
  • Switching to a different package

Understanding fix states

Grype assigns one of four fix states to each vulnerability based on information from vulnerability data sources:

Fix StateDescription
fixedA fix is available for this vulnerability
not-fixedNo fix is available yet, but maintainers may release one
wont-fixPackage maintainers have decided not to fix this vulnerability
unknownNo fix state information is available

Vulnerabilities with no fix state information are treated as unknown. This ensures Grype handles missing data consistently.

Ignore specific fix states

The --ignore-states flag gives you fine-grained control over which fix states to filter out. You can ignore one or more fix states by specifying them as a comma-separated list:

# Ignore vulnerabilities with unknown fix states
grype alpine:3.10 --ignore-states unknown

Only vulnerabilities with known fix states appear:

NAME       INSTALLED  FIXED IN   TYPE  VULNERABILITY   SEVERITY  EPSS         RISK
apk-tools  2.10.6-r0  2.10.7-r0  apk   CVE-2021-36159  Critical  1.0% (76th)  0.9
# Ignore both wont-fix and not-fixed vulnerabilities
grype alpine:3.10 --ignore-states wont-fix,not-fixed

This leaves only fixed vulnerabilities and those with unknown states:

NAME          INSTALLED  FIXED IN   TYPE  VULNERABILITY   SEVERITY  EPSS           RISK
zlib          1.2.11-r1             apk   CVE-2022-37434  Critical  92.7% (99th)   87.1
libcrypto1.1  1.1.1k-r0             apk   CVE-2023-0286   High      89.1% (99th)   66.4
libssl1.1     1.1.1k-r0             apk   CVE-2023-0286   High      89.1% (99th)   66.4
apk-tools     2.10.6-r0  2.10.7-r0  apk   CVE-2021-36159  Critical  1.0% (76th)    0.9
...

Valid fix state values are: fixed, not-fixed, wont-fix, unknown.

If you specify an invalid fix state, Grype returns an error:

grype alpine:latest --ignore-states invalid-state
# Error: unknown fix state invalid-state was supplied for --ignore-states

Combining severity with fix filtering

You can combine --fail-on with fix state filtering to create sophisticated CI/CD policies:

# Fail only if fixable critical or high vulnerabilities exist
grype alpine:3.10 --fail-on high --only-fixed

Grype now only fails on fixable critical/high vulnerabilities:

NAME       INSTALLED  FIXED IN   TYPE  VULNERABILITY   SEVERITY  EPSS         RISK
apk-tools  2.10.6-r0  2.10.7-r0  apk   CVE-2021-36159  Critical  1.0% (76th)  0.9
[0026] ERROR discovered vulnerabilities at or above the severity threshold

# Exit code: 2
# Fail on medium or higher, but ignore wont-fix vulnerabilities
grype alpine:latest --fail-on medium --ignore-states wont-fix

The --fail-on check runs after vulnerability matching and filtering. Grype converts all filtering options (--only-fixed, --only-notfixed, --ignore-states, configuration ignore rules, and VEX documents) into ignore rules and applies them during matching. The severity threshold check then evaluates only the remaining vulnerabilities.

View filtered results

By default, Grype hides filtered vulnerabilities from output. You can view them in table output with --show-suppressed or in JSON output by inspecting the ignoredMatches field.

In table output

The --show-suppressed flag displays filtered vulnerabilities in table output with a (suppressed) label:

grype alpine:3.10 --only-fixed --show-suppressed

Filtered vulnerabilities now appear with a (suppressed) label:

NAME          INSTALLED  FIXED IN   TYPE  VULNERABILITY   SEVERITY  EPSS           RISK
apk-tools     2.10.6-r0  2.10.7-r0  apk   CVE-2021-36159  Critical  1.0% (76th)    0.9
zlib          1.2.11-r1             apk   CVE-2018-25032  High      < 0.1% (26th)  < 0.1  (suppressed)
libcrypto1.1  1.1.1k-r0             apk   CVE-2021-3711   Critical  2.7% (85th)    2.4    (suppressed)
libssl1.1     1.1.1k-r0             apk   CVE-2021-3711   Critical  2.7% (85th)    2.4    (suppressed)
libcrypto1.1  1.1.1k-r0             apk   CVE-2021-3712   High      0.5% (66th)    0.4    (suppressed)
libssl1.1     1.1.1k-r0             apk   CVE-2021-3712   High      0.5% (66th)    0.4    (suppressed)
...

In JSON output

When you use JSON output (-o json), Grype places filtered vulnerabilities in the ignoredMatches array. Non-filtered vulnerabilities appear in the matches array.

For details on the complete JSON structure and all fields, see Reading JSON output.

View the structure:

grype alpine:3.10 --only-fixed -o json | jq '{matches, ignoredMatches}'

The structure separates matched from ignored vulnerabilities:

{
  "matches": [
    {
      "vulnerability": {...},
      "artifact": {...},
      ...
    }
  ],
  "ignoredMatches": [
    {
      "vulnerability": {...},
      "artifact": {...},
      ...
    },
    ...
  ]
}

Inspect a specific ignored vulnerability:

grype alpine:3.10 --only-fixed -o json | jq '.ignoredMatches[0] | {vulnerability: .vulnerability.id, package: .artifact.name, reason: .appliedIgnoreRules}'

Each ignored match shows why it was filtered:

{
  "vulnerability": "CVE-2018-25032",
  "package": "zlib",
  "reason": [
    {
      "namespace": "",
      "fix-state": "unknown"
    }
  ]
}

The appliedIgnoreRules field shows why each vulnerability was filtered.

Ignore specific vulnerabilities or packages

You can create ignore rules in your .grype.yaml configuration file to exclude specific vulnerabilities or packages from scan results.

Use ignore rules

Create a .grype.yaml file with ignore rules:

ignore:
  # Ignore specific CVEs
  - vulnerability: CVE-2008-4318
  - vulnerability: GHSA-1234-5678-90ab

  # Ignore all vulnerabilities in a package
  - package:
      name: libcurl

  # Ignore vulnerabilities in a specific version
  - package:
      name: openssl
      version: 1.1.1g

  # Ignore by package type
  - package:
      type: npm
      name: lodash

  # Ignore by package location (supports glob patterns)
  - package:
      location: "/usr/local/lib/node_modules/**"

  # Ignore by fix state
  - vulnerability: CVE-2020-1234
    fix-state: not-fixed

  # Combine multiple criteria
  - vulnerability: CVE-2008-4318
    fix-state: unknown
    package:
      name: libcurl
      version: 1.5.1

Valid fix-state values are: fixed, not-fixed, wont-fix, unknown.

When you combine multiple criteria in a rule, all criteria must match for the rule to apply.

Use VEX documents

Grype supports Vulnerability Exploitability eXchange (VEX) documents to provide information about which vulnerabilities affect your software. VEX allows you to communicate vulnerability status in a machine-readable format that follows CISA minimum requirements.

Grype supports two VEX formats as input:

  • OpenVEX - Compact JSON format with minimal required fields
  • CSAF VEX - Comprehensive format with rich advisory metadata (OASIS standard)

VEX-filtered vulnerabilities behave like other filtered results:

  • Table output: Hidden by default, shown with --show-suppressed flag and marked as (suppressed by VEX)
  • JSON output: Moved to the ignoredMatches array with VEX rules listed in appliedIgnoreRules

This guide uses OpenVEX examples for simplicity, but both formats work identically with Grype. The core concepts (status values, product identification, filtering behavior) apply to both formats.

Basic usage

Use the --vex flag to provide one or more VEX documents:

# Single VEX document
grype alpine:latest --vex vex-report.json

# Multiple VEX documents
grype alpine:latest --vex vex-1.json,vex-2.json

You can also specify VEX documents in your configuration file:

# .grype.yaml file
vex-documents:
  - vex-report.json
  - vex-findings.json

VEX status values

VEX documents use four standard status values:

Filtering statuses (automatically applied):

  • not_affected - Product is not affected by the vulnerability
  • fixed - Vulnerability has been remediated

Augmenting statuses (require explicit configuration):

  • affected - Product is affected by the vulnerability
  • under_investigation - Impact is still being assessed

By default, Grype moves vulnerabilities with not_affected or fixed status to the ignored list. Vulnerabilities with affected or under_investigation status are only added to results when you enable augmentation:

vex-add: ["affected", "under_investigation"]

Creating VEX documents with vexctl

The easiest way to create OpenVEX documents is with vexctl:

# Create a VEX statement marking a CVE as not affecting your image
vexctl create \
  --product="pkg:oci/alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412" \
  --subcomponents="pkg:apk/alpine/busybox@1.37.0-r19" \
  --vuln="CVE-2024-58251" \
  --status="not_affected" \
  --justification="vulnerable_code_not_present" \
  --file="vex.json"

# Use the VEX document with Grype
grype alpine:3.22.2 --vex vex.json

You can also create VEX documents manually. Here’s an OpenVEX example:

{
  "@context": "https://openvex.dev/ns/v0.2.0",
  "@id": "https://openvex.dev/docs/public/vex-07f09249682f6d9d2924be146078475538731fa0ee6a50ad3c9f33617e4a0be4",
  "author": "Alex Goodman",
  "version": 1,
  "statements": [
    {
      "vulnerability": {
        "name": "CVE-2024-58251"
      },
      "products": [
        {
          "@id": "pkg:oci/alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412",
          "subcomponents": [
            {
              "@id": "pkg:apk/alpine/busybox@1.37.0-r19"
            }
          ]
        }
      ],
      "status": "not_affected",
      "justification": "vulnerable_code_not_present",
      "timestamp": "2025-11-21T20:30:11.725672Z"
    }
  ],
  "timestamp": "2025-11-21T20:30:11Z"
}

CSAF VEX documents have a more complex structure with product trees, branches, and vulnerability arrays. See the CSAF specification for complete structure details.

Justifications for not_affected

OpenVEX provides standardized justification values when marking vulnerabilities as not_affected:

  • component_not_present - The component is not included in the product
  • vulnerable_code_not_present - The vulnerable code is not present
  • vulnerable_code_not_in_execute_path - The vulnerable code cannot be executed
  • vulnerable_code_cannot_be_controlled_by_adversary - The vulnerability cannot be exploited
  • inline_mitigations_already_exist - Mitigations prevent exploitation

CSAF VEX uses a richer product status model with categories like known_not_affected that Grype maps to the standard VEX statuses. See the CSAF specification for details on CSAF-specific fields.

These justifications help security teams understand the rationale behind VEX statements.

Product identification

Grype matches VEX statements to scan results using several identification methods:

Container images (most reliable):

"products": [
  { "@id": "pkg:oci/alpine@sha256:124c7d2707a0ee..." }
]

Image tags (less reliable, can change):

"products": [
  { "@id": "alpine:3.17" }
]

Individual packages via PURLs:

"products": [
  {
    "@id": "pkg:oci/alpine@sha256:124c7d...",
    "subcomponents": [
      { "@id": "pkg:apk/alpine/libssl3@3.0.8-r3" }
    ]
  }
]

Use container digests for the most reliable matching, as tags can move to different images over time.

Next steps

Additional resources:

7 - Vulnerability Database

Using the Grype Vulnerability Database

Grype uses a locally cached database of known vulnerabilities when searching a container, directory, or SBOM for security vulnerabilities. Anchore collates vulnerability data from common feeds, and publishes that data online, at no cost to users.

Updating the local database

When Grype is launched, it checks for an existing vulnerability database, and looks for an updated one online. If available, Grype will automatically download the new database.

To update the database manually, use the following command:

grype db update

If instead, you would like to simply check if a new DB is available without actually updating, use:

grype db check

This will return 0 if the database is up to date, and 1 if an update is available.

Or, you can delete the local database entirely:

grype db delete

Searching the database

The Grype vulnerability database contains detailed information about vulnerabilities and affected packages across all supported ecosystems. While you can examine the raw SQLite database directly (use grype db status to find the local storage path), the grype db search commands provide a much easier way to explore what’s in the database.

Search for affected packages

Use grype db search to find packages affected by vulnerabilities. This is useful when you want to understand what packages are impacted by a specific CVE, or when you want to see all vulnerabilities affecting a particular package.

For example, to find all packages affected by Log4Shell across all ecosystems:

grype db search --vuln CVE-2021-44228

To find all vulnerable versions of the log4j package:

grype db search --pkg log4j

To search by PURL or CPE formats:

grype db search --pkg 'pkg:rpm/redhat/openssl'
grype db search --pkg 'cpe:2.3:a:jetty:jetty_http_server:*:*:*:*:*:*:*:*'

Any version value provided will be ignored entirely.

You can also use these options in combination to filter results further (finding the common intersection); in this example, finding packages named “openssl” in Alpine Linux 3.18 that have fixes available:

grype db search --pkg openssl --distro alpine-3.18 --fixed-state fixed

Search for vulnerabilities

Use grype db search vuln to look up vulnerability details directly, including descriptions, severity ratings, and data sources.

This is subtly different from searching for affected packages, as it focuses on the vulnerabilities themselves, so you can find information about vulnerabilities that may not affect any packages (there are a few reasons why this could happen.)

To view full metadata for a specific CVE:

grype db search vuln CVE-2021-44228

To filter by data provider:

grype db search vuln CVE-2021-44228 --provider nvd

Next steps

Now that you understand how Grype’s vulnerability database works, here are additional resources: