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
cmd/grype/main.goconstructs aclio.Identificationand callscli.Application(...).Run().cmd/grype/cli/cli.gobuilds the cobra root with subcommands, configures logging/UI, and setsWithMapExitCode(severity threshold → 2, DB-update-available → 100).- The root cobra command (
commands.Root) dispatchesrunGrype(ctx, ...). runGrype:- Builds a
format.MakeScanResultWriterfrom--output/--file. - Spawns a goroutine that calls
LoadVulnerabilityDB(...)to produce avulnerability.Provider. - Concurrently calls
pkg.Provide(userInput, providerConfig)to get the[]pkg.Package, thepkg.Context(distro, source description), and a*sbom.SBOMfor downstream CycloneDX rendering.
- Builds a
applyVexRules(opts)may appendIgnoreRuleentries derived from VEX options.- A
vex.NewProcessor(opts.VexDocuments, opts.Ignore)is built — it selects OpenVEX or CSAF backend based on the first document's content. - A
grype.VulnerabilityMatcheris instantiated with the provider, the matchers (one per ecosystem frommatcher.NewDefaultMatchers), the ignore rules, the fail-on threshold, the VEX processor, and the alerts config. 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.- Matches are filtered through
IgnoreRule(and the implicit rules added by--only-fixed,--only-notfixed,--ignore-states). - The VEX processor moves
not_affected/fixedmatches from the live list toignoredMatches, and conversely promotesaffected/under_investigationwithvex-add. - If
--fail-onis set and any remaining match meets the threshold, the matcher returnsgrypeerr.ErrAboveSeverityThreshold(exit 2 — but the report is still written first). 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
windowsdistro is detectable but has narrower vulnerability coverage than Linux distros — the MSRC matcher handles KB-style advisories. Don't expect parity withapk/deb/rpmmatching.
2.5 Supported ecosystems (matcher list)
From grype/matcher/matchers.go (NewDefaultMatchers), in registration
order:
- dpkg — Debian/Ubuntu/Raspbian (deb packages).
- ruby — RubyGems.
- python — PyPI (wheel/egg/dist-info).
- dotnet — .NET assemblies + NuGet.
- rpm — Red Hat / CentOS / Fedora / Rocky / Alma / Oracle / Mariner / Azure / SLES / OpenSUSE / Photon / Amazon Linux.
- java — Java archives (jar/war/ear).
- javascript — npm.
- apk — Alpine, Wolfi, Chainguard.
- golang — Go modules and binary build info.
- msrc — Microsoft Security Response Center (Windows KBs).
- portage — Gentoo.
- rust —
cargo auditdata + binary auditable info. - hex — Erlang/Elixir Hex.
- stock — fallback CPE-only matcher when ecosystem is unknown.
- bitnami — Bitnami stack packages.
- 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, andtokenfields are typed assecret, which causes them to be redacted from any log line emitted viainternal/logthanks tointernal/redact. Inspect the redact registry with-vvto 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:
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
# == 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:
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
nonrootimage variant runs as UID 65532. If your DB cache mount is owned by a different user, it'll fail to write —chown -R 65532:65532the mount, or use the defaultlatest(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 isWAL-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 tracklatest. - Pin DB: download once,
grype db import, setdb.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:00Zfor reproducible JSON output (otherwisedescriptor.timestampdiffers between runs).
5. Operations
5.1 Operational checklists
Pre-flight checklist for a new CI integration:
- [ ] Pin grype version (
v0.x.y) — never tracklatestin pipeline scripts. - [ ] Cache
~/.cache/grype/dbbetween runs. - [ ] Set
--fail-onto a defined threshold; document the policy in your repo. - [ ] Set
--only-fixedif 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 andGRYPE_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 checkexits 0 (no drift). - [ ]
grype versionmatches the pinned version. - [ ] Recent scans exit 0 or 2 (never 1 —
1means 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:
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/redactand 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:
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:
grype db delete(clear corrupted state).grype db update(orgrype db import <archive>).grype db statusto 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
- SSH (or kubectl exec) into a runner.
- Run
grype db check -vv 2>&1 | tee /tmp/db-check.log. - If DNS or TLS errors: check egress firewall rules to
grype.anchore.io. - If
checksumerrors:grype db delete && grype db update. - If persistent: switch to mirror via
GRYPE_DB_UPDATE_URL=.... - File the incident with the log.
Runbook: Scan suddenly shows 100s of new "Critical" findings
- Check
grype versionandgrype db status— did the DB update overnight? - Diff the last good scan against current:
grype db diff <old-db> <new-db>. - 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)?
- If false positives, add scoped
ignorerules withreason: "tracking #NNNN". - 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
- Verify the finding with
grype <target> -o json | jq '.matches[] | select(.vulnerability.id == "CVE-...")'. - Confirm the package version against vendor advisory.
- If genuinely not applicable: add an
ignorerule withreasonreferencing the triage ticket. - Re-run; the match moves to
ignoredMatches. - Audit
.grype.yamlquarterly — 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: falsefor ecosystems you don't care about (skips the CPE expansion step). --excludeaggressively when scanning directories — every excluded path is one fewer file to stat and parse.- Reuse image-source cache: stereoscope caches layer extracts in
/tmpby default; persist the temp dir if you scan the same image many times.
6.3 Benchmarking
The repo includes a benchmark for DB loading:
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:
developer: profile: cpuThe output goes to
cpu.pprofin CWD; analyze withgo tool pprof cpu.pprof.
7. Security
7.1 Trust model
When you run grype, you're trusting:
- The grype binary itself. Distributed via GitHub releases, signed
with cosign (sigstore keyless OIDC), checksums in
checksums.txt, checksums signed inchecksums.txt.sig. The install script verifies sha256 on every download and (with-v) verifies the cosign signature againsthttps://github.com/anchore/grype/.github/workflows/release.yaml@refs/heads/main. - 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. - The syft import. Pinned in
go.mod; you trust the audit chain of the syft project (same organization). - The data providers that build the DB upstream. The DB is assembled
by the
grype-dbbuild pipeline from feeds like NVD, GitHub Security Advisories, OSV, OS-vendor security feeds (Red Hat OVAL, Ubuntu USNs, etc.).grype db providerslists 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:
- Mirror the published
latest.jsonlisting and the referenced archive to an internal HTTPS server. - Sign your mirror's TLS cert with an internal CA; configure
db.ca-cert: /etc/ssl/certs/internal-ca.crt. - Set
db.update-url: https://internal-mirror.corp/grype/v6/latest.json. - Set
db.auto-update: falseif you want to manually drive sync. - Use
grype db importto 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:
# 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-warningssetting 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:
- Treat every High/Critical finding as a real bug until proven false.
- To prove false: cite the vendor advisory or upstream commit that backports the fix.
- Add an
ignorerule withreason:linking to the proof. - 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.yamlwith rawregistry.auth[].passwordorregistry.auth[].tokenvalues. UseGRYPE_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/):
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 (thedisclosureandresolutionqueries map to OSV-style and PURL-style data sources).
A matcher's Match() typically:
- Calls
findCPEMatches(...)if the package has CPEs andusing-cpes: true. - Calls
findOSPackageMatches(...)(for OS matchers) using the package's distro. - Calls
findLanguagePackageMatches(...)(for language matchers) using the ecosystem. - Merges results, deduplicates, applies ecosystem-specific tweaks (e.g. epoch handling for dpkg/rpm).
Note
The
stockmatcher 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'ssearch.include-indexed-archives.Packages.JavaArchive.IncludeUnindexedArchives— controlled viasearch.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:
- Unit tests: alongside source files (
*_test.go). Run withtask unitorgo test ./.... - Integration tests (
test/integration/): end-to-end DB build + match scenarios using fixture data.task integration. - CLI tests (
test/cli/): exec the snapshot binary against fixture images and assert on output.task cli. - Install tests (
test/install/): exerciseinstall.shacross multiple shells and OS environments. - Quality tests (
test/quality/): theyardstickframework — 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.Configis built up via setters in the root command'sgetMatcherConfig. - Provider interfaces:
vulnerability.Provideris 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 incli.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.FlagAdderinterface: each options struct that wants to expose CLI flags implementsAddFlags(flags clio.FlagSet).mapstructuretags: required for env-var binding (the YAML→struct layer goes through mapstructure under viper).
8.8 Extending grype
To add a new matcher:
- Create
grype/matcher/<ecosystem>/matcher.gowith a struct that satisfiesmatch.Matcher. - Register it in
grype/matcher/matchers.go::NewDefaultMatchers. - Add config to
cmd/grype/cli/options/match.go(and the corresponding YAML key). - Add a fixture under
test/integration/testdata/. - Write integration coverage in
test/integration/.
To add a new presenter:
- Create
grype/presenter/<format>/presenter.gowith aPresent(io.Writer) errormethod. - Add a
Formatconstant ininternal/format/format.go. - Wire it into
internal/format/presenter.go::GetPresenter. - Add the format to
AvailableFormats.
To add a new distro:
- Add a
Typeconstant + entry inAllingrype/distro/type.go. - Add an entry to
IDMapping(the/etc/os-releaseIDmapping). - Optionally extend
aliasTypesfor legacy IDs. - Add a fixture directory
grype/distro/testdata/os/<distro>/etc/os-releaseand assert inTest_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-lintwithgofmt,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—bouncerenforces an approved license list (BSD/MIT/Apache/MPL/ISC/WTFPL).task check-go-mod-tidy— fails ifgo.mod/go.sumaren'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 integrationtask clitask install-testtask 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 —
chronicleuses 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:
- 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
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
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)
# 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
# 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:
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 fordb search.schema/grype/db-search-vuln/json/— versioned schemas fordb search vuln.schema/grype/db-diff/json/— versioned schemas fordb 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(...).