Jesse A. Green / notebook

Grype Administrator & Developer Guide

Generated April 2026 from anchore/grype at 6d0b66c

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

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:

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

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:

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:

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

# 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/):

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:

- 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 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(...).