Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Grype User Guide

Generated April 2026 from anchore/grype at 6d0b66c

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Grype is a vulnerability scanner from Anchore that finds known security issues in container images, filesystems, and Software Bills of Material (SBOMs). It catalogs every package in a target — operating-system packages (apk, dpkg, rpm, portage, pacman) and language ecosystem packages (Java, JavaScript, Python, Ruby, Go, .NET, Rust, Erlang/Elixir Hex, Bitnami) — matches each package against a curated vulnerability database, and prints the CVEs (or GHSAs, RHSAs, ELSAs, ALAS advisories, MSRC entries, etc.) that apply.

This guide covers everything an operator needs to install grype, configure it, run scans against any supported source, manage the vulnerability database, and integrate scans into CI/CD. For architecture, internals, and code-level details, see the companion Grype Administrator & Developer Guide.


1. Quick Start

The fastest path from "no grype" to "first scan" is the official install script: it pulls the latest release for your OS and architecture from GitHub, verifies the SHA-256 checksum, and drops the grype binary into ./bin/grype.

curl -sSfL https://get.anchore.io/grype | sh -s -- -b /usr/local/bin

Then scan a public image:

grype alpine:3.20
 ✔ Vulnerability DB                [no update available]
 ✔ Pulled image
 ✔ Loaded image                                                  alpine:3.20
 ✔ Parsed image           sha256:b0c9d60fc5e3e1dabe98d8a0a68cfe70c5d2a2a0b3a
 ✔ Cataloged contents               14e3a3...
   ├── ✔ Packages                        [15 packages]
   └── ✔ Executables                     [17 executables]
 ✔ Scanned for vulnerabilities     [0 vulnerability matches]
   ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible
   └── by status:   0 fixed, 0 not-fixed, 0 ignored

No vulnerabilities found

That's the entire workflow: point at a target, get a verdict.

Tip

If you don't want to install at all, run grype straight from the published Docker image — the database is downloaded into the container's cache on first run:

docker run --rm anchore/grype:latest alpine:3.20

What you'll need

  • [x] A 64-bit Linux, macOS, or Windows host (x86_64, arm64, ppc64le, or s390x)
  • [x] Outbound HTTPS (grype.anchore.io for the database, github.com for releases)
  • [x] Optional but recommended: Docker (or Podman) for image-source support
  • [x] ~200 MB of free disk space for the vulnerability database cache

Headline features

  • Scan anything: Docker images, OCI archives, OCI image directories, plain filesystems, single files, Singularity SIF containers, registry references (no daemon required), Syft JSON SBOMs, CycloneDX SBOMs, SPDX SBOMs, package URLs (PURLs), and CPEs.
  • Many ecosystems: apk, dpkg, rpm, portage, pacman, Bitnami, Java (Maven), JavaScript (npm), Python (PyPI), Ruby (RubyGems), Go modules, .NET, Rust crates, Erlang/Elixir Hex, plus Microsoft (MSRC) for KB-style updates.
  • Multiple output formats: table (default), json, cyclonedx-json, cyclonedx-xml, sarif, and Go-template formatting.
  • CI-ready exit codes: --fail-on critical returns exit code 2 so pipelines can break the build when a severity threshold is exceeded.
  • VEX-aware: ingest OpenVEX or CSAF VEX documents to suppress false-positive matches that you've already triaged.
  • Offline-friendly: the database is a single tar archive that can be mirrored, imported with grype db import, and used without internet.

What to try next

  • See the Installation section for package managers, distro repositories, and Docker variants.
  • See Common Tasks for ready-to-paste recipes including SARIF output for GitHub code scanning, SBOM-first pipelines, and air-gapped operation.
  • See the Admin Guide for the full configuration reference and security model.

2. Installation

Grype is a single Go binary with no runtime dependencies — installation is just "get the binary onto $PATH". Pick the method that matches how you manage other CLI tools.

2.1 System requirements

Requirement Detail
Operating system Linux (kernel 3.10+), macOS 11+, or Windows 10+
Architecture amd64, arm64, ppc64le (Linux only), s390x (Linux only)
Disk space ~50 MB for the binary, ~200 MB for the active vulnerability DB
Memory 256 MB minimum; 1–2 GB recommended for large images or multi-arch SBOMs
Network HTTPS to grype.anchore.io and github.com (only for installs and DB updates — scans themselves are local)
Optional Docker or Podman daemon if you want to scan local images by tag

Note

Grype releases ship as tar.gz archives for Linux/macOS, zip archives for Windows, plus signed .deb and .rpm packages. Container images are published to both Docker Hub (anchore/grype) and GHCR (ghcr.io/anchore/grype) in latest, nonroot, and debug flavors, covering amd64, arm64v8, ppc64le, and s390x.

2.2 Install methods

The official install.sh is the path used in the project's own documentation and CI. It infers your OS and architecture, downloads the right archive, verifies the SHA-256 checksum, optionally verifies the release signature with cosign, and installs the binary to a directory you choose.

# default: installs to ./bin/grype
curl -sSfL https://get.anchore.io/grype | sh -s --

# install to /usr/local/bin (system-wide)
curl -sSfL https://get.anchore.io/grype | sh -s -- -b /usr/local/bin

# pin a specific tag (do this in CI for reproducible builds)
curl -sSfL https://get.anchore.io/grype | sh -s -- -b /usr/local/bin v0.96.1

# enable cosign signature verification (requires `cosign` on PATH)
curl -sSfL https://get.anchore.io/grype | sh -s -- -v -b /usr/local/bin

The script accepts these flags: -b DIR (install dir, default ./bin), -d / -dd (debug / trace logging to stderr), -v (cosign signature verification, requires release ≥ v0.72.0 and the cosign binary on PATH), and a positional TAG argument to pin a release.

Important

Always install grype to a location that your CI runner has on $PATH and permission to execute. The default ./bin is a current-directory install — convenient locally, surprising in pipelines. Use -b /usr/local/bin (or ~/.local/bin for non-root installs) on shared runners.

Homebrew (macOS, Linux)

The release ships a Homebrew cask (not a formula) so the prebuilt upstream binary is used directly:

brew install anchore/grype/grype

Upgrades flow through brew upgrade grype. If you previously installed the old grype formula, the cask conflicts with it and Homebrew will tell you to uninstall the formula first.

Debian / Ubuntu (.deb)

The release publishes signed .deb packages alongside the tarballs:

curl -OL https://github.com/anchore/grype/releases/latest/download/grype_<version>_linux_amd64.deb
sudo dpkg -i grype_<version>_linux_amd64.deb

Replace <version> with the exact release version (e.g. 0.96.1) — the filenames embed the version number.

Red Hat / Fedora / Rocky / Alma (.rpm)

curl -OL https://github.com/anchore/grype/releases/latest/download/grype_<version>_linux_amd64.rpm
sudo rpm -i grype_<version>_linux_amd64.rpm

Manual binary install (any Linux/macOS/Windows)

Download the archive matching your OS/ARCH from the releases page, extract it, and place grype (or grype.exe on Windows) on $PATH:

curl -OL https://github.com/anchore/grype/releases/latest/download/grype_<version>_linux_amd64.tar.gz
tar -xzf grype_<version>_linux_amd64.tar.gz
sudo install -m 0755 grype /usr/local/bin/grype

Docker / OCI image

The published image is a self-contained binary plus the bare minimum to run it. The first scan downloads the vulnerability DB into the container's /.cache directory, so persist that directory across runs to avoid re-downloading on every invocation.

docker pull anchore/grype:latest

# scan a remote image (no Docker socket needed — uses registry mode internally)
docker run --rm anchore/grype:latest alpine:3.20

# scan a local image (mount the docker socket)
docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  anchore/grype:latest myorg/myimage:tag

# scan a local directory (mount the directory read-only)
docker run --rm -v "$PWD":/src:ro anchore/grype:latest dir:/src

# persist the DB cache across runs
docker run --rm \
  -v "$HOME/.cache/grype":/.cache/grype \
  anchore/grype:latest alpine:3.20

Image variants:

Tag Purpose
anchore/grype:latest Default — root user, full toolchain.
anchore/grype:nonroot Same binary, runs as a non-root UID for hardened CI runners.
anchore/grype:debug Shipped alongside latest for troubleshooting.
anchore/grype:<version> Pinned release. Always pin in production pipelines.
anchore/grype:<version>-nonroot Pinned non-root variant.

Build from source

You'll need Go 1.26.2 or newer (matching go.mod):

git clone https://github.com/anchore/grype
cd grype
go install ./cmd/grype

Or use the project's task runner (which wires up the same release pipeline the CI uses):

# install all required tools into ./.tool
task tools

# build a snapshot binary
task snapshot

Warning

A development build sets version to [not provided]. The grype version command will report this, the application-update check is skipped, and the install-script signature-verification flag is meaningless for that binary. Use task release (and the production CI pipeline) to produce a real release.

2.3 Post-install verification

Confirm the binary, then download the vulnerability database for your schema:

grype version
Application:        grype
Version:            0.96.1
BuildDate:          2026-04-21T18:42:11Z
GitCommit:          6d0b66c8...
GitDescription:     v0.96.1
Platform:           linux/amd64
GoVersion:          go1.26.2
Compiler:           gc
Syft Version:       v1.42.5-0.20260412145408-e4203224943d
Supported DB Schema: 6
grype db update
 ✔ Vulnerability DB                [updated]
Vulnerability database updated to latest version!
grype db status
Path:      /home/you/.cache/grype/db/6/vulnerability.db
Schema:    v6.0.0
Built:     2026-04-27T01:31:06Z
From:      https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst?checksum=sha256%3A...
Status:    valid

If Status: valid is reported, you're ready to scan. If it reports invalid, see the Troubleshooting section.

2.4 Shell completion

Grype ships a built-in completion generator for bash, zsh, and fish, with a custom completion that lists local Docker images by repo:tag — handy for grype <TAB>.

# bash (per-user)
grype completion bash > /etc/bash_completion.d/grype       # Linux, root
grype completion bash > /usr/local/etc/bash_completion.d/grype  # macOS

# zsh
echo "autoload -U compinit; compinit" >> ~/.zshrc
grype completion zsh > "${fpath[1]}/_grype"

# fish
grype completion fish | source
grype completion fish > ~/.config/fish/completions/grype.fish

2.5 Uninstall

Method Command
Install script sudo rm /usr/local/bin/grype (or whatever -b you used)
Homebrew brew uninstall --cask grype
.deb sudo apt remove grype
.rpm sudo rpm -e grype
Docker docker rmi anchore/grype:latest
Cache rm -rf ~/.cache/grype && rm -rf ~/.config/grype

3. Configuration

Grype is fully usable with zero configuration — defaults work for the common case. Configuration kicks in when you want to set fail thresholds, ignore lists, registry credentials, or matcher tuning persistently rather than passing flags every time.

3.1 Configuration sources, in precedence order

When grype starts, it merges values from these sources (later wins):

  1. Built-in defaults (compiled in).
  2. The first config file found via -c PATH, --config PATH, or in the default search path (see below).
  3. Environment variables prefixed GRYPE_* (with _ separator for nested keys — e.g. GRYPE_DB_AUTO_UPDATE=false, GRYPE_REGISTRY_AUTH_USERNAME).
  4. Command-line flags.

3.2 Config-file search order

If you don't pass -c PATH, grype looks for the first file that exists, in this order:

  1. ./.grype.yaml
  2. ./.grype/config.yaml
  3. ~/.grype.yaml
  4. ~/.config/grype/config.yaml
  5. /etc/grype/config.yaml (system-wide)

YAML, JSON, and TOML are all accepted (file extension determines format), but YAML is canonical and what every example below uses.

3.3 A practical starter config

This config sets sane CI-friendly defaults: JSON to stdout, fail on high-or-worse severity, ignore unfixed kernel-headers noise, and a sample ignore rule.

# .grype.yaml — typical CI configuration

# pick output format(s); use "<format>=<file>" for fan-out
output:
  - json

# break the build for High or Critical findings
fail-on-severity: high

# only show fixable vulnerabilities (drop unfixed/wont-fix)
only-fixed: true

# show suppressed/ignored matches in the table presenter
show-suppressed: false

# normalize the table by CVE rather than per-namespace ID
by-cve: true

# image platform for multi-arch repos
platform: linux/amd64

# database tuning
db:
  auto-update: true
  validate-age: true
  max-allowed-built-age: 120h
  update-url: https://grype.anchore.io/databases

# ignore rules (each rule must match all listed fields)
ignore:
  # silence a known false positive in our base image
  - vulnerability: CVE-2024-0001
    package:
      name: openssl
      version: "3.0.*"
  # ignore wont-fix matches for noise reduction
  - fix-state: wont-fix

Drop that into ~/.grype.yaml (user-wide) or ./.grype.yaml (repo-specific). Override individual values with flags or GRYPE_* environment variables on a per-invocation basis.

Tip

grype config (no args) prints the fully merged config — defaults plus everything from your file plus current env. Pipe it through less when you're hunting down "why is this setting wrong":

grype config | less

3.4 Environment variables you'll actually use

Every config key has a corresponding GRYPE_* env var. The most common:

Env var Equivalent flag/key Purpose
GRYPE_DB_CACHE_DIR db.cache-dir Override DB cache location (default ~/.cache/grype/db).
GRYPE_DB_AUTO_UPDATE db.auto-update Disable on-demand DB updates (set to false in air-gapped CI).
GRYPE_DB_UPDATE_URL db.update-url Point at a private mirror of the DB listing.
GRYPE_DB_CA_CERT db.ca-cert Custom CA cert for TLS to a private DB mirror.
GRYPE_REGISTRY_AUTH_AUTHORITY registry.auth[].authority Registry hostname.
GRYPE_REGISTRY_AUTH_USERNAME registry.auth[].username Registry username (sensitive — redacted in logs).
GRYPE_REGISTRY_AUTH_PASSWORD registry.auth[].password Registry password (sensitive — redacted in logs).
GRYPE_REGISTRY_AUTH_TOKEN registry.auth[].token Registry bearer token (sensitive — redacted in logs).
GRYPE_CHECK_FOR_APP_UPDATE check-for-app-update Disable the "new release available" notice.

The full list lives in the Admin Guide → Configuration Reference.

3.5 The .grype.yaml ignore-rules format

An ignore rule is a list entry under ignore:. A match must satisfy every specified field for the rule to apply (it's an AND). Empty fields are wildcards. Available fields:

ignore:
  - vulnerability: CVE-2024-1234   # CVE/GHSA/RHSA/etc. ID, regex allowed
    reason: "patched out of band"  # free-form, surfaces in JSON output
    namespace: nvd:cpe              # advisory namespace
    fix-state: wont-fix             # one of: fixed, not-fixed, wont-fix, unknown
    vex-status: not_affected        # one of: not_affected, fixed, affected, under_investigation
    package:
      name: openssl                  # package name (regex allowed)
      version: "1.1.1*"              # version glob
      type: rpm                      # package type (apk, deb, rpm, npm, ...)
      location: "/usr/lib/**"        # file glob
      upstream-name: openssl         # source/upstream package name
    match-type: exact-direct-match   # one of: exact-direct-match, exact-indirect-match, cpe-match

Warning

An ignore rule with no fields silently ignores every match. Always include at least vulnerability, package.name, or fix-state. Lint your config with grype config | grep -A1 ignore: before committing.

3.6 Distro hint

If grype can't determine the distro of a directory or SBOM target (no /etc/os-release, no PURL ?distro= qualifier), it'll warn and the OS matchers won't run. Force it:

grype dir:/path/to/rootfs --distro debian:11
grype dir:/path/to/rootfs --distro rhel:9.4+eus      # with a fix channel
grype dir:/path/to/rootfs --distro alpine@3.20       # @ separator
grype dir:/path/to/rootfs --distro ubuntu-focal      # codename allowed
grype dir:/path/to/rootfs --distro opensuse-leap:15.2 # multi-segment names handled

The accepted separators are -, :, and @. opensuse-leap is special-cased so the literal hyphen in the ID is preserved.


4. Core Usage

This section is the working reference: every input scheme grype accepts, every output format it produces, and every database subcommand.

4.1 The input model

A target reference can be given as a positional argument, piped on stdin, or — when scoped via a scheme prefix — read from a path or URL. The first positional argument is the user input string. If you pipe Syft JSON on stdin, grype reads from stdin and the positional arg is optional.

Scheme prefix What it scans Example
(none) Auto-detect: image tag, then dir, then file. grype alpine:3.20
docker: Force Docker daemon lookup. grype docker:alpine:3.20
podman: Force Podman daemon lookup. grype podman:alpine:3.20
docker-archive: A docker save tarball on disk. grype docker-archive:./image.tar
oci-archive: An OCI image tar (Podman/Skopeo). grype oci-archive:./image.tar
oci-dir: An OCI image layout directory. grype oci-dir:./oci-layout
singularity: A Singularity SIF container. grype singularity:./image.sif
dir: A filesystem directory. grype dir:./project
file: A single file on disk. grype file:./binary
sbom: A Syft, CycloneDX, or SPDX SBOM document. grype sbom:./syft.json
registry: Pull straight from a registry, no daemon. grype registry:ghcr.io/anchore/grype:latest
purl: A newline-separated file of PURLs. grype purl:./purls.txt
(PURL literal) A single Package URL on the command line. grype 'pkg:apk/openssl@3.2.1?distro=alpine-3.20.3'
cpes: A newline-separated file of CPEs. grype cpes:./cpes.txt
(CPE literal) A single CPE on the command line. grype 'cpe:2.3:a:openssl:openssl:3.0.14:*:*:*:*:*'

You can also pipe Syft output directly:

syft alpine:3.20 -o json | grype

Note

When the scheme is omitted, grype tries Docker first, then Podman, then directory/file detection. To skip the daemons entirely (e.g. on CI runners with no Docker socket), use --from registry,oci-dir,oci-archive or set GRYPE_DEFAULT_IMAGE_PULL_SOURCE=registry.

4.2 Top-level flags (root command)

These apply when grype is performing a scan (the root grype <target> invocation). DB-subcommand flags are listed under DB management.

Flag Type Default Purpose
-c, --config PATH string (search path) Use a specific config file.
-o, --output FMT[=FILE] repeatable table Output format. Repeat for fan-out (e.g. -o table -o json=out.json).
--file PATH string (stdout) Default output file (used when -o doesn't specify one).
--name NAME string (detected) Override the source name shown in reports.
--distro DISTRO string (detected) Force-set the target distro (alpine:3.20, rhel:9.4+eus).
--add-cpes-if-none bool false Synthesize CPEs for packages without them (broadens NVD matches).
--by-cve bool false Normalize the report by CVE rather than per-namespace advisory ID.
--exclude PATH repeatable (none) Glob of paths to skip when scanning a directory or image.
--fail-on SEVERITY enum (none) Exit 2 if any non-ignored match meets/exceeds this severity.
--only-fixed bool false Suppress matches with no available fix.
--only-notfixed bool false Suppress matches that do have a fix.
--ignore-states LIST csv (empty) Suppress matches with these fix-states (e.g. wont-fix,unknown).
--platform PLATFORM string (host) OS/arch when pulling multi-arch images (e.g. linux/arm64).
--show-suppressed bool false Include ignored matches in the table presenter.
--vex PATH repeatable (none) OpenVEX or CSAF VEX document to apply.
-q, --quiet bool false Suppress all logging output (still writes the report).
-v / -vv counted 0 Increase log verbosity (debug / trace).
-h, --help flag — Show help. The root help also embeds the full default config.
--version flag — Print version and exit.

Severity levels for --fail-on, in increasing order: negligible, low, medium, high, critical. (Anything with no severity classification is treated as below negligible for the threshold check.)

4.3 Output formats

grype alpine:3.20 -o table       # default, human-readable
grype alpine:3.20 -o json        # full Grype JSON document
grype alpine:3.20 -o cyclonedx-json  # CycloneDX SBOM with vulnerabilities
grype alpine:3.20 -o cyclonedx-xml   # CycloneDX XML
grype alpine:3.20 -o sarif       # GitHub-compatible SARIF 2.1.0
grype alpine:3.20 -o template -t ./report.tmpl  # Go template

You can request multiple formats in one run, optionally writing each to a separate file:

grype myimage:latest \
  -o table \
  -o json=results.json \
  -o sarif=results.sarif

The <format>=<file> syntax is parsed by parseOutputFlags in internal/format/writer.go. If a path's parent directory doesn't exist, grype will create it (mode 0755).

Available formats at a glance

Format Type Use case
table text Human-readable terminal output. Default.
json JSON Pipe to jq, grype explain, or downstream tooling.
cyclonedx-json JSON CycloneDX BOM with vulnerabilities array.
cyclonedx-xml XML CycloneDX XML BOM.
sarif JSON GitHub Code Scanning, Azure DevOps, JetBrains Qodana.
template text Custom output via Go's text/template (Sprig functions enabled).

Note

The embedded-cyclonedx-vex-json and embedded-cyclonedx-vex-xml formats from earlier releases have been folded into cyclonedx-json and cyclonedx-xml respectively. The old names still parse for backwards compatibility but emit a deprecation warning and will be removed at v1.0.

Table format

The default. One row per match, columns: NAME, INSTALLED, FIXED-IN, TYPE, VULNERABILITY, SEVERITY. With --show-suppressed, ignored matches are appended below.

JSON format

A single document with top-level keys: matches, ignoredMatches, source, distro, descriptor (which contains name, version, configuration, and db metadata). Stable schema — version is bumped explicitly.

CycloneDX (JSON & XML)

Produces a CycloneDX BOM where each component is a package and the vulnerabilities array lists matched advisories. The XML form includes both an embedded section for backwards compatibility and the standard CycloneDX vulnerability schema.

SARIF

A SARIF 2.1.0 document. Each result represents a vulnerability match with the package as the location. Compatible with GitHub's github/codeql-action/upload-sarif action and any tool that consumes SARIF.

Template

The most flexible format — write a Go template against the same data model the JSON presenter uses, with all Sprig functions available:

grype alpine:3.20 -o template -t ./csv.tmpl > report.csv

A minimal CSV template:

{{- range .Matches -}}
{{.Vulnerability.ID}},{{.Vulnerability.Severity}},{{.Artifact.Name}},{{.Artifact.Version}},{{ index .Vulnerability.Fix.Versions 0 | default "" }}
{{ end -}}

Variables you'll reach for: .Matches[].Vulnerability.{ID,Severity,Description,DataSource}, .Matches[].Vulnerability.Fix.{Versions,State}, .Matches[].Artifact.{Name,Version,Type,Locations}, .Source.{Type,Target}, .Distro.{Name,Version,IDLike}, .Descriptor.Configuration, .Descriptor.DB. See the Admin Guide for the full template variable reference.

4.4 DB management

Every grype scan needs a vulnerability database — a sqlite file plus metadata, packed into a tar.zst archive and published at https://grype.anchore.io/databases/v6/latest.json. By default grype auto-updates on first scan; you can also drive the cache manually.

grype db --help

Subcommands

Command Purpose
grype db check Is a newer DB available? Exits 100 if yes (CI-friendly).
grype db update Download and install the latest DB if newer.
grype db status Print local DB path, schema, build date, validity.
grype db list Print metadata for the latest published DB.
grype db delete Remove the local DB cache (forces re-download).
grype db import FILE|URL Install a DB from a local tarball or HTTPS URL.
grype db diff [old] [new] Compare two DBs (URLs or paths) and report added/removed/modified packages.
grype db search [args] Query the local DB for affected packages or vulnerabilities.
grype db search vuln ID... Look up specific vulnerability IDs in the local DB.
grype db providers List the data providers used to assemble the local DB.

grype db check

grype db check
Installed DB version v6.0.0 was built on 2026-04-25T01:31:06Z
Updated DB version v6.0.0 was built on 2026-04-27T01:31:06Z
You can run 'grype db update' to update to the latest db

Add -o json for machine-parseable output. The exit code is 100 when an update is available, 0 otherwise — useful in cron-style "warn me when new" jobs.

grype db update

grype db update
Vulnerability database updated to latest version!

grype db status

grype db status -o json
{
 "schemaVersion": "6.0.0",
 "from": "https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst?checksum=sha256%3A...",
 "built": "2026-04-27T01:31:06Z",
 "path": "/home/you/.cache/grype/db/6/vulnerability.db",
 "valid": true
}

grype db list

grype db list
Status:   active
Schema:   v6.0.0
Built:    2026-04-27T01:31:06Z
Listing:  https://grype.anchore.io/databases/v6/latest.json
DB URL:   https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst
Checksum: sha256:16bcb6551c748056f752f299fcdb4fa50fe61589d086be3889e670261ff21ca4

-o raw gives a one-line JSON array for piping into other tooling.

grype db delete

grype db delete
Vulnerability database deleted

Removes the DB directory at ~/.cache/grype/db/6/. The next scan or grype db update will re-download.

grype db import

# from a local archive (air-gapped install)
grype db import ./vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst

# from a URL with explicit checksum verification
grype db import 'https://mirror.example.com/grype/v6/vulnerability-db_v6.0.0.tar.zst?checksum=sha256:16bcb6551c748056f752f299fcdb4fa50fe61589d086be3889e670261ff21ca4'

If the URL has a checksum=sha256:<hex> query parameter, grype verifies the archive's digest against it before unpacking.

grype db diff

grype db diff /tmp/old/v6/vulnerability.db /tmp/new/v6/vulnerability.db

Outputs a table or JSON of which packages had vulnerabilities added, removed, or modified between two DB snapshots. Use --packages, --vulns, --epss, and --kev to narrow the diff to a single category.

The full search command supports filtering by vulnerability, package name, PURL, CPE, ecosystem, and OS:

# packages affected by a specific CVE
grype db search --vuln CVE-2021-44228

# packages by name (any ecosystem)
grype db search --pkg log4j

# narrow to one CVE in one package
grype db search --pkg log4j --vuln CVE-2021-44228

# by PURL
grype db search --pkg 'pkg:rpm/redhat/openssl'
grype db search --ecosystem rpm --pkg openssl

# by CPE
grype db search --pkg 'cpe:2.3:a:jetty:jetty_http_server:*:*:*:*:*:*:*:*'
grype db search --pkg 'cpe:/a:jetty:jetty_http_server'

--ecosystem accepts apk, deb, rpm, npm, python, gem, golang, maven, nuget, cargo, hex, etc. — any ecosystem grype catalogs.

grype db search vuln ID...

grype db search vuln CVE-2021-44228 GHSA-jfh8-c2jp-5v3q
ID                  PROVIDER       PUBLISHED   SEVERITY  REFERENCE
CVE-2021-44228      nvd            2021-12-10  Critical  https://nvd.nist.gov/...
GHSA-jfh8-c2jp-5v3q github         2021-12-10  Critical  https://github.com/...

Aliases: vulnerability, vulnerabilities, vulns. Schema v6+ only.

grype db providers

grype db providers
NAME       VERSION  PROCESSOR   DATE CAPTURED                  INPUT DIGEST
alpine     1.0.0    vunnel@3.2  2026-04-26 14:30:00 +0000 UTC  xxh64:1234567...
github     1.0.0    vunnel@3.2  2026-04-26 14:31:00 +0000 UTC  xxh64:abcdef0...
nvd        1.0.0    vunnel@3.2  2026-04-26 14:32:00 +0000 UTC  xxh64:9876543...

4.5 The grype explain command

grype alpine:3.20 -o json | grype explain --id CVE-2024-1234

A prototype command that pretty-prints reasoning about a specific CVE match: which packages were affected, why, what versions fix it, and the chain of references. It currently requires Grype JSON on stdin; future versions may scan and explain in one call.

Warning

grype explain is marked as a prototype feature in the source — its output format and flags may change between releases. Don't build automation that parses its text output; use -o json upstream and parse that instead.

4.6 Exit codes

Code Meaning
0 Scan completed successfully.
1 A non-recoverable error occurred (network, parse, invalid args).
2 Scan completed AND --fail-on threshold was met or exceeded.
100 (grype db check only) An update is available.

This is implemented in cmd/grype/cli/cli.go's WithMapExitCode. Use them to make CI workflows precise: 2 is "build broken because of severity", 1 is "infrastructure broke".


5. Common Tasks

Each recipe follows the same shape: Goal, Prerequisites, Steps, Verify, If it fails.

Recipe 1: Scan a public Docker image

Goal: confirm an image you're about to run has no critical CVEs.

Prerequisites: grype installed; outbound HTTPS for the first DB pull.

Steps:

grype alpine:3.20 --fail-on critical

Verify: exit code 0 means no critical CVEs were matched (a 2 breaks your CI). The summary line 0 critical, X high, ... confirms the scan ran.

If it fails:

  • Network error? Set GRYPE_DB_AUTO_UPDATE=false and retry against a pre-downloaded DB (see Recipe 11).
  • "no Docker daemon" error? Use registry: mode: grype registry:alpine:3.20.

Recipe 2: Scan a local filesystem directory

Goal: scan the unpacked rootfs of a custom system before flashing it.

Prerequisites: the rootfs at a known path, with a populated /etc/os-release file (or pass --distro).

Steps:

grype dir:/srv/firmware/rootfs --distro debian:12 -o json > scan.json

Verify: jq '.matches | length' scan.json returns the match count.

If it fails:

  • Warning "Unable to determine the OS distribution"? Pass --distro to force it.
  • "no packages found"? Confirm package metadata directories exist (/var/lib/dpkg, /var/lib/rpm, /lib/apk/db, /var/db/pkg).

Recipe 3: Scan via SBOM (the syft + grype pipeline)

Goal: generate one SBOM in CI, scan it many times — fast and auditable.

Prerequisites: syft v1.0+ and grype both installed.

Steps:

# generate the SBOM once
syft myorg/myimage:tag -o json > sbom.json

# scan the SBOM (no daemon, no registry calls)
grype sbom:./sbom.json --fail-on high

# or pipe directly without writing to disk
syft myorg/myimage:tag -o json | grype --fail-on high

Verify: the SBOM-driven scan produces the same matches as a direct image scan (compare jq '.matches | length' between both runs).

If it fails:

  • "unable to parse SBOM"? Confirm Syft output is JSON: syft ... -o json. Grype also accepts CycloneDX (-o cyclonedx-json) and SPDX (-o spdx-json).
  • Missing distro context? Re-run syft with the image to capture the linux-release cataloger output, or pass --distro on the grype side.

Recipe 4: Generate SARIF for GitHub code scanning

Goal: show vulnerability findings on the Security tab of a GitHub repo via Code Scanning.

Prerequisites: a GitHub repo with code scanning enabled; an actions workflow that builds the image.

Steps:

# .github/workflows/scan.yml
name: Vulnerability scan
on: [push]
permissions:
  security-events: write
  contents: read
jobs:
  grype:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t app:${{ github.sha }} .
      - name: Install grype
        run: curl -sSfL https://get.anchore.io/grype | sudo sh -s -- -b /usr/local/bin
      - name: Scan
        run: grype app:${{ github.sha }} -o sarif --file results.sarif
      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif

Verify: the Code scanning alerts view in the repo's Security tab shows any matches grype produced.

If it fails:

  • SARIF upload errors with "missing helpURI"? See GitHub issue #2874 — earlier releases left helpURI empty for some advisories. Upgrade to the latest grype to pick up the fix.
  • "Resource not accessible by integration"? You're missing permissions: security-events: write on the job.

Recipe 5: Author and apply an ignore-list

Goal: silence a known-not-applicable false positive without breaking the rest of the scan.

Prerequisites: a .grype.yaml in the repo root, the CVE ID, and ideally a reason that satisfies your audit team.

Steps:

# .grype.yaml
ignore:
  - vulnerability: CVE-2024-99999
    reason: "vendor confirmed not exploitable in our deployment topology"
    package:
      name: openssl
      version: "3.0.*"
grype myimage:latest -c ./.grype.yaml --show-suppressed

Verify: the suppressed match shows up under "(suppressed)" in the table view, and jq '.ignoredMatches[] | select(.match.vulnerability.id == "CVE-2024-99999")' scan.json finds it in the JSON output.

If it fails:

  • All matches disappear? Your rule has no fields and matches everything — add at least one specific selector.
  • Rule doesn't apply? Use --show-suppressed and check the package name and version regex match exactly what grype detected.

Recipe 6: Fail the build on severity in CI

Goal: any High or Critical match should fail the pipeline.

Prerequisites: grype installed in the CI runner; image already built.

Steps:

grype "$IMAGE_TAG" --fail-on high --only-fixed -o json --file scan.json
case $? in
  0) echo "no high/critical findings" ;;
  2) echo "BLOCKED: high/critical findings — see scan.json"; exit 1 ;;
  *) echo "scan errored"; exit 1 ;;
esac

Verify: an image you know has High vulnerabilities (e.g. an old debian:9 base) returns exit 2.

If it fails:

  • "discovered vulnerabilities at or above the severity threshold" but exit code is 1? Check that the run wasn't aborted before the threshold evaluation — e.g. by a flag parse error.

Recipe 7: Custom output template

Goal: emit findings as Markdown for a Slack notifier.

Prerequisites: grype installed; a template file.

Steps:

cat > slack.tmpl <<'EOF'
{{- if .Matches -}}
*Vulnerability findings for {{ .Source.Target }}:*
{{ range .Matches }}
• `{{.Artifact.Name}}@{{.Artifact.Version}}` — *{{.Vulnerability.Severity}}* {{.Vulnerability.ID}}
{{- end }}
{{- else -}}
No vulnerabilities found.
{{- end }}
EOF

grype myimage:latest -o template -t ./slack.tmpl

Verify: the output is plain Markdown ready to POST to a Slack webhook.

If it fails:

  • "function not defined" in template? Sprig's full library is available — check spelling. default, printf, lower, upper, replace, toJson all work.
  • "expected an integer"? Use {{ index .Vulnerability.Fix.Versions 0 }} rather than [0] indexing in Go templates.

Recipe 8: Scan an OCI tarball

Goal: scan an image that exists only as a .tar saved by Skopeo or Podman.

Prerequisites: the OCI tarball on disk.

Steps:

# OCI archive (from podman save or skopeo copy --format oci)
grype oci-archive:./my-image.tar

# Docker save tarball
grype docker-archive:./my-image.tar

# OCI image-layout directory (e.g. from `skopeo copy ... oci:./layout`)
grype oci-dir:./layout

Verify: the scan completes without making any network calls (other than DB updates, if enabled).

If it fails:

  • "unable to identify image source"? Confirm the archive is the format you claimed — try the other prefix (docker-archive: ↔ oci-archive:).
  • "no manifest found"? Ensure the tarball is not just layer blobs — it must include manifest.json or index.json.

Recipe 9: Scan a directory but exclude paths

Goal: scan a giant repo while skipping vendored deps and test fixtures.

Prerequisites: glob patterns for paths to skip.

Steps:

grype dir:./project \
  --exclude './**/node_modules/**' \
  --exclude './**/vendor/**' \
  --exclude './testdata/**'

Verify: the scan completes faster and the package count drops in the summary line.

If it fails:

  • Patterns don't seem to apply? Use ** (doublestar) globs and quote them to prevent shell expansion. Test with -vv to see which paths grype is visiting.

Recipe 10: Set the DB cache location

Goal: put the DB on a fast SSD or share it across CI runners on a networked volume.

Prerequisites: a writable directory.

Steps:

export GRYPE_DB_CACHE_DIR=/mnt/fast/grype-db
mkdir -p "$GRYPE_DB_CACHE_DIR"
grype db update
grype alpine:3.20

Or in .grype.yaml:

db:
  cache-dir: /mnt/fast/grype-db

Verify: grype db status reports the new path.

If it fails:

  • "permission denied"? The runner's UID needs read+write on cache-dir/v6/.
  • "no such file or directory"? Create the parent directory before the first db update.

Recipe 11: Mirror the DB on an air-gapped host

Goal: scan in environments with no internet access.

Prerequisites: an internet-connected host to fetch the DB; an internal mirror (HTTP server or filesystem path) reachable from the air-gapped host.

Steps:

# on internet-connected host
grype db list -o raw | jq -r '.[0].path' | xargs -I{} curl -OL "https://grype.anchore.io/databases/v6/{}"
# now copy the .tar.zst archive to the air-gapped host

# on air-gapped host
export GRYPE_DB_AUTO_UPDATE=false
grype db import ./vulnerability-db_v6.0.0_2026-04-27T01:31:06Z_1748234567.tar.zst

# scan as normal (no network calls)
grype dir:/srv/app

For repeated mirroring, host the listing and tarballs on an internal HTTPS server and point grype at the listing URL:

db:
  update-url: https://internal-mirror.corp/grype/v6/latest.json
  ca-cert: /etc/ssl/certs/internal-ca.crt

Verify: grype db status shows the imported DB; subsequent scans run without network access.

If it fails:

  • "checksum mismatch on import"? Re-fetch the archive — partial download or corruption.
  • "TLS handshake error"? The mirror's server certificate isn't trusted — set db.ca-cert to point at the issuing CA.

Recipe 12: JSON parsing for downstream tooling

Goal: extract just (package, version, CVE, severity, fix) rows for a spreadsheet.

Prerequisites: jq installed; grype JSON output.

Steps:

grype myimage:latest -o json | jq -r '
  .matches[] |
  [.artifact.name,
   .artifact.version,
   .vulnerability.id,
   .vulnerability.severity,
   (.vulnerability.fix.versions // [] | join("|"))]
  | @csv'

Verify: output is CSV with one row per match.

If it fails:

  • jq errors on null fields? Use // "" defaults like the example above (every field that may be absent gets a default).

Recipe 13: Distro override for a directory scan

Goal: scan a directory whose /etc/os-release is missing or wrong.

Prerequisites: know the actual distro and version.

Steps:

grype dir:/srv/rootfs --distro alpine:3.20
grype dir:/srv/rhel-9-rootfs --distro rhel:9.4+eus
grype dir:/srv/sles-rootfs --distro sles:15.5

Verify: the warning "Unable to determine the OS distribution" no longer appears.

If it fails:

  • "unknown distro"? Check the Admin Guide → Architecture for the full supported distro list. The string after --distro is the distro ID (debian, rhel, alpine, etc.) followed by -, :, or @ and a version or codename.

Recipe 14: Apply VEX documents

Goal: consume vendor-issued OpenVEX or CSAF VEX documents to suppress matches that have been triaged as not-affected.

Prerequisites: an OpenVEX or CSAF VEX file from your vendor; grype installed.

Steps:

grype myimage:latest --vex ./vex.json --vex ./second-vex.json

Verify: matches that the VEX document marks as not_affected or fixed move from matches to ignoredMatches in the JSON output. With --show-suppressed, they appear under "(suppressed)" in the table.

If it fails:

  • "VEX document not found"? Confirm the path; relative paths resolve from CWD.
  • "unsupported VEX document format"? grype currently supports OpenVEX (@context: "https://openvex.dev/ns/...") and CSAF VEX (document.csaf_version). Other VEX dialects aren't supported.
  • Need to opt in to keeping affected matches that VEX flags? Use vex-add: ["affected"] in .grype.yaml.

Recipe 15: Scan with a config file

Goal: apply a project-specific config without polluting environment variables.

Prerequisites: a .grype.yaml (or .grype/config.yaml) in the repo.

Steps:

# point at a specific file
grype myimage:latest -c ./.grype/ci-config.yaml

# or rely on the search path (./.grype.yaml is found automatically)
grype myimage:latest

# print the merged config for debugging
grype config

Verify: grype config shows your settings overriding the defaults.

If it fails:

  • Settings ignored? Check grype config to confirm which file was loaded — env vars can win over file values; flags always win over both.
  • YAML parse error? Run the file through yq eval or python -c "import yaml,sys; yaml.safe_load(sys.stdin)" < .grype.yaml to localize the syntax problem.

Recipe 16: Image platform override (multi-arch images)

Goal: scan the linux/arm64 variant of a multi-arch image from an amd64 host.

Prerequisites: outbound access to the registry hosting the image.

Steps:

grype myorg/myimage:tag --platform linux/arm64

Verify: the JSON source.target.architecture field reads arm64.

If it fails:

  • "no matching manifest" — confirm the platform string. Valid forms: linux/amd64, linux/arm64, linux/arm/v7, windows/amd64, etc.

Recipe 17: Generate missing CPEs to broaden NVD matching

Goal: find vulnerabilities for binaries grype catalogs without an explicit CPE (e.g. some Go binaries, custom-built packages).

Prerequisites: grype installed; a target with packages that lack CPEs.

Steps:

grype mybuild:latest --add-cpes-if-none

Verify: the match count rises for binary-only artifacts; review for false positives because CPE inference is heuristic.

If it fails:

  • Too many false positives? Pin to specific ecosystems via your matcher config (see Admin Guide → Configuration Reference). For Java in particular, set match.java.using-cpes: false.

6. Troubleshooting

Diagnostic decision tree

  1. Does grype version print a version? If not, the binary is missing from $PATH — re-check the install location.
  2. Does grype db status say valid? If not, run grype db delete && grype db update. If it still fails, see the DB-related entries below.
  3. Does the scan get past "Loaded image"? If not, the source can't be read — see "image source" entries.
  4. Does the matcher run but produce zero matches when you expect some? Try --add-cpes-if-none, --by-cve, or pass an explicit --distro.
  5. Is the exit code 2 when you didn't expect it? That's --fail-on firing. Drop or raise the threshold, or audit the matches that tipped it over.

Error: db could not be loaded: checksum mismatch

Cause: the local DB tarball was corrupted on download (network drop, partial write) or modified after install. Detected on the curator's validation pass.

Solution:

  1. Run grype db delete to wipe the cache.
  2. Run grype db update to re-download.
  3. If it persists, set GRYPE_DB_VALIDATE_CHECKSUM=false (NOT recommended for production) to confirm whether checksum is the issue.

Prevention: scope your CI cache key to include the DB schema version so stale artifacts don't poison rebuilds.

Error: failed to load vulnerability db: ... import.json

Cause: the DB cache is missing its import-metadata file, usually because a partial install was interrupted (Ctrl-C during grype db update). The notification reads Unable to find database import metadata and is emitted by validateDBLoad() in cmd/grype/cli/commands/root.go.

Solution:

  1. grype db delete
  2. grype db update
  3. Retry the scan.

Prevention: don't kill grype mid-update on shared volumes.

Error: Unable to determine the OS distribution of some packages

Cause: the target has dpkg/rpm/apk packages but no recognizable /etc/os-release, so the OS matchers can't run. Common with stripped filesystem images and bespoke base images.

Solution: pass --distro debian:11 (or whatever the actual distro is). For SBOMs, regenerate with syft --select-catalogers +linux-release to ensure distro detection runs.

Prevention: keep /etc/os-release in your minimal images — it's a few hundred bytes and unlocks correct vulnerability matching.

Error: no Docker daemon

Cause: the default scheme tried to reach Docker first; there's no socket. Common in CI runners using BuildKit-only setups or Kaniko.

Solution: prefix your target with registry: (no daemon required) or set GRYPE_DEFAULT_IMAGE_PULL_SOURCE=registry:

grype registry:myorg/myimage:tag

Prevention: in containerized CI jobs, always default to registry mode so daemon presence isn't a precondition.

Error: attempted CPE match against package with no CPEs

Cause: internal trace-level message. A matcher tried CPE-based search on a package that the cataloger didn't tag with any CPEs (typical for language-ecosystem packages where CPE is not natural).

Solution: this is normally a debug message, not an error. If you want CPE matches for those packages, run with --add-cpes-if-none. If you're seeing the message at WARN level, confirm your matcher config doesn't force CPE for the wrong ecosystem.

Prevention: none needed — the message is informational unless it correlates with missing matches you expected.

Error: discovered vulnerabilities at or above the severity threshold

Cause: --fail-on SEVERITY matched at least one finding. Exit code is 2. The error is grypeerr.ErrAboveSeverityThreshold and is the intended behavior — not a bug.

Solution: review scan.json's matches[] array for the offenders. Either fix the underlying packages, add ignore rules, or relax the threshold.

Prevention: keep --fail-on at the level your team has agreed to gate on. high is a reasonable default; critical is the floor.

Error: unsupported output format "<X>"

Cause: typo or deprecated format. The supported set is table, json, cyclonedx-json, cyclonedx-xml, sarif, template.

Solution: correct the spelling. Note that embedded-cyclonedx-vex-json is deprecated and resolves to cyclonedx-json (with a deprecation warning).

Prevention: lint your .grype.yaml output: list against the supported formats before merging configs.

Error: must specify at least one vulnerability ID (db search vuln)

Cause: grype db search vuln with no args.

Solution: pass at least one ID — grype db search vuln CVE-2021-44228.

Prevention: check the --help for any unfamiliar subcommand.

Error: provider not found: "<name>"

Cause: you filtered db search to a provider (--provider <name>) that isn't present in the local DB.

Solution: run grype db providers to see the actual list. Provider names are lowercase (alpine, nvd, github, redhat, ubuntu, etc.).

Prevention: drive provider lists from grype db providers -o json | jq -r '.[].name' rather than hard-coding strings.

Issue: false positive — Flask / Werkzeug from Python ecosystem

Cause: see GitHub issue #3354 — the Python ecosystem matcher matches via PURL on certain Werkzeug/Flask CVEs even when the installed version is patched, because the advisory range is broad.

Solution: add a targeted ignore in .grype.yaml:

ignore:
  - vulnerability: CVE-2024-34069
    package:
      name: werkzeug
      type: python
    reason: "false positive — see anchore/grype#3354"

Prevention: track upstream issue #3354 for a permanent fix, then remove the ignore.

Issue: false positive — GHSA-pxwf-m68q-gmw8 (CVE-2023-2004)

Cause: see GitHub issue #3250 — fuzzy package-name matching in some ecosystems produces this false positive against unrelated packages.

Solution: add an ignore rule scoped to the package name and ecosystem that's misfiring; subscribe to the upstream issue for a fix.

Issue: --only-fixed not effective for Python packages

Cause: see GitHub issue #3312 — the fix-state filter doesn't correctly filter all Python ecosystem advisories.

Solution: combine --only-fixed with --ignore-states wont-fix,unknown to compensate, or filter in your downstream tooling on .matches[].vulnerability.fix.state == "fixed".

Issue: SARIF output missing helpUri

Cause: see GitHub issue #2874 (closed in newer releases) — earlier versions left the SARIF result.helpUri field empty for some advisories.

Solution: upgrade to the latest grype release; the fix is included.

Prevention: pin grype versions in CI and bump them deliberately on a schedule.

Issue: false positive on Node.js 22 and CVE-2026-21710

Cause: see GitHub issue #3359 (closed) — the Node.js advisory range overlapped with already-fixed versions.

Solution: upgrade to a release after the issue's fix landed.

Issue: scan hangs on a large image

Cause: typically the cataloger phase reading a deep filesystem with many small files (Java archives are a common culprit because of the indexed/unindexed archive walk).

Solution: profile with -vv. To skip Java archive deep-indexing:

grype myimage:latest \
  --search-include-indexed-archives=false \
  --search-include-unindexed-archives=false

Or limit memory pressure on the runner.

Prevention: for very large monorepo scans, prefer the SBOM-first pipeline (Recipe 3) — generate the SBOM once with syft and scan it many times.

Issue: registry pull fails with 401/403

Cause: grype is anonymously pulling from a private registry.

Solution: add credentials via GRYPE_REGISTRY_AUTH_* env vars or in .grype.yaml:

registry:
  auth:
    - authority: ghcr.io
      token: ${GHCR_TOKEN}
    - authority: my-private.registry.local
      username: ci-bot
      password: ${REGISTRY_PASSWORD}

The username, password, and token fields are marked sensitive and redacted from grype's own logs by internal/redact.

Prevention: standardize on env vars in CI; never commit auth blocks with raw credentials.

Issue: grype completion doesn't tab-complete image names

Cause: completion is wired up but Docker isn't reachable to enumerate local images — dockerImageValidArgsFunction falls back to the default behavior.

Solution: confirm docker images works; the completion uses the same client. If you don't use Docker, this is expected.


7. FAQ

Q: How is grype different from syft?

Syft generates SBOMs (a list of packages); grype scans for vulnerabilities. They're sister tools from the same project — grype imports syft as a library to do its cataloging step. A typical pipeline: syft generates an SBOM once, grype scans the SBOM repeatedly.

Q: Where is the vulnerability database stored?

~/.cache/grype/db/<schema-version>/ by default — currently ~/.cache/grype/db/6/ for schema v6. Override with GRYPE_DB_CACHE_DIR or the db.cache-dir config key.

Q: How often is the database rebuilt?

Anchore publishes new builds multiple times per day. By default grype checks for updates on every scan, but caches the answer for 2 hours (max-update-check-frequency). Set db.auto-update: false in your config to pin a known-good DB.

Q: What's the difference between --only-fixed and --ignore-states wont-fix,not-fixed,unknown?

--only-fixed is a shorthand that adds the equivalent of --ignore-states wont-fix,not-fixed,unknown. They're functionally the same. Use whichever is clearer in your config.

Q: How do I exclude a specific path from a directory scan?

Use --exclude PATTERN (repeatable). The pattern is a doublestar glob — quote it to keep the shell from expanding it: --exclude './**/node_modules/**'.

Q: Can grype scan Windows containers?

Yes — grype itself runs on Windows, and the Windows distro type is handled (it appears in the Type enum). However, Windows containers in practice use very different package metadata and the matcher coverage is narrower than for Linux distros. The Microsoft Security Response Center (MSRC) matcher handles KB-style advisories.

Q: What's a "fix channel" (e.g. rhel:9.4+eus)?

A fix channel is a distinct vulnerability/fix feed for a distro variant. Red Hat's Extended Update Support (EUS) is the canonical example: RHEL 9.4 with EUS gets a different stream of CVE fixes than non-EUS 9.4. Grype honors the +eus suffix on the distro string and applies the EUS feed when present.

Q: What's the difference between cyclonedx-json and embedded-cyclonedx-vex-json?

They're identical now — embedded-cyclonedx-vex-json is the legacy name, kept for backwards compatibility, and emits a deprecation warning. Use cyclonedx-json. The embedded-* aliases will be removed at v1.0.

Q: What's --by-cve for?

By default each match is keyed by the advisory namespace (NVD, GHSA, RHSA, etc.). --by-cve collapses everything to the canonical CVE identifier so a single CVE doesn't appear multiple times for different data sources. Useful when downstream tooling expects per-CVE rows.

Q: What ecosystems does grype catalog?

apk, deb, rpm, portage, pacman; Java (Maven JARs/WARs/EARs), JavaScript (npm), Python (PyPI), Ruby (RubyGems), Go modules, .NET, Rust crates, Erlang/Elixir Hex, Bitnami packages, MSRC. The cataloger code lives in syft; the matcher code is in grype/matcher/.

Q: How do I make scans deterministic for a CI cache hash?

Pin both grype version and DB version. Pin grype with a specific install tag (curl ... | sh -s -- v0.96.1). Pin the DB by setting GRYPE_DB_AUTO_UPDATE=false and using grype db import ./pinned.tar.zst in your prebuild step.

Q: Does grype phone home?

Only for two purposes: (1) DB updates from grype.anchore.io (disable with db.auto-update: false), and (2) an application-update check (one GET to toolbox-data.anchore.io/grype/releases/latest/VERSION — disable with check-for-app-update: false). No telemetry, no analytics.

Q: Can I use grype offline?

Yes — see Recipe 11. grype db import accepts a local file or HTTPS URL. Set db.auto-update: false to prevent any network calls during scans.

Q: What's the relationship between schema versions and grype releases?

The DB schema version (currently 6) and the grype release version (currently 0.96.x) are independent. A grype release is built against a specific schema version; the binary contains a Supported DB Schema: line in grype version. When the schema bumps, all consumers must update grype before the new DB will load — see schema/grype/db/README.md in the repo for the SchemaVer rules.

Q: Why is grype db search vuln v6-only?

The query path uses v6 GORM models and SQL joins. Earlier schemas had a different on-disk layout and didn't expose the same indices.

Q: Where do EPSS and KEV data come from?

Schema v6+ embeds Exploit Prediction Scoring System (EPSS) scores and CISA's Known Exploited Vulnerabilities (KEV) catalog as additional columns on advisory records. They're surfaced in the JSON output and in grype db diff (--epss, --kev selectors).

Q: How do I write a regex in an ignore rule?

The vulnerability, package.name, package.upstream-name, and package.location fields all accept Go regex syntax. Anchor your patterns or you'll match more than you intended:

ignore:
  - vulnerability: ^CVE-2024-(0001|0002)$
    package:
      name: ^lib(crypto|ssl).*$

Q: Where do I report false positives?

Open an issue at https://github.com/anchore/grype/issues with the package name, ecosystem, version, the advisory ID, and a minimal reproducer (a small Dockerfile or SBOM is ideal). Many false-positive issues are tracked there — see #3354 (Flask/Werkzeug), #3250 (GHSA), #3312 (Python --only-fixed).

Q: How do I scan an OCI image directly from a registry without pulling first?

Use the registry: scheme — grype talks directly to the registry HTTP API without a local daemon: grype registry:ghcr.io/anchore/grype:latest. Auth via GRYPE_REGISTRY_AUTH_* or .grype.yaml.

Q: What does the message "EOL distro" mean?

When the alerts subsystem is enabled, grype warns when scanned packages come from a distro release that has reached end-of-life — vulnerability data may be stale or incomplete. The detection happens in VulnerabilityMatcher when Alerts.EnableEOLDistroWarnings is set. Toggle via alerts.enable-eol-distro-warnings in config.