diff --git a/internal/report/lint.go b/internal/report/lint.go index 2761fb40..5ef1f2bd 100644 --- a/internal/report/lint.go +++ b/internal/report/lint.go @@ -133,6 +133,34 @@ func (r *Report) lintCVEs(addIssue func(string)) { } } +func (r *Report) lintRelated(addIssue func(string)) { + if len(r.Related) == 0 { + return + } + + aliases := r.Aliases() + for _, related := range r.Related { + // In most cases, the related list is very short, so there's no + // need create a map of aliases. + if slices.Contains(aliases, related) { + addIssue(fmt.Sprintf("related: identifier %s is also listed among aliases", related)) + } + if !isIdentifier(related) { + addIssue(fmt.Sprintf("related: %s is not a recognized identifier (CVE, GHSA or Go ID)", related)) + } + } +} + +func isIdentifier(id string) bool { + return cveschema5.IsCVE(id) || ghsa.IsGHSA(id) || IsGoID(id) +} + +var goIDregexp = regexp.MustCompile(`^GO-\d{4}-\d{4,}$`) + +func IsGoID(s string) bool { + return goIDregexp.MatchString(s) +} + const maxLineLength = 80 func (r *Report) lintLineLength(field, content string, addIssue func(string)) { @@ -410,6 +438,7 @@ func (r *Report) lint(pc *proxy.Client) []string { r.lintLineLength("cve_metadata.description", r.CVEMetadata.Description, addIssue) } r.lintCVEs(addIssue) + r.lintRelated(addIssue) if isFirstParty && !r.IsExcluded() { r.lintStdLibLinks(addIssue) diff --git a/internal/report/lint_test.go b/internal/report/lint_test.go index 41aaa80a..c10ede73 100644 --- a/internal/report/lint_test.go +++ b/internal/report/lint_test.go @@ -474,6 +474,23 @@ func TestLintOffline(t *testing.T) { "excluded report must have at least one associated CVE or GHSA", }, }, + { + desc: "related field", + report: validReport(func(r *Report) { + r.CVEs = []string{"CVE-0000-1111"} + r.Related = []string{ + "not-an-id", // bad + "CVE-0000-1111", // bad (duplicate) + "CVE-0000-1112", // ok + "GHSA-0000-0000-0000", // ok + "GO-1990-0001", // ok + } + }), + want: []string{ + "not-an-id is not a recognized identifier", + "CVE-0000-1111 is also listed among aliases", + }, + }, { desc: "invalid module-version pair ignored", report: validReport(func(r *Report) { diff --git a/internal/report/report.go b/internal/report/report.go index 9fa1ed9f..aa9ce428 100644 --- a/internal/report/report.go +++ b/internal/report/report.go @@ -204,6 +204,10 @@ type Report struct { // the above CVEs. GHSAs []string `yaml:",omitempty"` + // Related is a list of identifiers (e.g. CVEs or GHSAs) + // that are related to, but are not direct aliases of, this report. + Related []string `yaml:",omitempty"` + Credits []string `yaml:",omitempty"` References []*Reference `yaml:",omitempty"`