# Grype Administrator & Developer Guide

This guide is for security engineers, platform engineers, and developers
operating grype as part of a software-supply-chain pipeline. It assumes
familiarity with the user-facing material covered in the **Grype User
Guide** and goes deeper into architecture, the full configuration surface,
deployment patterns, the security model, and the matcher internals.

The companion **Grype User Guide** covers installation, day-to-day usage,
and the recipe-driven cookbook. This document covers everything else:
how grype is built, how it decides whether a package is vulnerable, how
to operate it at scale, and how to extend or audit the codebase.

---

## 1. Architecture Overview

Grype is a Go binary with a clean pipeline: it accepts a target reference
(image, directory, SBOM, PURL, CPE), catalogs the packages in that target,
loads a vulnerability database, runs ecosystem-specific matchers across
the cartesian product of (packages × applicable advisories), filters out
ignored matches and VEX-attested matches, and emits the survivors in the
caller's chosen format.

### 1.1 Component diagram

```
                      ┌─────────────────────────────────────────┐
                      │              grype binary               │
                      │                                         │
   ┌────────────┐    │   ┌────────────┐    ┌────────────┐    │
   │   Target   │────┼──▶│  Provider  │───▶│  Catalog   │    │
   │ (image,    │    │   │ (syft)     │    │ (packages) │    │
   │  dir,      │    │   └────────────┘    └─────┬──────┘    │
   │  sbom,     │    │                            │           │
   │  PURL,     │    │   ┌────────────┐          ▼           │
   │  CPE)      │    │   │  vuln DB   │    ┌────────────┐    │
   └────────────┘    │   │ (sqlite v6)│───▶│  Matchers  │    │
                      │   └─────▲──────┘    │ (per       │    │
   ┌────────────┐    │         │            │  ecosystem) │    │
   │  Curator   │────┼─────────┘            └─────┬──────┘    │
   │ (download, │    │                            │           │
   │  validate, │    │   ┌────────────┐          ▼           │
   │  import)   │    │   │   Ignore   │    ┌────────────┐    │
   └────────────┘    │   │   rules    │───▶│ Match list │    │
         ▲           │   └────────────┘    └─────┬──────┘    │
         │           │   ┌────────────┐          │           │
   ┌─────┴──────┐    │   │    VEX     │          ▼           │
   │ grype.     │    │   │ documents  │───▶┌────────────┐    │
   │ anchore.io │    │   └────────────┘    │ Presenter  │    │
   │  /v6/      │    │                     │ (table,    │    │
   │ databases  │    │                     │  json,     │    │
   └────────────┘    │                     │  sarif,    │    │
                      │                     │  cyclonedx,│    │
                      │                     │  template) │    │
                      │                     └─────┬──────┘    │
                      └────────────────────────────│──────────┘
                                                   ▼
                                          ┌────────────┐
                                          │  stdout /  │
                                          │  file(s)   │
                                          └────────────┘
```

> [!NOTE]
> Grype's cataloging step is delegated to **syft** — both projects are
> developed by Anchore and grype imports syft as a Go module
> (`github.com/anchore/syft`). The vulnerability matching is what's unique
> to grype; the package extraction is shared.

### 1.2 Component inventory

| Component | Path | Role |
|---|---|---|
| Application bootstrap | `cmd/grype/main.go` | Wires build-time vars, hands off to the CLI. |
| CLI builder | `cmd/grype/cli/cli.go` | Constructs the cobra/clio command tree, exit-code map, UI selection. |
| Subcommands | `cmd/grype/cli/commands/` | Each cobra command: root scan, db.*, completion, explain. |
| Options & flags | `cmd/grype/cli/options/` | Every config struct, with `mapstructure` tags for clio binding. |
| TUI handler | `cmd/grype/cli/ui/handler.go` | Bubbletea-based progress UI for interactive runs. |
| Provider | `grype/pkg/` | Wraps syft to produce a `[]pkg.Package` from any input scheme. |
| Distro | `grype/distro/` | Distro identification, fix-channel logic, parse helpers. |
| Match | `grype/match/` | Core types: `Match`, `Matches`, `IgnoreRule`, `IgnoredMatch`. |
| Matcher | `grype/matcher/` | Per-ecosystem matchers (apk, dpkg, rpm, etc.). |
| Matcher internals | `grype/matcher/internal/` | Shared CPE/distro/language matching primitives. |
| Search | `grype/search/` | Query helpers used by matchers to interrogate the DB. |
| Vulnerability | `grype/vulnerability/` | The `Provider` interface and shared types. |
| DB curator (v6) | `grype/db/v6/installation/` | Download, validate, import, delete. |
| DB distribution (v6) | `grype/db/v6/distribution/` | Listing fetch, archive resolution. |
| DB diff (v6) | `grype/db/v6/diff/` | The `db diff` command's engine. |
| DB v5 (legacy) | `grype/db/v5/` | Older schema retained for compatibility. |
| Presenters | `grype/presenter/` | `table`, `json`, `cyclonedx`, `sarif`, `template`, `explain`. |
| Format dispatch | `internal/format/` | Format parsing + writer multiplex. |
| VEX | `grype/vex/` | OpenVEX and CSAF VEX processors. |
| Top-level matcher | `grype/vulnerability_matcher.go` | Orchestrates DB + VEX + ignore-rule application. |
| DB loader | `grype/load_vulnerability_db.go` | The function CLI uses to assemble a `vulnerability.Provider`. |
| Errors | `grype/grypeerr/` | Sentinel errors mapped to exit codes. |
| Bus | `internal/bus/` | partybus wrapper for events and notifications. |
| Logger | `internal/log/` | go-logger wrapper. |
| Redact | `internal/redact/` | Sensitive-string registry consulted by every log emitter. |
| Schema versions | `internal/schemaver/` | Parses/validates SchemaVer (`MODEL.REVISION.ADDITION`). |

### 1.3 Technology stack

| Layer | Choice | Why |
|---|---|---|
| Language | Go 1.26.2 | Single static binary, `embed` for templates, fast cold-start. |
| CLI | `spf13/cobra` v1.10.2 + Anchore `clio` | Cobra commands with clio gluing config, logging, and exit codes. |
| Config | `spf13/viper` v1.21.0 + Anchore `fangs` | YAML/JSON/TOML, env-var binding, default merging. |
| TUI | `charmbracelet/bubbletea` v1.3.10 + Anchore `bubbly` | Nonblocking progress UI when stdout is a TTY. |
| Cataloger | `anchore/syft` (pinned) | Shared codebase for package detection. |
| Image source | `anchore/stereoscope` (pinned) | Docker/Podman/registry/OCI archive abstraction. |
| DB ORM | `gorm.io/gorm` v1.31.1 + `glebarez/sqlite` v1.11.0 | GORM models drive both runtime queries and JSON-schema generation. |
| HTTP | `hashicorp/go-cleanhttp`, `hashicorp/go-getter` | Standardized HTTP transports, archive download. |
| Compression | `klauspost/compress`, `ulikunitz/xz`, `mholt/archives` | tar.zst, tar.gz, tar.xz handling for DB archives. |
| CycloneDX | `CycloneDX/cyclonedx-go` v0.10.0 | First-party CycloneDX serialization. |
| SARIF | `owenrumney/go-sarif` v1.1.2 | SARIF 2.1.0 output. |
| Versioning | Anchore `go-version` | Distro/package version comparison primitives. |
| Templating | `Masterminds/sprig/v3` v3.3.0 | Sprig functions for `-o template`. |
| OpenVEX | `openvex/go-vex` v0.2.8 | OpenVEX document parsing. |
| CSAF | `gocsaf/csaf/v3` v3.5.1 | CSAF VEX parsing. |

### 1.4 Data flow for a typical scan

1. `cmd/grype/main.go` constructs a `clio.Identification` and calls `cli.Application(...).Run()`.
2. `cmd/grype/cli/cli.go` builds the cobra root with subcommands, configures
   logging/UI, and sets `WithMapExitCode` (severity threshold → 2,
   DB-update-available → 100).
3. The root cobra command (`commands.Root`) dispatches `runGrype(ctx, ...)`.
4. `runGrype`:
   - Builds a `format.MakeScanResultWriter` from `--output`/`--file`.
   - Spawns a goroutine that calls `LoadVulnerabilityDB(...)` to produce a
     `vulnerability.Provider`.
   - Concurrently calls `pkg.Provide(userInput, providerConfig)` to get the
     `[]pkg.Package`, the `pkg.Context` (distro, source description), and a
     `*sbom.SBOM` for downstream CycloneDX rendering.
5. `applyVexRules(opts)` may append `IgnoreRule` entries derived from VEX
   options.
6. A `vex.NewProcessor(opts.VexDocuments, opts.Ignore)` is built — it
   selects OpenVEX or CSAF backend based on the first document's content.
7. A `grype.VulnerabilityMatcher` is instantiated with the provider, the
   matchers (one per ecosystem from `matcher.NewDefaultMatchers`), the
   ignore rules, the fail-on threshold, the VEX processor, and the alerts
   config.
8. `vulnMatcher.FindMatchesContext(ctx, packages, pkgContext)` runs each
   package through each matcher. For OS packages, the matcher uses distro +
   ecosystem to query the DB; for language packages, it uses ecosystem +
   PURL + (optionally) CPE.
9. Matches are filtered through `IgnoreRule` (and the implicit rules added
   by `--only-fixed`, `--only-notfixed`, `--ignore-states`).
10. The VEX processor moves `not_affected` / `fixed` matches from the live
    list to `ignoredMatches`, and conversely promotes `affected` /
    `under_investigation` with `vex-add`.
11. If `--fail-on` is set and any remaining match meets the threshold, the
    matcher returns `grypeerr.ErrAboveSeverityThreshold` (exit 2 — but the
    report is still written first).
12. `models.NewDocument(...)` builds the unified report data structure;
    `writer.Write(...)` fans it out to all configured presenters.

```
       [main.go]
           │
           ▼
 [cli.Application] ◀── identifies, sets up clio (flags, env, config, logging)
           │
           ▼
   [commands.Root] ──► runGrype(ctx, opts, userInput)
           │
   ┌───────┴────────┐
   ▼                ▼
[LoadDB]      [pkg.Provide]
   │                │
   ▼                ▼
 [Provider]   [Packages + Context + SBOM]
   │                │
   └─────┬──────────┘
         ▼
 [VulnerabilityMatcher.FindMatchesContext]
         │
         ├─► per-ecosystem [Matcher.Match()] using grype/search/* helpers
         ├─► [VEXProcessor.ApplyVEX()]
         ├─► [IgnoreRule] application
         └─► [hasSeverityAtOrAbove] → grypeerr.ErrAboveSeverityThreshold
         │
         ▼
   [models.NewDocument]
         │
         ▼
   [format.MakeScanResultWriter] ──► [Presenter] ──► stdout / file(s)
```

> [!NOTE]
> The pipeline is intentionally serial after the parallel
> "load-DB-while-cataloging" phase. Grype is a CLI not a service, so
> throughput optimization is at the matcher level (concurrent per-package
> matching) rather than at the request level.

---

## 2. System Requirements

### 2.1 Runtime

| Resource | Minimum | Recommended | Notes |
|---|---|---|---|
| CPU | 1 vCPU | 2–4 vCPUs | Matcher phase parallelizes across packages. |
| Memory | 256 MB | 1–2 GB | Large multi-arch images and the v6 DB load (~150 MB resident) are the drivers. |
| Disk (binary) | 50 MB | 100 MB | Binary + log space. |
| Disk (DB cache) | 200 MB | 500 MB | The active DB is ~150 MB; allow 2x for the download/extract cycle. |
| Disk (image scan) | 0 (registry mode) | 2× image size | Daemon scans use the daemon's storage; archive scans need scratch. |
| Outbound network | HTTPS to `grype.anchore.io` (DB) and `github.com` (releases) | Optional `toolbox-data.anchore.io` for app-update check. |
| Tmp space | 100 MB | 1 GB | DB extraction uses `os.TempDir()`. |

### 2.2 Build

| Requirement | Version |
|---|---|
| Go | 1.26.2 (matches `go.mod`) |
| `task` | v3.49.1 (managed via `binny`) |
| `goreleaser` | v2.15.2 (release builds) |
| `golangci-lint` | v2.11.4 (lint) |
| `cosign` | v3.0.5 (release signing) |
| `binny` | v0.13.0 (toolchain manager) |

`task tools` will install all of these into `./.tool/`.

### 2.3 Supported platforms

Release matrix from `.goreleaser.yaml`:

| OS | Architecture |
|---|---|
| Linux | `amd64`, `arm64`, `ppc64le`, `s390x` |
| macOS | `amd64`, `arm64` |
| Windows | `amd64` |

Container images: `amd64`, `arm64v8`, `ppc64le`, `s390x` for both `latest`,
`nonroot`, and `debug` flavors, on both Docker Hub and GHCR.

### 2.4 Supported distros (matcher coverage)

The full enumeration from `grype/distro/type.go`:

`alpine`, `amazonlinux`, `archlinux`, `azurelinux` (mariner v3+),
`busybox`, `centos`, `chainguard`, `debian`, `echo`, `fedora`, `gentoo`,
`hummingbird`, `mariner`, `minimos`, `oraclelinux`, `opensuseleap`,
`photon`, `postmarketos`, `raspbian`, `redhat`, `rockylinux`, `almalinux`,
`scientific`, `secureos`, `sles`, `ubuntu`, `windows`, `wolfi`.

Each of these has at least one fixture in `grype/distro/testdata/os/`
and is exercised by `Test_NewDistroFromRelease_Coverage`, which fails
if a defined distro type lacks a coverage fixture.

> [!IMPORTANT]
> The `windows` distro is detectable but has narrower vulnerability
> coverage than Linux distros — the MSRC matcher handles KB-style
> advisories. Don't expect parity with `apk`/`deb`/`rpm` matching.

### 2.5 Supported ecosystems (matcher list)

From `grype/matcher/matchers.go` (`NewDefaultMatchers`), in registration
order:

1. **dpkg** — Debian/Ubuntu/Raspbian (deb packages).
2. **ruby** — RubyGems.
3. **python** — PyPI (wheel/egg/dist-info).
4. **dotnet** — .NET assemblies + NuGet.
5. **rpm** — Red Hat / CentOS / Fedora / Rocky / Alma / Oracle / Mariner / Azure / SLES / OpenSUSE / Photon / Amazon Linux.
6. **java** — Java archives (jar/war/ear).
7. **javascript** — npm.
8. **apk** — Alpine, Wolfi, Chainguard.
9. **golang** — Go modules and binary build info.
10. **msrc** — Microsoft Security Response Center (Windows KBs).
11. **portage** — Gentoo.
12. **rust** — `cargo audit` data + binary auditable info.
13. **hex** — Erlang/Elixir Hex.
14. **stock** — fallback CPE-only matcher when ecosystem is unknown.
15. **bitnami** — Bitnami stack packages.
16. **pacman** — Arch Linux.

---

## 3. Configuration Reference

This section is the **complete** config surface. Every key here is honored
by `cmd/grype/cli/options/*` (the structs that clio binds against). Keys
prefixed `db.` apply to all `grype db ...` subcommands; the rest apply to
the root scan command.

### 3.1 Configuration files

| File | Purpose | Format |
|---|---|---|
| `./.grype.yaml` | Project-local config. | YAML (canonical), JSON, TOML. |
| `./.grype/config.yaml` | Alternative project-local. | YAML. |
| `~/.grype.yaml` | User-wide. | YAML. |
| `~/.config/grype/config.yaml` | XDG user config. | YAML. |
| `/etc/grype/config.yaml` | System-wide. | YAML. |

Search order is defined by the clio config layer (Anchore's
`fangs`/`viper` wrapper); the first existing file wins. Use `-c PATH` to
override the search.

### 3.2 Top-level scan configuration

| YAML key | Env var | CLI flag | Type | Default | Purpose |
|---|---|---|---|---|---|
| `output` | `GRYPE_OUTPUT` | `-o, --output` | list of strings | `[table]` | One or more output formats; supports `<format>=<file>` pairs. |
| `file` | `GRYPE_FILE` | `--file` | string | `""` | Default output path when `-o` doesn't specify one. |
| `output-template-file` | `GRYPE_OUTPUT_TEMPLATE_FILE` | `-t` | string | `""` | Path to Go template file for `-o template`. |
| `pretty` | `GRYPE_PRETTY` | `--pretty` | bool | `false` | Pretty-print JSON output. |
| `name` | `GRYPE_NAME` | `--name` | string | `""` | Override source name in reports. |
| `distro` | `GRYPE_DISTRO` | `--distro` | string | `""` | Force distro (`alpine:3.20`, `rhel:9.4+eus`, `ubuntu-focal`). |
| `add-cpes-if-none` | `GRYPE_ADD_CPES_IF_NONE` | `--add-cpes-if-none` | bool | `false` | Synthesize CPEs for missing-CPE packages. |
| `by-cve` | `GRYPE_BY_CVE` | `--by-cve` | bool | `false` | Normalize matches to canonical CVE IDs. |
| `exclude` | `GRYPE_EXCLUDE` | `--exclude` | list of strings | `[]` | Doublestar globs to skip. |
| `fail-on-severity` | `GRYPE_FAIL_ON_SEVERITY` | `--fail-on` | enum | `""` | `negligible`, `low`, `medium`, `high`, `critical`. |
| `only-fixed` | `GRYPE_ONLY_FIXED` | `--only-fixed` | bool | `false` | Drop unfixed matches (shorthand for `ignore-states`). |
| `only-notfixed` | `GRYPE_ONLY_NOTFIXED` | `--only-notfixed` | bool | `false` | Drop fixed matches. |
| `ignore-states` | `GRYPE_IGNORE_STATES` | `--ignore-states` | csv | `""` | Comma-separated fix states to drop: `fixed`, `not-fixed`, `wont-fix`, `unknown`. |
| `match-upstream-kernel-headers` | `GRYPE_MATCH_UPSTREAM_KERNEL_HEADERS` | — | bool | `false` | Disable the built-in linux kernel-headers ignores. |
| `platform` | `GRYPE_PLATFORM` | `--platform` | string | `""` | OS/arch for multi-arch image pulls. |
| `default-image-pull-source` | `GRYPE_DEFAULT_IMAGE_PULL_SOURCE` | — | string | `""` | When omitting a scheme: `docker`, `podman`, `registry`. |
| `from` | `GRYPE_FROM` | `--from` | csv | `""` | Restrict source providers (e.g. `registry,oci-archive`). |
| `show-suppressed` | `GRYPE_SHOW_SUPPRESSED` | `--show-suppressed` | bool | `false` | Show ignored matches in the table presenter. |
| `vex-documents` | `GRYPE_VEX_DOCUMENTS` | `--vex` | list | `[]` | OpenVEX or CSAF document paths. |
| `vex-add` | `GRYPE_VEX_ADD` | — | list of strings | `[]` | VEX statuses to also keep: `affected`, `under_investigation`. |
| `check-for-app-update` | `GRYPE_CHECK_FOR_APP_UPDATE` | — | bool | `true` | Disable to suppress release-update notice. |
| `quiet` | — | `-q, --quiet` | bool | `false` | Suppress all logs. |
| `log.verbosity` | `GRYPE_LOG_VERBOSITY` | `-v` (counted) | int | `0` | 0=warn, 1=info, 2=debug, 3=trace. |
| `log.level` | `GRYPE_LOG_LEVEL` | — | string | `"warn"` | Explicit level: `error`, `warn`, `info`, `debug`, `trace`. |
| `log.file` | `GRYPE_LOG_FILE` | — | string | `""` | Mirror logs to a file. |
| `log.structured` | `GRYPE_LOG_STRUCTURED` | — | bool | `false` | Emit JSON-formatted log lines. |
| `config` | — | `-c, --config` | string | (search path) | Override config-file search. |
| `timestamp` | `GRYPE_TIMESTAMP` | — | RFC3339 string | (now) | Override the report timestamp (testing/reproducibility). |
| `sort-by.criteria` | `GRYPE_SORT_BY_CRITERIA` | — | string | `severity` | Match-list sort: `severity`, `package`, `vulnerability`, `cve`. |

### 3.3 `db.*` configuration

| YAML key | Env var | Type | Default | Purpose |
|---|---|---|---|---|
| `db.cache-dir` | `GRYPE_DB_CACHE_DIR` | string | `~/.cache/grype/db` | Where the DB lives on disk. |
| `db.update-url` | `GRYPE_DB_UPDATE_URL` | URL | `https://grype.anchore.io/databases` | DB listing root. |
| `db.ca-cert` | `GRYPE_DB_CA_CERT` | path | `""` | Custom CA for TLS to private mirror. |
| `db.auto-update` | `GRYPE_DB_AUTO_UPDATE` | bool | `true` | Whether to check for new DB on every scan. |
| `db.validate-age` | `GRYPE_DB_VALIDATE_AGE` | bool | `true` | Reject DBs older than `max-allowed-built-age`. |
| `db.max-allowed-built-age` | `GRYPE_DB_MAX_ALLOWED_BUILT_AGE` | duration | `120h` | How old is "too old"? |
| `db.update-check-max-frequency` | `GRYPE_DB_UPDATE_CHECK_MAX_FREQUENCY` | duration | `2h` | Min interval between update checks. |
| `db.validate-checksum` | `GRYPE_DB_VALIDATE_CHECKSUM` | bool | `true` | Validate sha256 of downloaded archives. |
| `db.require-update-check` | `GRYPE_DB_REQUIRE_UPDATE_CHECK` | bool | `false` | Fail closed if update check fails. |
| `db.user-agent` | `GRYPE_DB_USER_AGENT` | string | `"<app> <version>"` | HTTP User-Agent for DB fetches. |

### 3.4 `registry.*` configuration

| YAML key | Env var | Type | Purpose |
|---|---|---|---|
| `registry.insecure-skip-tls-verify` | `GRYPE_REGISTRY_INSECURE_SKIP_TLS_VERIFY` | bool | Skip TLS verification (NOT recommended for prod). |
| `registry.insecure-use-http` | `GRYPE_REGISTRY_INSECURE_USE_HTTP` | bool | Connect over HTTP instead of HTTPS. |
| `registry.ca-cert` | `GRYPE_REGISTRY_CA_CERT` | path | CA cert (or directory of `*.crt`/`*.cert`/`*.pem`). |
| `registry.auth[].authority` | `GRYPE_REGISTRY_AUTH_AUTHORITY` | string | Registry hostname (e.g. `ghcr.io`, `localhost:5000`). |
| `registry.auth[].username` | `GRYPE_REGISTRY_AUTH_USERNAME` | string (sensitive) | Username for basic auth. |
| `registry.auth[].password` | `GRYPE_REGISTRY_AUTH_PASSWORD` | string (sensitive) | Password for basic auth. |
| `registry.auth[].token` | `GRYPE_REGISTRY_AUTH_TOKEN` | string (sensitive) | Bearer token (mutually exclusive with username/password). |
| `registry.auth[].tls-cert` | `GRYPE_REGISTRY_AUTH_TLS_CERT` | path | Client TLS cert. |
| `registry.auth[].tls-key` | `GRYPE_REGISTRY_AUTH_TLS_KEY` | path | Client TLS key. |

The env-var auth credentials are **prepended** to the file-defined `auth`
list so env vars take precedence (`registry.go::PostLoad`).

> [!IMPORTANT]
> The `username`, `password`, and `token` fields are typed as `secret`,
> which causes them to be redacted from any log line emitted via
> `internal/log` thanks to `internal/redact`. Inspect the redact registry
> with `-vv` to confirm a value is registered for redaction.

### 3.5 `match.*` matcher tuning

Per-ecosystem matcher knobs:

| YAML key | Type | Default | Purpose |
|---|---|---|---|
| `match.java.using-cpes` | bool | `false` | Use CPEs in addition to PURL/Maven coordinates for Java matching. |
| `match.java.external-search.search-maven-upstream` | bool | `false` | Query Maven Central for missing artifact metadata. |
| `match.java.external-search.maven-base-url` | URL | `https://search.maven.org/solrsearch/select` | Maven search endpoint. |
| `match.java.external-search.maven-rate-limit` | duration | `300ms` | Throttle for Maven queries. |
| `match.ruby.using-cpes` | bool | `false` | CPE matching for Ruby gems. |
| `match.python.using-cpes` | bool | `false` | CPE matching for Python packages. |
| `match.dotnet.using-cpes` | bool | `false` | CPE matching for .NET assemblies. |
| `match.javascript.using-cpes` | bool | `false` | CPE matching for npm packages. |
| `match.golang.using-cpes` | bool | `false` | CPE matching for Go modules. |
| `match.golang.always-use-cpe-for-stdlib` | bool | `true` | Special-case Go's stdlib. |
| `match.golang.allow-main-module-pseudo-version-comparison` | bool | `false` | Compare against pseudo-versions of `main` module. |
| `match.hex.using-cpes` | bool | `false` | CPE matching for Erlang/Elixir Hex. |
| `match.stock.using-cpes` | bool | `true` | CPE matching for unrecognized ecosystems (the fallback). |
| `match.dpkg.missing-epoch-strategy` | string | `"zero"` | One of: `zero`, `auto`. How to compare versions where one side lacks an epoch. |
| `match.dpkg.use-cpes-for-eol` | bool | `false` | Fall back to CPE matching when distro is EOL and feed is stale. |
| `match.rpm.missing-epoch-strategy` | string | `"auto"` | Same options as dpkg; `auto` infers from package metadata. |
| `match.rpm.use-cpes-for-eol` | bool | `false` | CPE fallback for EOL RPM distros. |

### 3.6 `external-sources.*` configuration

| YAML key | Type | Default | Purpose |
|---|---|---|---|
| `external-sources.enable` | bool | `false` | Master switch for external network calls during matching. |
| `external-sources.maven.search-upstream-by-sha1` | bool | `false` | Query Maven Central by JAR sha1 for unidentified Java archives. |
| `external-sources.maven.base-url` | URL | `https://search.maven.org/solrsearch/select` | Maven search endpoint. |
| `external-sources.maven.rate-limit` | duration | `300ms` | Throttle. |

### 3.7 `search.*` configuration

| YAML key | Type | Default | Purpose |
|---|---|---|---|
| `search.scope` | string | `"squashed"` | Image scan scope: `squashed` (effective rootfs) or `all-layers`. |
| `search.include-indexed-archives` | bool | `true` | Walk into indexed Java archives (jar/war/ear). |
| `search.include-unindexed-archives` | bool | `false` | Walk into archives without an explicit index (slow). |

### 3.8 `alerts.*` configuration

| YAML key | Type | Default | Purpose |
|---|---|---|---|
| `alerts.enable-eol-distro-warnings` | bool | `false` | Warn when scanning packages from EOL distros. |

### 3.9 `developer.*` configuration

| YAML key | Type | Purpose |
|---|---|---|
| `developer.profile` | string | Enable Go pprof: `cpu`, `mem`, `block`, `mutex`, `trace`. |
| `developer.db.debug` | bool | Verbose DB logging during build/diff. |

### 3.10 `ignore[]` configuration

Authoritative shape from `grype/match/ignore.go`:

```yaml
ignore:
  - vulnerability: CVE-2024-1234       # regex; matches against ID and aliases
    namespace: nvd:cpe                  # regex; data-source 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
    reason: "vendor confirmed not exploitable"   # free-form, surfaces in JSON
    match-type: exact-direct-match      # one of: exact-direct-match, exact-indirect-match, cpe-match
    package:
      name: openssl                      # regex
      version: "3.0.*"                   # version glob
      type: rpm                          # syft package type
      location: "/usr/lib/**"            # doublestar glob
      upstream-name: openssl             # source/upstream name
```

A rule fires only if **every** specified field matches; absent fields are
wildcards. Empty rules match every match — the validator emits a warning
but doesn't refuse to run.

### 3.11 An exhaustive annotated config

```yaml
# == top-level scan options ==
output:
  - table
  - json=results.json
  - sarif=results.sarif
file: ""
output-template-file: ""
pretty: false
name: ""
distro: ""
add-cpes-if-none: false
by-cve: false
exclude:
  - "**/node_modules/**"
  - "**/vendor/**"
fail-on-severity: high
only-fixed: false
only-notfixed: false
ignore-states: ""
match-upstream-kernel-headers: false
platform: ""
default-image-pull-source: ""
from: ""
show-suppressed: false
vex-documents: []
vex-add: []
check-for-app-update: true
quiet: false
sort-by:
  criteria: severity
timestamp: ""

# == logging ==
log:
  verbosity: 0
  level: warn
  file: ""
  structured: false

# == database ==
db:
  cache-dir: ~/.cache/grype/db
  update-url: https://grype.anchore.io/databases
  ca-cert: ""
  auto-update: true
  validate-age: true
  max-allowed-built-age: 120h
  update-check-max-frequency: 2h
  validate-checksum: true
  require-update-check: false
  user-agent: ""

# == registry auth ==
registry:
  insecure-skip-tls-verify: false
  insecure-use-http: false
  ca-cert: ""
  auth:
    - authority: ghcr.io
      username: ""
      password: ""
      token: ""
      tls-cert: ""
      tls-key: ""

# == matcher tuning ==
match:
  java:
    using-cpes: false
    external-search:
      search-maven-upstream: false
      maven-base-url: https://search.maven.org/solrsearch/select
      maven-rate-limit: 300ms
  ruby:
    using-cpes: false
  python:
    using-cpes: false
  dotnet:
    using-cpes: false
  javascript:
    using-cpes: false
  golang:
    using-cpes: false
    always-use-cpe-for-stdlib: true
    allow-main-module-pseudo-version-comparison: false
  hex:
    using-cpes: false
  stock:
    using-cpes: true
  dpkg:
    missing-epoch-strategy: zero
    use-cpes-for-eol: false
  rpm:
    missing-epoch-strategy: auto
    use-cpes-for-eol: false

# == external sources ==
external-sources:
  enable: false
  maven:
    search-upstream-by-sha1: false
    base-url: https://search.maven.org/solrsearch/select
    rate-limit: 300ms

# == search scope ==
search:
  scope: squashed
  include-indexed-archives: true
  include-unindexed-archives: false

# == alerts ==
alerts:
  enable-eol-distro-warnings: false

# == fix channels ==
fix-channel:
  rhel-eus:
    apply: auto
    versions: ">= 8.0"

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

# == developer-only ==
developer:
  profile: ""
  db:
    debug: false
```

Print the merged config (defaults + file + env) at any time with `grype
config`.

---

## 4. Deployment & Scaling

Grype is a CLI; "deployment" means orchestrating it in CI/CD pipelines,
scheduled scanners, and registry-event hooks rather than running a daemon.

### 4.1 Reference deployment shapes

| Shape | Where it runs | Cadence | Notes |
|---|---|---|---|
| **Inline CI scan** | GitHub Actions / GitLab CI / Jenkins on every PR | Per build | Use `--fail-on` to block merges. |
| **Image promotion gate** | Pre-push hook in image registry workflow | Per release | Scan the candidate; promote only on pass. |
| **Scheduled re-scan** | Cron job per registry repo | Daily/hourly | Catches new CVEs in already-published images. |
| **Registry webhook** | Triggered by Harbor/Quay/ECR push events | Event-driven | Lambda/Cloud Run/Knative service that runs grype and reports. |
| **SBOM-first pipeline** | syft once, grype many | Once per build | Decouple cataloging from scanning. |
| **Air-gapped enclave** | Internal-only network | Daily DB sync | Use `db import` from an internal mirror. |

### 4.2 Sizing rules of thumb

- A typical scan of an `alpine:3.20`-based image: ~5–10 seconds end-to-end on a 2-vCPU runner, with the DB already cached.
- A heavy multi-language monorepo (`node_modules` + Python venvs + Java jars): 1–5 minutes; cataloging dominates.
- Memory peaks during JSON serialization at ~2× the resident size of the package list — large SBOMs (>10k packages) can momentarily push 1–2 GB.
- DB load time is ~200–500 ms after the file is on disk.
- The DB archive download is ~150 MB; budget 30s on a typical CI runner.

### 4.3 Caching strategies

The DB is the single biggest cold-start cost. Cache it aggressively:

| Runner | Strategy |
|---|---|
| GitHub Actions | `actions/cache` with key `grype-db-v6-${{ runner.os }}` and path `~/.cache/grype/db`. |
| GitLab CI | `cache.paths: [.cache/grype/db/]` with `key: grype-db-v6`. |
| Jenkins | A persistent agent volume mounted into the workspace at `~/.cache/grype/db`. |
| Buildkite | `BUILDKITE_PLUGIN_CACHE_PATH=~/.cache/grype/db`. |
| Self-hosted | NFS-mounted `cache-dir` shared by all runners. |

Set `db.update-check-max-frequency: 2h` (default) so concurrent jobs don't
all hammer the listing endpoint.

> [!TIP]
> If you have many parallel runners, mirror the DB internally (Recipe 11
> in the User Guide) and point all runners at the mirror. The Anchore
> public DB serves a CDN-fronted file but a single internal Nginx cache
> reduces cross-team rate-limit risk.

### 4.4 Container-runtime considerations

When running grype inside a container:

```bash
docker run --rm \
  -v "$HOME/.cache/grype":/.cache/grype \
  -v /var/run/docker.sock:/var/run/docker.sock \
  anchore/grype:latest myorg/myimage:tag
```

- The `nonroot` image variant runs as UID 65532. If your DB cache mount is owned by a different user, it'll fail to write — `chown -R 65532:65532` the mount, or use the default `latest` (root) variant.
- The Docker socket mount is privileged — be sure you trust the image. Use the `registry:` scheme to avoid mounting the socket entirely.
- The published images are signed with cosign and SBOM-attested via the GoReleaser pipeline.

### 4.5 Scaling-out patterns

Grype itself is single-process per scan. Throughput scales by running
multiple grype processes:

- **Image registries (multi-tag)**: run grype once per tag in parallel; each process gets its own DB read.
- **SBOM batches**: with the same `cache-dir`, multiple grype processes share the DB on disk. SQLite reads are concurrent (the v6 sqlite is `WAL`-mode).
- **Distributed scanning**: a queue (SQS/RabbitMQ/Kafka) of "scan this image" tasks, with workers running grype. Each worker maintains a local DB cache and pulls updates on a schedule.

### 4.6 Determinism & reproducibility

- **Pin grype version** in CI (`curl ... | sh -s -- v0.96.1`). Don't track `latest`.
- **Pin DB**: download once, `grype db import`, set `db.auto-update: false`. Re-pin on a cadence.
- **Pin syft version**: indirect — grype's syft import is locked in `go.mod`, so pinning grype pins syft.
- **Override timestamp**: `GRYPE_TIMESTAMP=2026-04-28T00:00:00Z` for reproducible JSON output (otherwise `descriptor.timestamp` differs between runs).

---

## 5. Operations

### 5.1 Operational checklists

Pre-flight checklist for a new CI integration:

- [ ] Pin grype version (`v0.x.y`) — never track `latest` in pipeline scripts.
- [ ] Cache `~/.cache/grype/db` between runs.
- [ ] Set `--fail-on` to a defined threshold; document the policy in your repo.
- [ ] Set `--only-fixed` if you only act on actionable findings; otherwise document why "wont-fix" matches are kept.
- [ ] Configure registry auth via `GRYPE_REGISTRY_AUTH_*` (never commit auth blocks).
- [ ] If the runner has no Docker daemon, prefer `registry:` scheme and `GRYPE_DEFAULT_IMAGE_PULL_SOURCE=registry`.
- [ ] Upload SARIF to GitHub Code Scanning (or equivalent) for visibility.
- [ ] Decide whether to commit `.grype.yaml` (recommended) or pass flags inline.

Daily operations checklist:

- [ ] `grype db check` exits 0 (no drift).
- [ ] `grype version` matches the pinned version.
- [ ] Recent scans exit 0 or 2 (never 1 — `1` means infrastructure failure).
- [ ] Disk usage at `~/.cache/grype/db/6/` < 500 MB.

### 5.2 Logging

Logs go to stderr by default. Levels (set via `-v` count or
`log.verbosity`):

| `-v` | Level | What you'll see |
|---|---|---|
| 0 | warn | Warnings and errors. |
| 1 (`-v`) | info | Phase-level progress + above. |
| 2 (`-vv`) | debug | Internal decisions, DB lookups, matcher routing. |
| 3 (`-vvv`) | trace | Per-package, per-rule decisions — very chatty. |

Mirror logs to a file:

```yaml
log:
  verbosity: 2
  file: /var/log/grype/scan.log
  structured: true   # JSON Lines
```

Structured logs include `time`, `level`, `msg`, plus `WithFields` keys —
parseable by Loki, Splunk, ELK, etc.

> [!NOTE]
> Sensitive fields like registry passwords and tokens are registered with
> `internal/redact` and replaced in log output before emission. Don't
> assume nothing's been logged — the redact step *removes* the value but
> leaves placeholders so the original log message structure is preserved.

### 5.3 Metrics & monitoring

Grype doesn't expose Prometheus metrics; for ops dashboards, parse the
JSON output:

```bash
grype myimage:latest -o json | jq '
  {
    matches: (.matches | length),
    ignored: (.ignoredMatches | length),
    bySeverity: ([.matches[] | .vulnerability.severity] | group_by(.) | map({key: .[0], value: length}) | from_entries)
  }'
```

Pipe that through your metrics ingestion. For Prometheus, a sidecar
script can scrape JSON outputs and `pushgateway` them.

### 5.4 Updates & upgrades

| Component | Update path |
|---|---|
| Grype binary | `curl -sSfL https://get.anchore.io/grype \| sudo sh -s -- -b /usr/local/bin v0.96.1` (pin a tag). |
| DB | `grype db update` (auto-runs unless `db.auto-update: false`). |
| Schema | When the schema bumps, **upgrade grype first** before the new DB will load. The release notes flag schema changes prominently. |
| Container image | `docker pull anchore/grype:v0.96.1` (always pin in production). |

### 5.5 Backups & disaster recovery

The DB is downloadable, so a "backup" is really "the published archive
URL". To restore:

1. `grype db delete` (clear corrupted state).
2. `grype db update` (or `grype db import <archive>`).
3. `grype db status` to confirm.

Keep a copy of `vulnerability-db_v6.X.Y_<timestamp>_<id>.tar.zst` from a
known-good day if you're paranoid about upstream availability.

### 5.6 Runbooks

#### Runbook: DB updates failing in CI

1. SSH (or kubectl exec) into a runner.
2. Run `grype db check -vv 2>&1 | tee /tmp/db-check.log`.
3. If DNS or TLS errors: check egress firewall rules to `grype.anchore.io`.
4. If `checksum` errors: `grype db delete && grype db update`.
5. If persistent: switch to mirror via `GRYPE_DB_UPDATE_URL=...`.
6. File the incident with the log.

#### Runbook: Scan suddenly shows 100s of new "Critical" findings

1. Check `grype version` and `grype db status` — did the DB update overnight?
2. Diff the last good scan against current: `grype db diff <old-db> <new-db>`.
3. Inspect the new advisories: are they real (e.g. an OS provider released a backlog) or false positives (track in github.com/anchore/grype/issues)?
4. If false positives, add scoped `ignore` rules with `reason: "tracking #NNNN"`.
5. Notify stakeholders that the count change is data-driven, not codebase-driven.

#### Runbook: A scan blocks the build but the CVE is known-not-applicable

1. Verify the finding with `grype <target> -o json | jq '.matches[] | select(.vulnerability.id == "CVE-...")' `.
2. Confirm the package version against vendor advisory.
3. If genuinely not applicable: add an `ignore` rule with `reason` referencing the triage ticket.
4. Re-run; the match moves to `ignoredMatches`.
5. Audit `.grype.yaml` quarterly — remove ignores once a fix lands.

---

## 6. Performance

### 6.1 Where time goes

For an `alpine:3.20` scan on a typical runner, the rough breakdown is:

| Phase | Wall-clock | Notes |
|---|---|---|
| DB load (cache hit) | 200–500 ms | sqlite open + integrity check. |
| DB update check (cache miss) | 500 ms – 30 s | Listing fetch + archive download. |
| Image pull / source resolution | 1–10 s | Depends on registry latency, image size. |
| Cataloging (syft) | 1–60 s | Scales with file count and archive depth. |
| Matching | 100 ms – 30 s | Scales with package count × matcher count. |
| VEX / ignore filter | Negligible | In-memory list filter. |
| Presenter render | 50 ms – 5 s | JSON/CycloneDX scale with match count. |
| Total | 5 s – 5 min | |

### 6.2 Optimization knobs

- **Cache the DB** between runs (see §4.3) — biggest win.
- **SBOM-first pipeline**: Generate the SBOM once with syft, scan it many times. Avoids re-cataloging.
- **Disable indexed-archive walking** for non-Java workloads: `search.include-indexed-archives: false`.
- **Skip unindexed archives**: `search.include-unindexed-archives: false` (already default).
- **Use `registry:` scheme** when available to skip daemon overhead.
- **Limit matchers** by setting `match.<eco>.using-cpes: false` for ecosystems you don't care about (skips the CPE expansion step).
- **`--exclude` aggressively** when scanning directories — every excluded path is one fewer file to stat and parse.
- **Reuse image-source cache**: stereoscope caches layer extracts in `/tmp` by default; persist the temp dir if you scan the same image many times.

### 6.3 Benchmarking

The repo includes a benchmark for DB loading:

```bash
go test -bench=BenchmarkLoadVulnerabilityDB ./grype/...
```

The benchmark validates checksums and exists primarily to track regressions
when the DB grows.

> [!TIP]
> If you suspect a regression, capture a CPU profile:
> ```yaml
> developer:
>   profile: cpu
> ```
> The output goes to `cpu.pprof` in CWD; analyze with `go tool pprof
> cpu.pprof`.

---

## 7. Security

### 7.1 Trust model

When you run grype, you're trusting:

1. **The grype binary itself.** Distributed via GitHub releases, signed
   with cosign (sigstore keyless OIDC), checksums in `checksums.txt`,
   checksums signed in `checksums.txt.sig`. The install script verifies
   sha256 on every download and (with `-v`) verifies the cosign signature
   against `https://github.com/anchore/grype/.github/workflows/release.yaml@refs/heads/main`.
2. **The vulnerability database.** Distributed from
   `https://grype.anchore.io/databases/v6/`. Each archive's URL is
   appended with `?checksum=sha256:<hex>` and verified before extract.
3. **The syft import.** Pinned in `go.mod`; you trust the audit chain of
   the syft project (same organization).
4. **The data providers** that build the DB upstream. The DB is assembled
   by the `grype-db` build pipeline from feeds like NVD, GitHub Security
   Advisories, OSV, OS-vendor security feeds (Red Hat OVAL, Ubuntu USNs,
   etc.). `grype db providers` lists every provider that contributed to
   the DB you have on disk.

> [!WARNING]
> The signature-verification flag (`install.sh -v`) is only available on
> grype releases ≥ `v0.72.0`. If you pin an older release, you cannot
> cosign-verify it through the install script. Either pin a newer release
> or download the artifacts manually and run cosign yourself.

### 7.2 Air-gapped & isolated environments

For air-gapped environments, the threat surface narrows to the DB
distribution only:

1. Mirror the published `latest.json` listing and the referenced archive
   to an internal HTTPS server.
2. Sign your mirror's TLS cert with an internal CA; configure
   `db.ca-cert: /etc/ssl/certs/internal-ca.crt`.
3. Set `db.update-url: https://internal-mirror.corp/grype/v6/latest.json`.
4. Set `db.auto-update: false` if you want to manually drive sync.
5. Use `grype db import` to install a known-good archive.

The archive's checksum is in the listing; verify it with `sha256sum`
before importing if you're paranoid.

### 7.3 Running grype against untrusted images

Grype reads (but doesn't execute) image contents. Still, untrusted images
deserve sandboxing:

- Run the scan in a rootless container (`anchore/grype:nonroot`).
- Mount only what's needed — never expose the host filesystem read-write.
- For directory scans of untrusted material, use a read-only bind mount: `-v /path:/scan:ro`.
- Set resource limits: `--memory=2g --cpus=2 --pids-limit=1024`.

The cataloger code in syft has had vulnerability advisories of its own in
the past (e.g. archive-extraction zip-bombs); run with conservative
resource limits to bound damage.

### 7.4 Supply-chain implications

The grype release pipeline:

- **Builds** from a tagged commit via `goreleaser`.
- **Signs** binaries with cosign keyless via GitHub Actions OIDC.
- **Generates SBOMs** for every release (with syft) and attaches them as release assets.
- **Notarizes** macOS binaries via `quill`.
- **Publishes** Docker images to both Docker Hub and GHCR with provenance metadata (BUILD_DATE, VCS_REF, VCS_URL).

If you're auditing your supply chain, verify:

```bash
# fetch the release and signing material
gh release download v0.96.1 -p 'checksums.txt*'

# verify the checksums signature
cosign verify-blob \
  --certificate checksums.txt.pem \
  --signature checksums.txt.sig \
  --certificate-identity 'https://github.com/anchore/grype/.github/workflows/release.yaml@refs/heads/main' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  checksums.txt

# verify your binary against the checksums
sha256sum -c checksums.txt --ignore-missing
```

### 7.5 CVE/GHSA accuracy caveats

Grype matches package versions against vulnerability advisories. False
positives are inherent to this model:

- **Version-range advisories** are only as accurate as the upstream
  feed. Feeds occasionally publish ranges that include already-fixed
  versions (see issues #3354, #3359 for examples).
- **CPE matching is fuzzy**: NVD's CPE feed is broad; `match.stock.using-cpes: true` (the default for unrecognized ecosystems) can produce false positives on similarly-named software.
- **EOL distros** publish stale data, if any. The `alerts.enable-eol-distro-warnings` setting surfaces this risk; consider treating EOL findings as informational only.
- **Backports** are common in Linux distros (the distro patches an old version with a security fix without changing the version number). Distro feeds usually annotate this; grype honors the annotation, but unusual feeds can mismatch.

Triage workflow:

1. Treat every High/Critical finding as a real bug until proven false.
2. To prove false: cite the vendor advisory or upstream commit that backports the fix.
3. Add an `ignore` rule with `reason:` linking to the proof.
4. File an issue at github.com/anchore/grype/issues with the same evidence so the data feed can be improved.

### 7.6 Sensitive-data handling

Grype produces reports that include:

- Image names and tags (potentially including private registry hostnames).
- Package versions (rarely sensitive, occasionally proprietary).
- File paths within scanned filesystems.

The `descriptor.configuration` block in JSON output includes the
**redacted** runtime config — sensitive fields are stripped. Verify with
`grype scan -o json | jq '.descriptor.configuration.registry.auth'` —
authority is shown but credentials are blank.

When publishing scan results (e.g. attaching SARIF to a public PR),
double-check that your image names and paths don't leak proprietary
information.

> [!CAUTION]
> Never commit `.grype.yaml` with raw `registry.auth[].password` or
> `registry.auth[].token` values. Use `GRYPE_REGISTRY_AUTH_*` env vars
> sourced from your secret manager.

---

## 8. Developer Internals

### 8.1 Repository structure

```
grype/
├── cmd/grype/                  # CLI entry points and command construction
│   ├── main.go                 # build-time vars + Application().Run()
│   ├── cli/                    # cobra/clio wiring
│   │   ├── cli.go              # SetupConfig, exit-code map, UI selection
│   │   ├── commands/           # one file per subcommand
│   │   │   ├── root.go         # the scan command (runGrype)
│   │   │   ├── db.go           # `db` parent + subcommand registration
│   │   │   ├── db_check.go
│   │   │   ├── db_delete.go
│   │   │   ├── db_diff.go
│   │   │   ├── db_import.go
│   │   │   ├── db_list.go
│   │   │   ├── db_providers.go
│   │   │   ├── db_search.go
│   │   │   ├── db_search_vuln.go
│   │   │   ├── db_status.go
│   │   │   ├── db_update.go
│   │   │   ├── completion.go
│   │   │   ├── explain.go
│   │   │   └── update.go       # app-update check helper
│   │   ├── options/            # config structs (every YAML/env key)
│   │   └── ui/                 # bubbletea handlers for progress UI
│   └── internal/               # CLI-internal helpers (`NotProvided`)
├── grype/                      # the library
│   ├── deprecated.go           # legacy FindVulnerabilities (pre-v1)
│   ├── lib.go                  # SetLogger, SetBus
│   ├── load_vulnerability_db.go # constructs a vulnerability.Provider
│   ├── vulnerability_matcher.go # the orchestrator
│   ├── cpe/                    # CPE expansion + comparison helpers
│   ├── distro/                 # distro types, parsing, fix channels
│   ├── db/                     # DB build pipeline (used by grype-db, not the user CLI)
│   │   ├── build.go            # writes a new DB from provider state
│   │   ├── v5/                 # legacy schema (still importable)
│   │   ├── v6/                 # current schema (GORM models, blob types)
│   │   │   ├── installation/   # curator: download, validate, import, delete
│   │   │   ├── distribution/   # listing.json, archive URL resolution
│   │   │   ├── diff/           # `db diff` engine
│   │   │   └── schema/         # JSON-schema generator for blobs
│   ├── event/                  # partybus event types and parsers
│   ├── grypeerr/               # sentinel errors → exit codes
│   ├── match/                  # Match, IgnoreRule, IgnoredMatch types
│   ├── matcher/                # per-ecosystem matchers
│   │   ├── matchers.go         # NewDefaultMatchers
│   │   ├── apk/                # Alpine
│   │   ├── bitnami/
│   │   ├── dotnet/
│   │   ├── dpkg/               # Debian/Ubuntu
│   │   ├── golang/
│   │   ├── hex/                # Erlang/Elixir
│   │   ├── java/
│   │   ├── javascript/
│   │   ├── msrc/               # Microsoft
│   │   ├── pacman/             # Arch
│   │   ├── portage/            # Gentoo
│   │   ├── python/
│   │   ├── rpm/
│   │   ├── ruby/
│   │   ├── rust/
│   │   ├── stock/              # CPE-only fallback
│   │   └── internal/           # shared CPE/distro/language matching primitives
│   ├── pkg/                    # syft-bridge: pkg.Package, pkg.Provide
│   ├── presenter/              # output formatters
│   │   ├── table/
│   │   ├── json/
│   │   ├── cyclonedx/
│   │   ├── sarif/
│   │   ├── template/
│   │   ├── explain/
│   │   ├── models/             # the unified report data model
│   │   └── internal/           # shared helpers
│   ├── search/                 # DB query helpers used by matchers
│   ├── version/                # version-comparison primitives (semver, deb, rpm, apk, etc.)
│   ├── vex/                    # OpenVEX + CSAF processors
│   └── vulnerability/          # Provider interface, types
├── internal/                   # internal-only helpers (not exported)
│   ├── bus/                    # partybus singleton wrapper
│   ├── cvss/                   # CVSS v2/3/3.1/4 metric parsing
│   ├── dbtest/                 # test-only DB server
│   ├── file/                   # file/archive helpers, hashers
│   ├── format/                 # output-format dispatch
│   ├── log/                    # go-logger singleton wrapper
│   ├── redact/                 # sensitive-string registry
│   ├── schemaver/              # SchemaVer parser
│   ├── stringutil/             # string helpers
│   ├── testutils/              # test fixtures
│   └── format/                 # output-format multiplex (writer fan-out)
├── schema/                     # versioned JSON schemas
│   └── grype/
│       ├── db/                 # SQL + blob JSON schemas (v6)
│       ├── db-diff/            # JSON schema for `db diff` output
│       ├── db-search/          # JSON schema for `db search` output
│       └── db-search-vuln/     # JSON schema for `db search vuln`
├── templates/                  # built-in output templates
├── test/                       # integration, CLI, install, quality tests
└── install.sh                  # POSIX-shell installer (downloaded by users)
```

### 8.2 The matcher hierarchy

`match.Matcher` is the per-ecosystem interface (in `grype/match/`):

```go
type Matcher interface {
    Type() MatcherType
    Match(vp vulnerability.Provider, p pkg.Package) ([]Match, []IgnoredMatch, error)
}
```

Each matcher under `grype/matcher/<ecosystem>/` implements it. They
typically delegate to the shared primitives in `grype/matcher/internal/`:

- `cpe.go` — CPE-based matching against the DB's CPE index.
- `distro.go` — OS-package matching (apk/dpkg/rpm) keyed on distro + ecosystem.
- `language.go` — Language-ecosystem matching keyed on package name + version + ecosystem (the `disclosure` and `resolution` queries map to OSV-style and PURL-style data sources).

A matcher's `Match()` typically:

1. Calls `findCPEMatches(...)` if the package has CPEs and `using-cpes: true`.
2. Calls `findOSPackageMatches(...)` (for OS matchers) using the package's distro.
3. Calls `findLanguagePackageMatches(...)` (for language matchers) using the ecosystem.
4. Merges results, deduplicates, applies ecosystem-specific tweaks (e.g. epoch handling for dpkg/rpm).

> [!NOTE]
> The `stock` matcher is special: it runs **after** all ecosystem matchers
> and provides CPE-only matching for any package whose ecosystem-specific
> matcher didn't return results. Disable it (`match.stock.using-cpes:
> false`) if you want strict ecosystem-only matching.

### 8.3 The cataloger pipeline (delegated to syft)

Cataloging is owned by the `syft` library. Grype calls `syft.CreateSBOM`
under the hood with a config tuned for vulnerability scanning:

- `Compliance.MissingVersion = ComplianceActionDrop` — drop packages
  without a version (they can't be matched anyway).
- `Packages.JavaArchive.IncludeIndexedArchives` — controlled via grype's
  `search.include-indexed-archives`.
- `Packages.JavaArchive.IncludeUnindexedArchives` — controlled via
  `search.include-unindexed-archives`.

The catalogers themselves (apk-db-cataloger, dpkg-db-cataloger,
go-module-binary-cataloger, java-pom-cataloger, ...) live in the syft
repository.

### 8.4 The vulnerability DB schema (v6)

The active DB is a sqlite file (`vulnerability.db`) plus an
`import.json` metadata file, packed in a zstd-compressed tarball.

Key tables (defined in `grype/db/v6/models.go`):

| Table | Purpose |
|---|---|
| `provider` | Each data source (alpine, github, nvd, etc.). |
| `vulnerability_handle` | Lightweight handle for a vulnerability advisory. |
| `vulnerability_blob` | The full advisory JSON (description, references, CVSS, etc.) — stored as a blob. |
| `affected_package_handle` | Lightweight handle for an "X package, in version range Y, has vulnerability Z" relationship. |
| `affected_package_blob` | Version-range and fix metadata for a package — also a blob. |
| `affected_cpe_handle` | CPE-based affected-software entries (NVD-style). |
| `package` | Package identity (name, ecosystem). |
| `operating_system` | Distro identity (name, major/minor version, codename, channel). |
| `cpe` | CPE entries. |
| `epss` | Exploit Prediction Scoring System scores per CVE. |
| `known_exploited_vulnerability_handle` | CISA KEV catalog rows. |
| `known_exploited_vulnerability_blob` | KEV blob payload. |
| `blob` | The deduplicated blob storage; handle tables reference these by hash. |

The schema is versioned via SchemaVer (`MODEL.REVISION.ADDITION`):

- **MODEL** bump → breaking change. Old grypes can't read the new DB; new grypes can't read the old DB.
- **REVISION** → semi-breaking; some queries against historical data may fail.
- **ADDITION** → fully compatible.

The current `ModelVersion` constant is `6`; incrementing it requires a
new release of grype before any client can consume the new DB.

### 8.5 The blob design

Most large advisory data is stored as JSON blobs in the `blob` table,
keyed by content hash. Multiple `vulnerability_handle` rows can reference
the same blob. The blob types are defined in `grype/db/v6/blobs.go`:

- `VulnerabilityBlob` — the canonical advisory payload (description, references, severity, CVSS metrics).
- `PackageBlob` — version ranges and fix metadata.
- `KnownExploitedVulnerabilityBlob` — CISA KEV row data.

The unified JSON schema for all blobs lives at
`schema/grype/db/blob/json/schema-latest.json` and is auto-regenerated by
`task generate-db-schema`.

### 8.6 The test harness

Three layers:

1. **Unit tests**: alongside source files (`*_test.go`). Run with `task unit` or `go test ./...`.
2. **Integration tests** (`test/integration/`): end-to-end DB build + match scenarios using fixture data. `task integration`.
3. **CLI tests** (`test/cli/`): exec the snapshot binary against fixture images and assert on output. `task cli`.
4. **Install tests** (`test/install/`): exercise `install.sh` across multiple shells and OS environments.
5. **Quality tests** (`test/quality/`): the `yardstick` framework — compares grype output against ground-truth labels for thousands of images, regression-tests matcher accuracy.

A typical PR will run unit + integration; CLI runs in CI on most PRs;
quality runs on a schedule because it's expensive.

> [!TIP]
> When adding a matcher or changing version-comparison logic, add a
> fixture under `test/integration/testdata/image-<ecosystem>-match-coverage/`
> with the canonical "this version is vulnerable, this version isn't" pair.
> The integration suite will exercise it.

### 8.7 Code patterns & conventions

- **Functional options for configs**: `matcher.Config` is built up via
  setters in the root command's `getMatcherConfig`.
- **Provider interfaces**: `vulnerability.Provider` is the lookup
  abstraction; the v5 and v6 implementations both satisfy it. New schema
  versions can be added without changing matchers.
- **Sentinel errors via `grypeerr.NewExpectedErr`**: errors that map to
  specific exit codes are sentinel-equality-checked in
  `cli.go::WithMapExitCode`.
- **Bus-driven UI**: progress events are published to a `partybus.Bus`;
  the bubbletea UI subscribes and renders, the no-UI mode logs them.
- **`clio.FlagAdder` interface**: each options struct that wants to
  expose CLI flags implements `AddFlags(flags clio.FlagSet)`.
- **`mapstructure` tags**: required for env-var binding (the YAML→struct
  layer goes through mapstructure under viper).

### 8.8 Extending grype

To add a new matcher:

1. Create `grype/matcher/<ecosystem>/matcher.go` with a struct that
   satisfies `match.Matcher`.
2. Register it in `grype/matcher/matchers.go::NewDefaultMatchers`.
3. Add config to `cmd/grype/cli/options/match.go` (and the corresponding
   YAML key).
4. Add a fixture under `test/integration/testdata/`.
5. Write integration coverage in `test/integration/`.

To add a new presenter:

1. Create `grype/presenter/<format>/presenter.go` with a `Present(io.Writer) error` method.
2. Add a `Format` constant in `internal/format/format.go`.
3. Wire it into `internal/format/presenter.go::GetPresenter`.
4. Add the format to `AvailableFormats`.

To add a new distro:

1. Add a `Type` constant + entry in `All` in `grype/distro/type.go`.
2. Add an entry to `IDMapping` (the `/etc/os-release` `ID` mapping).
3. Optionally extend `aliasTypes` for legacy IDs.
4. Add a fixture directory `grype/distro/testdata/os/<distro>/etc/os-release` and assert in `Test_NewDistroFromRelease_Coverage`.

### 8.9 Potential improvements (community signals)

From the GitHub issues triage:

- **Schema v7 wishlist** (#3369) — open thread for the next major bump; weigh in if you have feedback.
- **AWS ECR native support** (#774) — currently registry mode works for ECR but lacks IAM-role auto-auth.
- **PyPA DB source** (#531) — proposal to ingest the PyPA Advisory Database as a separate provider.
- **Zarf package scanning** (#3329) — proposal for first-class scanning of Zarf bundles.
- **CycloneDX output spec-version selector** (#3350) — currently fixed to one version.
- **Image-as-a-Product** (#3302) — proposal to treat OCI images as primary vulnerable entities (rather than always decomposing to packages).
- **Drop `VulnerabilityMetadataProvider`** (#2456) — internal cleanup.
- **OSV transformer improvements** (#3294) — multiple proposed enhancements bundled.

### 8.10 Contributing

The repo has rigid quality gates enforced by `task validate`:

- `task lint` — `golangci-lint` with `gofmt`, `goimports`, `gosimports`, and a curated linter set (asciicheck, bodyclose, copyloopvar, dogsled, dupl, errcheck, funlen, gocognit, goconst, gocritic, gocyclo, goprintffuncname, gosec, govet, ineffassign, misspell, nakedret, revive, staticcheck, unconvert, unparam, unused, whitespace).
- `task check-licenses` — `bouncer` enforces an approved license list (BSD/MIT/Apache/MPL/ISC/WTFPL).
- `task check-go-mod-tidy` — fails if `go.mod`/`go.sum` aren't tidy.
- `task check-json-schema-drift` — fails if generated JSON schemas diverge from the code.
- `task check-db-schema-drift` — fails if generated DB schemas diverge from the GORM models.
- `task unit` — coverage threshold 47%.
- `task integration`
- `task cli`
- `task install-test`
- `task quality` (occasional)

PRs that pass these are reviewed by Anchore maintainers. Conventions:

- Commit messages follow the project's existing style (concise imperative subject, optional body explaining the why).
- Breaking changes require explicit labeling — `chronicle` uses commit labels to gate major-version bumps.
- New features land with at least integration coverage; ideally CLI test coverage too.
- Schema changes require a SchemaVer bump (see `schema/grype/db/README.md`).

---

## 9. Integration Points

### 9.1 GitHub Actions

The official `anchore/scan-action` wraps grype:

```yaml
- uses: anchore/scan-action@v3
  with:
    image: my/image:tag
    fail-build: true
    severity-cutoff: high
```

For more control, install grype directly (see User Guide §2.2 / §5
Recipe 4) and call it inline.

### 9.2 GitLab CI

```yaml
grype-scan:
  image: anchore/grype:v0.96.1
  variables:
    GRYPE_DB_CACHE_DIR: ${CI_PROJECT_DIR}/.cache/grype/db
  cache:
    key: grype-db-v6
    paths:
      - .cache/grype/db/
  script:
    - grype "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}" --fail-on high -o sarif --file gl-grype-report.json
  artifacts:
    paths:
      - gl-grype-report.json
    reports:
      sast: gl-grype-report.json
```

### 9.3 Jenkins

```groovy
pipeline {
  agent any
  stages {
    stage('Vulnerability scan') {
      steps {
        sh '''
          curl -sSfL https://get.anchore.io/grype | sudo sh -s -- -b /usr/local/bin v0.96.1
          grype "${IMAGE_TAG}" --fail-on high -o json --file scan.json
        '''
        archiveArtifacts artifacts: 'scan.json'
      }
    }
  }
}
```

### 9.4 Harbor / Quay / ECR

Most registries support webhook-based scanning. The pattern: a small
service (Lambda / Cloud Run / Knative) receives the registry's
"image-pushed" event, runs `grype registry:<image>`, and posts results
back via the registry's API. Use `--platform` to scan each architecture
of multi-arch images.

### 9.5 SBOM workflows (Syft, CycloneDX, SPDX)

```bash
# generate, then scan
syft myimage:tag -o cyclonedx-json > sbom.cdx.json
grype sbom:./sbom.cdx.json

# scan an SPDX SBOM
grype sbom:./sbom.spdx.json

# pipe directly
syft myimage:tag -o json | grype --fail-on high
```

Grype recognizes SBOMs by content (auto-detect across syft-json,
cyclonedx-json, cyclonedx-xml, spdx-json, spdx-tag-value).

### 9.6 VEX integration

```bash
# OpenVEX
grype myimage:tag --vex ./openvex.json

# CSAF VEX
grype myimage:tag --vex ./csaf-vex.json

# Multiple documents
grype myimage:tag --vex ./vendor-a.vex --vex ./vendor-b.vex
```

VEX documents are matched against findings by product identifier (CPE,
PURL) and vulnerability ID. Matched findings move to the ignored list
according to their VEX status. To **keep** matches the VEX flags as
`affected` or `under_investigation`, add to `vex-add: ["affected"]` in
config.

### 9.7 Programmatic use (Go library)

While grype is primarily a CLI, its Go packages are importable. The
public surface is intentionally small — most consumers should shell out
to the CLI for stability. Stable entry points:

- `grype.LoadVulnerabilityDB(distCfg, installCfg, update bool) (vulnerability.Provider, *vulnerability.ProviderStatus, error)` — get a provider.
- `pkg.Provide(userInput string, cfg ProviderConfig) ([]pkg.Package, pkg.Context, *sbom.SBOM, error)` — catalog a target.
- `matcher.NewDefaultMatchers(cfg matcher.Config) []match.Matcher` — get the default matcher list.
- `grype.VulnerabilityMatcher{...}.FindMatchesContext(ctx, packages, pkgContext)` — run matching.

A minimal embedded scan:

```go
import (
    "context"
    "github.com/anchore/grype/grype"
    grypePkg "github.com/anchore/grype/grype/pkg"
    "github.com/anchore/grype/grype/matcher"
    v6dist "github.com/anchore/grype/grype/db/v6/distribution"
    v6inst "github.com/anchore/grype/grype/db/v6/installation"
)

func scan(ctx context.Context, target string) error {
    vp, _, err := grype.LoadVulnerabilityDB(
        v6dist.DefaultConfig(),
        v6inst.DefaultConfig(),
        true,
    )
    if err != nil { return err }

    pkgs, pkgCtx, _, err := grypePkg.Provide(target, grypePkg.ProviderConfig{})
    if err != nil { return err }

    m := grype.VulnerabilityMatcher{
        VulnerabilityProvider: vp,
        Matchers: matcher.NewDefaultMatchers(matcher.Config{}),
    }
    matches, _, err := m.FindMatchesContext(ctx, pkgs, pkgCtx)
    return err
}
```

> [!WARNING]
> The Go API is **not yet stabilized**. Several functions in `grype/`
> carry "TODO: deprecated, will remove before v1.0.0" comments
> (`FindVulnerabilities`, `FindVulnerabilitiesForPackage`,
> `presenter.GetPresenter`). Do not depend on these for production code;
> they will be removed.

### 9.8 Output schema stability

The JSON output schemas are versioned and kept stable within a major:

- `schema/grype/db-search/json/` — versioned schemas for `db search`.
- `schema/grype/db-search-vuln/json/` — versioned schemas for `db search vuln`.
- `schema/grype/db-diff/json/` — versioned schemas for `db diff`.
- `schema/grype/db/blob/json/` — internal blob schemas for the DB.

Each schema is `MODEL.REVISION.ADDITION` versioned per SchemaVer.
Consumer rule: pin against the major component; tolerate ADDITION bumps
in your code.

### 9.9 Event bus

Internally grype publishes events on a `partybus.Bus`:

| Event | Triggered by |
|---|---|
| `event.UpdateVulnerabilityDatabase` | DB update phase. |
| `event.VulnerabilityScanningStarted` | The matcher phase begins. |
| `event.DatabaseDiffingStarted` | `db diff` begins. |
| `event.CLIAppUpdateAvailable` | The app-update check found a newer release. |

These drive the bubbletea progress UI; library consumers can attach their
own subscribers via `grype.SetBus(...)`.

---

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