Vulnerability Scanning grype vulnerabilities filtering vex

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:

Last modified November 26, 2025: allow local too invocation (d20d613)