From f2baf2b3154acdf5148a85b06a6a1bb9a86d6f0e Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 12 Dec 2024 13:01:56 -0500 Subject: [PATCH] ensure we deduplicate vuln rows Signed-off-by: Alex Goodman --- cmd/grype/cli/commands/db_search_pkg.go | 2 +- cmd/grype/cli/commands/db_search_vuln.go | 51 +++++++++++++++++++----- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/cmd/grype/cli/commands/db_search_pkg.go b/cmd/grype/cli/commands/db_search_pkg.go index 56805c1aa01..7ed6cf5d812 100644 --- a/cmd/grype/cli/commands/db_search_pkg.go +++ b/cmd/grype/cli/commands/db_search_pkg.go @@ -48,7 +48,7 @@ func DBSearchPackages(app clio.Application) *cobra.Command { return app.SetupCommand(&cobra.Command{ Use: "pkg PURL|CPE|NAME...", Aliases: []string{"package", "packages", "pkgs"}, - Short: "get information regarding packages affected by vulnerabilities from the db", + Short: "Search for packages affected by vulnerabilities within the db", Args: func(_ *cobra.Command, args []string) error { opts.Package.Names = args return nil diff --git a/cmd/grype/cli/commands/db_search_vuln.go b/cmd/grype/cli/commands/db_search_vuln.go index ec1da07174f..6b29a0109e9 100644 --- a/cmd/grype/cli/commands/db_search_vuln.go +++ b/cmd/grype/cli/commands/db_search_vuln.go @@ -47,7 +47,7 @@ func DBSearchVulnerabilities(app clio.Application) *cobra.Command { return app.SetupCommand(&cobra.Command{ Use: "vuln ID...", Aliases: []string{"vulnerability", "vulnerabilities", "vulns"}, - Short: "get information regarding vulnerabilities from the db", + Short: "Search for vulnerabilities within the DB", Args: func(_ *cobra.Command, args []string) error { if len(args) == 0 { return fmt.Errorf("must specify at least one vulnerability ID") @@ -159,7 +159,15 @@ func presentDBSearchVulnerabilities(outputFormat string, structuredRows []dbsear } func renderDBSearchVulnerabilitiesTableRows(structuredRows []dbsearch.VulnerabilityRow) [][]string { - var rows [][]string + type row struct { + Vuln string + ProviderWithoutVersions string + PublishedDate string + Severity string + Reference string + } + + versionsByRow := make(map[row][]string) for _, rr := range structuredRows { // get the first severity value (which is ranked highest) var sev string @@ -168,12 +176,9 @@ func renderDBSearchVulnerabilitiesTableRows(structuredRows []dbsearch.Vulnerabil } prov := rr.Provider - if len(rr.OperatingSystems) > 0 { - var versions []string - for _, os := range rr.OperatingSystems { - versions = append(versions, os.Version) - } - prov = fmt.Sprintf("%s (%s)", rr.Provider, strings.Join(versions, ", ")) + var versions []string + for _, os := range rr.OperatingSystems { + versions = append(versions, os.Version) } var published string @@ -186,7 +191,35 @@ func renderDBSearchVulnerabilitiesTableRows(structuredRows []dbsearch.Vulnerabil ref = rr.References[0].URL } - rows = append(rows, []string{rr.ID, prov, published, sev, ref}) + r := row{ + Vuln: rr.ID, + ProviderWithoutVersions: prov, + PublishedDate: published, + Severity: sev, + Reference: ref, + } + versionsByRow[r] = append(versionsByRow[r], versions...) } + + var rows [][]string + for r, versions := range versionsByRow { + prov := r.ProviderWithoutVersions + if len(versions) > 0 { + sort.Strings(versions) + prov = fmt.Sprintf("%s (%s)", r.ProviderWithoutVersions, strings.Join(versions, ", ")) + } + rows = append(rows, []string{r.Vuln, prov, r.PublishedDate, r.Severity, r.Reference}) + } + + // sort rows by each column + sort.Slice(rows, func(i, j int) bool { + for k := range rows[i] { + if rows[i][k] != rows[j][k] { + return rows[i][k] < rows[j][k] + } + } + return false + }) + return rows }