Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1c26bb5
feat: ignore overlapping fixed vulns for rpm + more
kzantow Mar 30, 2026
c9e50b1
chore: update tests
kzantow Mar 30, 2026
6907ec1
fix: filterVulns should update version in match details
kzantow Mar 30, 2026
445ca53
fix: vulnerability provider constraint creation without version speci…
kzantow Mar 30, 2026
cc44061
perf: make relationships by overlapping locations
kzantow Apr 7, 2026
15dfdd5
Merge remote-tracking branch 'upstream/main' into feat/ignore-rpm-fixed
kzantow Apr 7, 2026
31a562d
chore: update tests
kzantow Apr 7, 2026
840ea16
chore: update match labels
kzantow Apr 7, 2026
a508b09
Merge remote-tracking branch 'upstream/main' into feat/ignore-rpm-fixed
kzantow Apr 8, 2026
31a8adf
Merge remote-tracking branch 'upstream/main' into feat/ignore-rpm-fixed
kzantow Apr 9, 2026
e5d3168
fix: align vuln filter with vuln provider constraint filtering when e…
kzantow Apr 10, 2026
a7a6e1c
Merge remote-tracking branch 'upstream/main' into feat/ignore-rpm-fixed
kzantow Apr 14, 2026
465d0e8
fix: ensure unaffected packages are applied everywhere as ignore filters
kzantow Apr 15, 2026
ebd04e2
fix: alma ignores & tests
kzantow Apr 15, 2026
c389ebc
feat(os transformer): unaffected package handles
willmurphyscode Apr 15, 2026
11b23be
fix: missed unaffected selection
kzantow Apr 17, 2026
64b7e55
Merge remote-tracking branch 'upstream/main' into feat/ignore-rpm-fixed
kzantow Apr 22, 2026
f1a1659
Merge remote-tracking branch 'will/feat-os-schema-uaps' into feat/ign…
kzantow Apr 23, 2026
57b4952
chore: add test cases
kzantow Apr 24, 2026
812f3c2
chore: add test cases / pr feedback
kzantow Apr 24, 2026
907e5c8
Merge remote-tracking branch 'upstream/main' into feat/ignore-rpm-fixed
kzantow Apr 24, 2026
13541c4
chore: move to new dbtest
kzantow Apr 24, 2026
f4ff230
chore: add metadata.json files
kzantow Apr 24, 2026
f67cda5
chore: update match labels to PR version
kzantow Apr 24, 2026
04eebce
chore: unused test variable
kzantow Apr 27, 2026
d22beae
chore: update comment
kzantow Apr 27, 2026
d32c55a
chore: update vuln match labels
kzantow Apr 29, 2026
e6ef3da
chore: address PR comments: add tests
kzantow Apr 29, 2026
18d617b
chore: address PR comments: add tests
kzantow Apr 29, 2026
37bb161
chore: update to match labels main
kzantow Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[
{
"Vulnerability": {
"CVSS": [
{
"base_metrics": {
"base_score": 7.8,
"base_severity": "High",
"exploitability_score": 1.8,
"impact_score": 5.9
},
"status": "draft",
"vector_string": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
"version": "3.1"
}
],
"Description": "Test vulnerability with a not-affected package and an affected package.",
"FixedIn": [
{
"Name": "ghostscript",
"NamespaceName": "rhel:8",
"VendorAdvisory": {
"AdvisorySummary": [],
"NoAdvisory": false
},
"Version": "0",
"VersionFormat": "rpm"
},
{
"Name": "firefox",
"NamespaceName": "rhel:8",
"VendorAdvisory": {
"AdvisorySummary": [
{
"ID": "RHSA-2020:1341",
"Link": "https://access.redhat.com/errata/RHSA-2020:1341"
}
],
"NoAdvisory": false
},
"Version": "0:68.6.1-1.el8_1",
"VersionFormat": "rpm"
}
],
"Link": "https://access.redhat.com/security/cve/CVE-2020-99999",
"Metadata": {},
"Name": "CVE-2020-99999",
"NamespaceName": "rhel:8",
"Severity": "Medium"
}
}
]
46 changes: 43 additions & 3 deletions grype/db/v6/build/transformers/os/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,56 @@ func Transform(vulnerability unmarshal.OSVulnerability, state provider.State) ([
},
}

for _, a := range getAffectedPackages(vulnerability) {
affected, unaffected := getPackages(vulnerability)
for _, a := range affected {
in = append(in, a)
}
for _, u := range unaffected {
in = append(in, u)
}

return transformers.NewEntries(in...), nil
}

func getAffectedPackages(vuln unmarshal.OSVulnerability) []db.AffectedPackageHandle {
func isNotAffectedGroup(fixedIns []unmarshal.OSFixedIn) bool {
for _, f := range fixedIns {
if versionutil.CleanFixedInVersion(f.Version) != "0" {
return false
}
}
return true
}

func getPackages(vuln unmarshal.OSVulnerability) ([]db.AffectedPackageHandle, []db.UnaffectedPackageHandle) {
var afs []db.AffectedPackageHandle
var unafs []db.UnaffectedPackageHandle
groups := groupFixedIns(vuln)
for group, fixedIns := range groups {
// APK providers already handle not-affected signaling in their own matching layer,
// so skip emitting unaffected package handles for them.
pkgType := getPackageType(group.osName)
if pkgType != pkg.ApkPkg && isNotAffectedGroup(fixedIns) {
unafs = append(unafs, db.UnaffectedPackageHandle{
OperatingSystem: getOperatingSystem(group.osName, group.id, group.osVersion, group.osChannel),
Package: getPackage(group),
BlobValue: &db.PackageBlob{
CVEs: getAliases(vuln),
Ranges: []db.Range{
{
Version: db.Version{
Type: fixedIns[0].VersionFormat,
Constraint: "",
},
Fix: &db.Fix{
State: db.NotAffectedFixStatus,
},
},
},
},
})
continue
}

// we only care about a single qualifier: rpm modules. The important thing to note about this is that
// a package with no module vs a package with a module should be detectable in the DB.
var qualifiers *db.PackageQualifiers
Expand Down Expand Up @@ -99,8 +138,9 @@ func getAffectedPackages(vuln unmarshal.OSVulnerability) []db.AffectedPackageHan

// stable ordering
sort.Sort(internal.ByAffectedPackage(afs))
sort.Sort(internal.ByUnaffectedPackage(unafs))

return afs
return afs, unafs
}

func getFix(fixedInEntry unmarshal.OSFixedIn) *db.Fix {
Expand Down
87 changes: 87 additions & 0 deletions grype/db/v6/build/transformers/os/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,85 @@ func TestTransform(t *testing.T) {
},
},
},
{
name: "testdata/rhel-8-not-affected.json",
provider: "rhel",
want: []transformers.RelatedEntries{
{
VulnerabilityHandle: &db.VulnerabilityHandle{
Name: "CVE-2020-99999",
Status: "active",
ProviderID: "rhel",
Provider: expectedProvider("rhel"),
BlobValue: &db.VulnerabilityBlob{
ID: "CVE-2020-99999",
Description: "Test vulnerability with a not-affected package and an affected package.",
References: []db.Reference{
{URL: "https://access.redhat.com/security/cve/CVE-2020-99999"},
},
Severities: []db.Severity{
{
Scheme: db.SeveritySchemeCHMLN,
Value: "medium",
Rank: 1,
},
{
Scheme: db.SeveritySchemeCVSS,
Value: db.CVSSSeverity{
Vector: "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
Version: "3.1",
},
Rank: 2,
},
},
},
},
Related: append(
affectedPkgSlice(
db.AffectedPackageHandle{
OperatingSystem: rhel8OS,
Package: &db.Package{Ecosystem: "rpm", Name: "firefox"},
BlobValue: &db.PackageBlob{
Qualifiers: &db.PackageQualifiers{RpmModularity: strRef("")},
Ranges: []db.Range{
{
Version: db.Version{Type: "rpm", Constraint: "< 0:68.6.1-1.el8_1"},
Fix: &db.Fix{
Version: "0:68.6.1-1.el8_1",
State: db.FixedStatus,
Detail: &db.FixDetail{
References: []db.Reference{
{
ID: "RHSA-2020:1341",
URL: "https://access.redhat.com/errata/RHSA-2020:1341",
Tags: []string{db.AdvisoryReferenceTag},
},
},
},
},
},
},
},
},
),
unaffectedPkgSlice(
db.UnaffectedPackageHandle{
OperatingSystem: rhel8OS,
Package: &db.Package{Ecosystem: "rpm", Name: "ghostscript"},
BlobValue: &db.PackageBlob{
Ranges: []db.Range{
{
Version: db.Version{Type: "rpm"},
Fix: &db.Fix{State: db.NotAffectedFixStatus},
},
},
},
},
)...,
),
},
},
},
}

for _, test := range tests {
Expand Down Expand Up @@ -1717,6 +1796,14 @@ func affectedPkgSlice(a ...db.AffectedPackageHandle) []any {
return r
}

func unaffectedPkgSlice(a ...db.UnaffectedPackageHandle) []any {
var r []any
for _, v := range a {
r = append(r, v)
}
return r
}

func loadFixture(t *testing.T, fixturePath string) []unmarshal.OSVulnerability {
t.Helper()

Expand Down
20 changes: 10 additions & 10 deletions grype/matcher/apk/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ func (m *Matcher) Match(store vulnerability.Provider, p pkg.Package) ([]match.Ma
}

//nolint:funlen,gocognit
func (m *Matcher) cpeMatchesWithoutSecDBFixes(provider vulnerability.Provider, p pkg.Package) ([]match.Match, error) {
func (m *Matcher) cpeMatchesWithoutSecDBFixes(provider vulnerability.Provider, p pkg.Package) ([]match.Match, []match.IgnoreFilter, error) {
// find CPE-indexed vulnerability matches specific to the given package name and version
cpeMatches, err := internal.MatchPackageByCPEs(provider, p, m.Type())
cpeMatches, ignored, err := internal.MatchPackageByCPEs(provider, p, m.Type())
if err != nil {
log.WithFields("package", p.Name, "error", err).Debug("failed to find CPE matches for package")
}
if p.Distro == nil {
return cpeMatches, nil
return cpeMatches, ignored, nil
}

cpeMatchesByID := matchesByID(cpeMatches)
Expand All @@ -83,15 +83,15 @@ func (m *Matcher) cpeMatchesWithoutSecDBFixes(provider vulnerability.Provider, p
search.ByPackageName(p.Name),
search.ByDistro(*p.Distro))
if err != nil {
return nil, err
return nil, nil, err
}

for _, upstreamPkg := range pkg.UpstreamPackages(p) {
secDBVulnerabilitiesForUpstream, err := provider.FindVulnerabilities(
search.ByPackageName(upstreamPkg.Name),
search.ByDistro(*upstreamPkg.Distro))
if err != nil {
return nil, err
return nil, nil, err
}
secDBVulnerabilities = append(secDBVulnerabilities, secDBVulnerabilitiesForUpstream...)
}
Expand Down Expand Up @@ -131,7 +131,7 @@ cveLoop:
// ...is the current package vulnerable?
vulnerable, err := vuln.Constraint.Satisfied(verObj)
if err != nil {
return nil, err
return nil, nil, err
}

if vulnerable {
Expand All @@ -141,7 +141,7 @@ cveLoop:
}
}
}
return finalCpeMatches, nil
return finalCpeMatches, ignored, nil
}

func deduplicateMatches(secDBMatches, cpeMatches []match.Match) (matches []match.Match) {
Expand Down Expand Up @@ -179,13 +179,13 @@ func vulnerabilitiesByID(vulns []vulnerability.Vulnerability) map[string][]vulne
func (m *Matcher) findMatchesForPackage(store vulnerability.Provider, p pkg.Package, catalogPkg *pkg.Package) ([]match.Match, []match.IgnoreFilter, error) {
// find SecDB matches for the given package name and version
// APK doesn't use epochs, so pass nil for the config
secDBMatches, secDBIgnores, err := internal.MatchPackageByDistroWithOwnedFiles(store, p, catalogPkg, m.Type(), nil)
secDBMatches, secDBIgnores, err := internal.MatchPackageByDistro(store, p, catalogPkg, m.Type(), nil)
if err != nil {
return nil, nil, err
}

// TODO: are there other errors that we should handle here that causes this to short circuit
cpeMatches, err := m.cpeMatchesWithoutSecDBFixes(store, p)
cpeMatches, cpeIgnores, err := m.cpeMatchesWithoutSecDBFixes(store, p)
if err != nil && !errors.Is(err, internal.ErrEmptyCPEMatch) {
return nil, nil, err
}
Expand All @@ -198,7 +198,7 @@ func (m *Matcher) findMatchesForPackage(store vulnerability.Provider, p pkg.Pack
// keep only unique CPE matches
matches = append(matches, deduplicateMatches(secDBMatches, cpeMatches)...)

return matches, secDBIgnores, nil
return matches, append(secDBIgnores, cpeIgnores...), nil
}

func (m *Matcher) findMatchesForOriginPackage(store vulnerability.Provider, catalogPkg pkg.Package) ([]match.Match, []match.IgnoreFilter, error) {
Expand Down
8 changes: 8 additions & 0 deletions grype/matcher/apk/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,14 @@ func Test_ignoreFilters(t *testing.T) {
},
},
vulns: []vulnerability.Vulnerability{
{
Reference: vulnerability.Reference{
ID: "GHSA-2014-fake-2",
Namespace: "wolfi:distro:wolfi:rolling",
},
PackageName: "not-foo",
Constraint: version.MustGetConstraint("< 0", version.ApkFormat),
},
{
Reference: vulnerability.Reference{
ID: "GHSA-2014-fake-3",
Expand Down
23 changes: 12 additions & 11 deletions grype/matcher/dpkg/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,57 +37,58 @@ func (m *Matcher) Type() match.MatcherType {
}

func (m *Matcher) Match(store vulnerability.Provider, p pkg.Package) ([]match.Match, []match.IgnoreFilter, error) {
matches := make([]match.Match, 0)

sourceMatches, err := m.matchUpstreamPackages(store, p)
matches, ignores, err := m.matchUpstreamPackages(store, p)
if err != nil {
return nil, nil, fmt.Errorf("failed to match by source indirection: %w", err)
}
matches = append(matches, sourceMatches...)

versionConfig := version.ComparisonConfig{
MissingEpochStrategy: m.cfg.MissingEpochStrategy,
}
exactMatches, _, err := internal.MatchPackageByDistro(store, p, nil, m.Type(), &versionConfig)
exactMatches, exactIgnores, err := internal.MatchPackageByDistro(store, p, nil, m.Type(), &versionConfig)
if err != nil {
return nil, nil, fmt.Errorf("failed to match by exact package name: %w", err)
}
matches = append(matches, exactMatches...)
ignores = append(ignores, exactIgnores...)

// if configured, also search by CPEs for packages from EOL distros
if m.cfg.UseCPEsForEOL && internal.IsDistroEOL(store, p.Distro) {
log.WithFields("package", p.Name, "distro", p.Distro).Debug("distro is EOL, searching by CPEs")
cpeMatches, err := internal.MatchPackageByCPEs(store, p, m.Type())
cpeMatches, ignored, err := internal.MatchPackageByCPEs(store, p, m.Type())
switch {
case errors.Is(err, internal.ErrEmptyCPEMatch):
log.WithFields("package", p.Name).Debug("package has no CPEs for EOL fallback matching")
case err != nil:
log.WithFields("package", p.Name, "error", err).Debug("failed to match by CPEs for EOL distro")
default:
matches = append(matches, cpeMatches...)
ignores = append(ignores, ignored...)
}
}

return matches, nil, nil
return matches, ignores, nil
}

func (m *Matcher) matchUpstreamPackages(store vulnerability.Provider, p pkg.Package) ([]match.Match, error) {
func (m *Matcher) matchUpstreamPackages(store vulnerability.Provider, p pkg.Package) ([]match.Match, []match.IgnoreFilter, error) {
var matches []match.Match
var ignores []match.IgnoreFilter

versionConfig := version.ComparisonConfig{
MissingEpochStrategy: m.cfg.MissingEpochStrategy,
}
for _, indirectPackage := range pkg.UpstreamPackages(p) {
indirectMatches, _, err := internal.MatchPackageByDistro(store, indirectPackage, &p, m.Type(), &versionConfig)
indirectMatches, ignored, err := internal.MatchPackageByDistro(store, indirectPackage, &p, m.Type(), &versionConfig)
if err != nil {
return nil, fmt.Errorf("failed to find vulnerabilities for dpkg upstream source package: %w", err)
return nil, nil, fmt.Errorf("failed to find vulnerabilities for dpkg upstream source package: %w", err)
}
matches = append(matches, indirectMatches...)
ignores = append(ignores, ignored...)
}

// we want to make certain that we are tracking the match based on the package from the SBOM (not the indirect package)
// however, we also want to keep the indirect package around for future reference
match.ConvertToIndirectMatches(matches, p)

return matches, nil
return matches, ignores, nil
}
Loading
Loading