# Grype User Guide

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`.

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

Then scan a public image:

```bash
grype alpine:3.20
```

```text
 ✔ 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:
> ```bash
> 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

#### Install script (recommended for one-off installs)

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.

```bash
# 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:

```bash
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:

```bash
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`)

```bash
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`:

```bash
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.

```bash
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`):

```bash
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):

```bash
# 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:

```bash
grype version
```

```text
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
```

```bash
grype db update
```

```text
 ✔ Vulnerability DB                [updated]
Vulnerability database updated to latest version!
```

```bash
grype db status
```

```text
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
# 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.

```yaml
# .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":
> ```bash
> 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:

```yaml
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:

```bash
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:

```bash
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

```bash
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:

```bash
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:

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

A minimal CSV template:

```text
{{- 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.

```bash
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`

```bash
grype db check
```

```text
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`

```bash
grype db update
```

```text
Vulnerability database updated to latest version!
```

#### `grype db status`

```bash
grype db status -o json
```

```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`

```bash
grype db list
```

```text
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`

```bash
grype db delete
```

```text
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`

```bash
# 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`

```bash
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.

#### `grype db search`

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

```bash
# 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...`

```bash
grype db search vuln CVE-2021-44228 GHSA-jfh8-c2jp-5v3q
```

```text
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`

```bash
grype db providers
```

```text
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

```bash
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:**

```bash
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:**

```bash
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](https://github.com/anchore/syft) v1.0+ and grype
both installed.

**Steps:**

```bash
# 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:**

```yaml
# .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:**

```yaml
# .grype.yaml
ignore:
  - vulnerability: CVE-2024-99999
    reason: "vendor confirmed not exploitable in our deployment topology"
    package:
      name: openssl
      version: "3.0.*"
```

```bash
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:**

```bash
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:**

```bash
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:**

```bash
# 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:**

```bash
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:**

```bash
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`:

```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:**

```bash
# 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:

```yaml
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:**

```bash
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:**

```bash
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:**

```bash
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:**

```bash
# 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:**

```bash
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:**

```bash
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`:

```bash
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`:

```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:

```bash
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`:

```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:

```yaml
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.

---

_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. Unofficial; not affiliated with or reviewed by the project._
