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:
- 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. - Per-ecosystem matchers in
grype/matcher/<ecosystem>/. Each implementsmatch.Matcher. - Match data structures in
grype/match/. DefinesMatch,Matches, fingerprints, ignore rules, and theIgnoreFilterinterface.
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 mocksThe 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
MatcherConfigexposed 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 aMatcherTypeconstant frommatch/matcher_type.go. - A
Match(...)doing the work, usually by delegating to helpers ingrype/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:
- Exact fingerprint match → merge details (case A in the code).
- Core fingerprint match, different fix versions → if the new match is direct and the existing is indirect, supersede; otherwise merge (case B).
- 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:
- Hard-coded (
grype/match/explicit_ignores.go): known false positives encoded by Anchore (linux-kernel-headers, etc.). - Matcher-emitted
IgnoreFilters: returned fromMatch()to filter what other matchers report. - 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:
- If the match is already a CVE, leave it alone.
- Find a CVE in
RelatedVulnerabilities. If 0 or >1, skip (we cannot uniquely normalize). - Look up the upstream metadata via the deprecated
MetadataProvider. - Rewrite
Vulnerability.ID,Namespace, and place the original ID intoRelatedVulnerabilities.
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/), implementmatch.Matcher, register it inmatchers.go::NewDefaultMatchers, and add theMatcherConfigfield tomatcher.Config. - Tune existing matcher behavior — modify the per-ecosystem package; most logic lives in
grype/matcher/<eco>/matcher.goplus version-comparison code ingrype/version/. - Change ignore semantics —
grype/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.