Open-Source Wikis

/

Grype

/

Systems

/

Matcher system

anchore/grype

Matcher system

Active contributors: Alex Goodman, Will Murphy, Keith Zantow

Purpose

The matcher system is the heart of Grype: it takes a list of catalogued packages plus a vulnerability provider and produces a deduplicated set of match.Match records. It is also where ignore rules and VEX filtering plug in.

The system has three layers:

  1. Top-level orchestration in grype/vulnerability_matcher.go. Builds a per-package-type matcher index, runs every package through its assigned matchers, applies ignore rules, optionally normalizes by CVE, and applies VEX.
  2. Per-ecosystem matchers in grype/matcher/<ecosystem>/. Each implements match.Matcher.
  3. Match data structures in grype/match/. Defines Match, Matches, fingerprints, ignore rules, and the IgnoreFilter interface.

Directory layout

grype/
├── vulnerability_matcher.go          # VulnerabilityMatcher orchestration
├── match/
│   ├── match.go                      # Match struct + Merge
│   ├── matches.go                    # Matches collection with fingerprint indexes
│   ├── matcher.go                    # Matcher interface, fatalError
│   ├── matcher_type.go               # MatcherType enum
│   ├── type.go                       # Match Type enum (ExactDirect, ExactIndirect, ...)
│   ├── details.go                    # Match details (per-found-by record)
│   ├── fingerprint.go                # Match fingerprint + core fingerprint
│   ├── ignore.go                     # IgnoreRule, IgnoreFilter, IgnoreRulePackage
│   ├── explicit_ignores.go           # hard-coded false-positive rules
│   ├── results.go, sort.go           # ordering and serialization helpers
│   └── provider.go                   # ExclusionProvider interface
└── matcher/
    ├── matchers.go                   # NewDefaultMatchers
    ├── apk/, dpkg/, rpm/             # OS distro matchers
    ├── java/, javascript/, ruby/     # language matchers
    ├── python/, dotnet/, golang/, rust/, hex/
    ├── bitnami/, msrc/, pacman/, portage/
    ├── stock/                        # fallback CPE matcher
    ├── internal/                     # shared search helpers (CPE, distro, language)
    └── mock/                         # test mocks

The Matcher interface

Defined in grype/match/matcher.go:

type Matcher interface {
    PackageTypes() []syftPkg.Type
    Type() MatcherType
    Match(vp vulnerability.Provider, p pkg.Package) ([]Match, []IgnoreFilter, error)
}

Every matcher implementation has the same shape:

  • A MatcherConfig exposed for CLI/YAML configuration.
  • A New<Name>Matcher(cfg) constructor returning the matcher value.
  • A PackageTypes() returning the Syft types this matcher handles.
  • A Type() returning a MatcherType constant from match/matcher_type.go.
  • A Match(...) doing the work, usually by delegating to helpers in grype/matcher/internal/.

How a scan flows

graph TD
    Pkgs[packages, pkgContext] --> VM[VulnerabilityMatcher.FindMatchesContext]
    VM --> Index[newMatcherIndex]
    Index -->|map syftPkg.Type to Matcher| Loop{for each package}
    Loop --> Stock[stock matcher fallback]
    Loop --> Eco[ecosystem matcher]
    Eco --> CallSafe[callMatcherSafely]
    Stock --> CallSafe
    CallSafe -->|panics become fatalError| Found[matches + ignorers]
    Found --> ExplicitIgnore[ApplyExplicitIgnoreRules]
    ExplicitIgnore --> Ignorers[ApplyIgnoreFilters]
    Ignorers --> Dedup[Matches.Add → fingerprint dedup]
    Dedup --> UserRules[applyIgnoreRules]
    UserRules --> Normalize{NormalizeByCVE?}
    Normalize -->|yes| ApplyAgain[apply ignore rules again]
    Normalize -->|no| VEX[VexProcessor.ApplyVEX]
    ApplyAgain --> VEX
    VEX --> Severity{FailSeverity?}
    Severity -->|met| ErrFail[grypeerr.ErrAboveSeverityThreshold]
    Severity -->|not met| Done[remainingMatches + ignoredMatches]

The orchestration is in VulnerabilityMatcher.FindMatchesContext (grype/vulnerability_matcher.go). It is intentionally readable as a series of phases, each with its own log lines and progress events.

Default matcher set

grype/matcher/matchers.go::NewDefaultMatchers returns the canonical matcher list:

return []match.Matcher{
    dpkg.NewDpkgMatcher(mc.Dpkg),
    ruby.NewRubyMatcher(mc.Ruby),
    python.NewPythonMatcher(mc.Python),
    dotnet.NewDotnetMatcher(mc.Dotnet),
    rpm.NewRpmMatcher(mc.Rpm),
    java.NewJavaMatcher(mc.Java),
    javascript.NewJavascriptMatcher(mc.Javascript),
    &apk.Matcher{},
    golang.NewGolangMatcher(mc.Golang),
    &msrc.Matcher{},
    &portage.Matcher{},
    rust.NewRustMatcher(mc.Rust),
    hex.NewHexMatcher(mc.Hex),
    stock.NewStockMatcher(mc.Stock),
    &bitnami.Matcher{},
    &pacman.Matcher{},
}

The order matters: when a package type is matched by multiple matchers, the first one to produce an ExactDirectMatch "wins" against later ExactIndirectMatch results from the stock matcher (see Matches.mergeCoreMatches).

Match deduplication

Matches are deduplicated via fingerprints in match/matches.go:

  • Fingerprint = (vulnerabilityID, namespace, packageID, fixVersions).
  • Core fingerprint = (vulnerabilityID, namespace, packageID) without the fix versions.

Matches.addOrMerge handles three cases:

  1. Exact fingerprint match → merge details (case A in the code).
  2. Core fingerprint match, different fix versions → if the new match is direct and the existing is indirect, supersede; otherwise merge (case B).
  3. No prior match → simple insert (case C).

The result: matchers can independently report the same vulnerability without producing duplicate output, and direct-evidence matches always win over indirect-evidence matches for the same vulnerability+package.

Match types

grype/match/type.go enumerates how a match was made:

Type Meaning
ExactDirectMatch Vulnerability matched on the package itself.
ExactIndirectMatch Vulnerability matched on an upstream/source package (e.g. linux-headers indirectly via linux).
CPEMatch Vulnerability matched via CPE rather than ecosystem-specific identifiers.
FuzzyMatch Approximate version comparison (rare).

Ignore pipeline

Ignore rules come from three sources:

  1. Hard-coded (grype/match/explicit_ignores.go): known false positives encoded by Anchore (linux-kernel-headers, etc.).
  2. Matcher-emitted IgnoreFilters: returned from Match() to filter what other matchers report.
  3. User-provided IgnoreRules: from CLI flags, env vars, or YAML config.

User rules can match on vulnerability ID, package fields (name/version/language/type/location/upstream-name), fix state, VEX status, and match type. The IgnoreRulePackage.Name field is treated as a regex when it contains regex syntax — useful for the linux-headers case which hard-codes linux(-.*)?-headers-.*.

ignoredMatchFilter in vulnerability_matcher.go builds three indexes for fast ignore evaluation:

  • by location (filesystem path)
  • by package name
  • by vulnerability ID

This avoids O(matches × ignores) blowups on scans with many user ignores.

CVE normalization

--by-cve rewrites advisory IDs (RHSA-..., GHSA-..., DSA-..., USN-...) to their CVE form when the vulnerability record carries a CVE in RelatedVulnerabilities. Implementation in vulnerability_matcher.go::normalizeByCVE:

  1. If the match is already a CVE, leave it alone.
  2. Find a CVE in RelatedVulnerabilities. If 0 or >1, skip (we cannot uniquely normalize).
  3. Look up the upstream metadata via the deprecated MetadataProvider.
  4. Rewrite Vulnerability.ID, Namespace, and place the original ID into RelatedVulnerabilities.

Ignore rules are applied twice when normalization is on: once before normalization (so user-supplied advisory-ID rules still work) and once after (in case normalization regressed against another rule).

Severity gating

If FailSeverity is set on the matcher, the pipeline checks every remaining match against the configured threshold and returns grypeerr.ErrAboveSeverityThreshold (mapped to exit code 2) when any match meets or exceeds it.

Key abstractions

Symbol Where Description
match.Match grype/match/match.go Pairing of a vulnerability.Vulnerability and a pkg.Package, with Details describing how the match was made.
match.Matches grype/match/matches.go Set of Match with fingerprint and per-package indexes.
match.IgnoreRule grype/match/ignore.go YAML-shaped ignore criteria.
match.IgnoreFilter same Interface for any object that can choose to ignore a Match.
match.IgnoreRelatedPackage same Specialized filter for upstream/related-package suppression.
match.NewFatalError grype/match/matcher.go Constructor for matcher panics that should abort the scan.
grype.VulnerabilityMatcher grype/vulnerability_matcher.go Top-level orchestrator.
matcher.NewDefaultMatchers grype/matcher/matchers.go The canonical matcher list.

Key source files

File Purpose
grype/vulnerability_matcher.go Top-level orchestration: phases, EOL tracking, severity gating.
grype/match/matches.go Fingerprint-based deduplication, direct vs. indirect resolution.
grype/match/ignore.go IgnoreRule and friends — large file (~400 lines).
grype/match/explicit_ignores.go Hard-coded false-positive suppressions.
grype/matcher/matchers.go Default matcher list.
grype/matcher/internal/ Shared search helpers used by ecosystem matchers.

Entry points for modification

  • Add a new ecosystem matcher — copy an existing matcher dir (e.g. grype/matcher/javascript/), implement match.Matcher, register it in matchers.go::NewDefaultMatchers, and add the MatcherConfig field to matcher.Config.
  • Tune existing matcher behavior — modify the per-ecosystem package; most logic lives in grype/matcher/<eco>/matcher.go plus version-comparison code in grype/version/.
  • Change ignore semanticsgrype/match/ignore.go. Tests are in the sibling _test.go (~30K lines).
  • Add a new explicit ignore — append to grype/match/explicit_ignores.go. These are global, hard-coded suppressions that apply to every scan.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Matcher system – Grype wiki | Factory