Skip to content

Commit 75a5eab

Browse files
authored
feat: group DSA and its CVEs together (google#1262)
For container scanning, we should only display DSA results and hide their related CVEs. - Add DSA's related CVEs to the same group. - Sort the group IDs with the DSA first. (DSA is more important than CVE, and one DSA contains multiple CVEs) - Don't print CVE records when there is a DSA (this reduces noise in container scanning results).
1 parent 56e3a99 commit 75a5eab

File tree

8 files changed

+127
-173
lines changed

8 files changed

+127
-173
lines changed

cmd/osv-scanner/__snapshots__/main_test.snap

Lines changed: 76 additions & 152 deletions
Large diffs are not rendered by default.

internal/output/identifiers.go renamed to internal/identifiers/identifiers.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
package output
1+
package identifiers
22

33
import (
44
"strings"
55
)
66

77
func prefixOrder(prefix string) int {
8-
if prefix == "CVE" {
9-
// Highest precedence
8+
if prefix == "DSA" {
9+
// Special case: For container scanning, DSA contains multiple CVEs and is more accurate.
10+
return 3
11+
} else if prefix == "CVE" {
12+
// Highest precedence for normal cases
1013
return 2
1114
} else if prefix == "GHSA" {
1215
// Lowest precedence
@@ -26,16 +29,6 @@ func prefixOrderForDescription(prefix string) int {
2629
return 2
2730
}
2831

29-
// idSortFunc sorts IDs ascending by CVE < [ECO-SPECIFIC] < GHSA
30-
func idSortFunc(a, b string) int {
31-
return idSort(a, b, prefixOrder)
32-
}
33-
34-
// idSortFuncForDescription sorts ID ascending by [ECO-SPECIFIC] < GHSA < CVE
35-
func idSortFuncForDescription(a, b string) int {
36-
return idSort(a, b, prefixOrderForDescription)
37-
}
38-
3932
func idSort(a, b string, prefixOrd func(string) int) int {
4033
prefixAOrd := prefixOrd(strings.Split(a, "-")[0])
4134
prefixBOrd := prefixOrd(strings.Split(b, "-")[0])
@@ -48,3 +41,13 @@ func idSort(a, b string, prefixOrd func(string) int) int {
4841

4942
return strings.Compare(a, b)
5043
}
44+
45+
// IDSortFunc sorts IDs ascending by CVE < [ECO-SPECIFIC] < GHSA
46+
func IDSortFunc(a, b string) int {
47+
return idSort(a, b, prefixOrder)
48+
}
49+
50+
// IDSortFuncForDescription sorts ID ascending by [ECO-SPECIFIC] < GHSA < CVE
51+
func IDSortFuncForDescription(a, b string) int {
52+
return idSort(a, b, prefixOrderForDescription)
53+
}

internal/output/identifiers_test.go renamed to internal/identifiers/identifiers_test.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package output
1+
package identifiers
22

33
import (
44
"slices"
@@ -36,7 +36,7 @@ func Test_idSortFunc(t *testing.T) {
3636
t.Run(tt.name, func(t *testing.T) {
3737
t.Parallel()
3838

39-
if got := idSortFunc(tt.args.a, tt.args.b); got != tt.want {
39+
if got := IDSortFunc(tt.args.a, tt.args.b); got != tt.want {
4040
t.Errorf("idSortFunc() = %v, want %v", got, tt.want)
4141
}
4242
})
@@ -65,12 +65,19 @@ func Test_idSortFuncUsage(t *testing.T) {
6565
},
6666
want: "RUSTSEC-2012-1234",
6767
},
68+
{
69+
args: []string{
70+
"CVE-2012-1234",
71+
"DSA-2012-1234",
72+
},
73+
want: "DSA-2012-1234",
74+
},
6875
}
6976
for _, tt := range tests {
7077
t.Run(tt.name, func(t *testing.T) {
7178
t.Parallel()
7279

73-
if got := slices.MinFunc(tt.args, idSortFunc); got != tt.want {
80+
if got := slices.MinFunc(tt.args, IDSortFunc); got != tt.want {
7481
t.Errorf("slices.MinFunc = %v, want %v", got, tt.want)
7582
}
7683
})

internal/output/result.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"slices"
88
"strings"
99

10+
"github.com/google/osv-scanner/internal/identifiers"
1011
"github.com/google/osv-scanner/pkg/models"
1112
"golang.org/x/exp/maps"
1213
)
@@ -159,7 +160,7 @@ func mapIDsToGroupedSARIFFinding(vulns *models.VulnerabilityResults) map[string]
159160
}
160161

161162
for _, gs := range results {
162-
slices.SortFunc(gs.AliasedIDList, idSortFunc)
163+
slices.SortFunc(gs.AliasedIDList, identifiers.IDSortFunc)
163164
gs.AliasedIDList = slices.Compact(gs.AliasedIDList)
164165
gs.DisplayID = gs.AliasedIDList[0]
165166
}

internal/output/sarif.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010
"text/template"
1111

12+
"github.com/google/osv-scanner/internal/identifiers"
1213
"github.com/google/osv-scanner/internal/url"
1314
"github.com/google/osv-scanner/internal/utility/results"
1415
"github.com/google/osv-scanner/internal/version"
@@ -191,7 +192,7 @@ func createSARIFHelpText(gv *groupedSARIFFinding) string {
191192
Details: strings.ReplaceAll(v.Details, "\n", "\n> "),
192193
})
193194
}
194-
slices.SortFunc(vulnDescriptions, func(a, b VulnDescription) int { return idSortFunc(a.ID, b.ID) })
195+
slices.SortFunc(vulnDescriptions, func(a, b VulnDescription) int { return identifiers.IDSortFunc(a.ID, b.ID) })
195196

196197
helpText := strings.Builder{}
197198

@@ -250,7 +251,7 @@ func PrintSARIFReport(vulnResult *models.VulnerabilityResults, outputWriter io.W
250251
// or use a random long description.
251252
var shortDescription, longDescription string
252253
ids := slices.Clone(gv.AliasedIDList)
253-
slices.SortFunc(ids, idSortFuncForDescription)
254+
slices.SortFunc(ids, identifiers.IDSortFuncForDescription)
254255

255256
for _, id := range ids {
256257
v := gv.AliasedVulns[id]

internal/output/table.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ func tableBuilderInner(vulnResult *models.VulnerabilityResults, calledVulns bool
114114

115115
for _, vuln := range group.IDs {
116116
links = append(links, OSVBaseVulnerabilityURL+text.Bold.Sprintf("%s", vuln))
117+
118+
// For container scanning results, if there is a DSA, then skip printing its sub-CVEs.
119+
if strings.Split(vuln, "-")[0] == "DSA" {
120+
break
121+
}
117122
}
118123

119124
outputRow = append(outputRow, strings.Join(links, "\n"))

pkg/grouper/grouper.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"golang.org/x/exp/maps"
88

9+
"github.com/google/osv-scanner/internal/identifiers"
910
"github.com/google/osv-scanner/pkg/models"
1011
)
1112

@@ -56,7 +57,7 @@ func Group(vulns []IDAliases) []models.GroupInfo {
5657
result := make([]models.GroupInfo, 0, len(sortedKeys))
5758
for _, key := range sortedKeys {
5859
// Sort the strings so they are always in the same order
59-
sort.Strings(extractedGroups[key])
60+
slices.SortFunc(extractedGroups[key], identifiers.IDSortFunc)
6061

6162
// Add IDs to aliases
6263
extractedAliases[key] = append(extractedAliases[key], extractedGroups[key]...)

pkg/grouper/grouper_models.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package grouper
22

3-
import "github.com/google/osv-scanner/pkg/models"
3+
import (
4+
"strings"
5+
6+
"github.com/google/osv-scanner/pkg/models"
7+
)
48

59
type IDAliases struct {
610
ID string
@@ -14,6 +18,14 @@ func ConvertVulnerabilityToIDAliases(c []models.Vulnerability) []IDAliases {
1418
ID: v.ID,
1519
Aliases: v.Aliases,
1620
}
21+
22+
// For Debian Security Advisory data,
23+
// all related CVEs should be bundled together, as they are part of this DSA.
24+
// TODO(gongh@): Revisit and provide a universal way to handle all Linux distro advisories.
25+
if strings.Split(v.ID, "-")[0] == "DSA" {
26+
idAliases.Aliases = append(idAliases.Aliases, v.Related...)
27+
}
28+
1729
output = append(output, idAliases)
1830
}
1931

0 commit comments

Comments
 (0)