Skip to content

Commit c56ebc5

Browse files
authored
feat(CVE5): Add link to NVD and computed CVEList file and hash (#4422)
It only makes sense that we include the references to the places we source our CVE data, both for triage, and just record keeping. This PR also fixes a bug where empty urls are able to be saved, which shouldn't happen based on the CVE5's quality requirements, but better safe than sorry. During this process, identifyPossibleURLs deduplication logic was moved to its own function to be used better elsewhere if necessary.
1 parent 0fedba3 commit c56ebc5

File tree

6 files changed

+58
-33
lines changed

6 files changed

+58
-33
lines changed

vulnfeeds/cmd/cve-bulk-converter/main.go

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ import (
55
_ "embed"
66
"encoding/json"
77
"flag"
8-
"fmt"
98
"log/slog"
109
"os"
1110
"path/filepath"
1211
"slices"
1312
"strings"
1413
"sync"
1514

16-
"github.com/go-git/go-git/v5"
1715
"github.com/google/osv/vulnfeeds/cvelist2osv"
1816
"github.com/google/osv/vulnfeeds/cves"
1917
"github.com/google/osv/vulnfeeds/utility/logger"
@@ -53,16 +51,10 @@ func main() {
5351
}
5452
}
5553

56-
// Get the HEAD commit hash of the repo.
57-
commitHash, err := getHeadCommit(*repoDir)
58-
if err != nil {
59-
logger.Warn("Failed to get HEAD commit hash", slog.Any("err", err))
60-
}
61-
6254
// Start the worker pool.
6355
for range *workers {
6456
wg.Add(1)
65-
go worker(&wg, jobs, *localOutputDir, cnaList, commitHash)
57+
go worker(&wg, jobs, *localOutputDir, cnaList)
6658
}
6759

6860
// Discover files and send them to the workers.
@@ -98,7 +90,7 @@ func main() {
9890
}
9991

10092
// worker is a function that processes CVE files from the jobs channel.
101-
func worker(wg *sync.WaitGroup, jobs <-chan string, outDir string, cnas []string, commitHash string) {
93+
func worker(wg *sync.WaitGroup, jobs <-chan string, outDir string, cnas []string) {
10294
defer wg.Done()
10395
for path := range jobs {
10496
data, err := os.ReadFile(path)
@@ -126,13 +118,11 @@ func worker(wg *sync.WaitGroup, jobs <-chan string, outDir string, cnas []string
126118
}
127119

128120
sourceLink := ""
129-
if commitHash != "" {
130-
baseDirCVEList := "cves/" // The base folder for the CVEListV5 repository.
131-
idx := strings.Index(path, baseDirCVEList)
132-
if idx != -1 {
133-
relPath := path[idx:]
134-
sourceLink = fmt.Sprintf("https://github.com/CVEProject/cvelistV5/blob/%s/%s", commitHash, relPath)
135-
}
121+
baseDirCVEList := "cves/" // The base folder for the CVEListV5 repository.
122+
idx := strings.Index(path, baseDirCVEList)
123+
if idx != -1 {
124+
relPath := path[idx:]
125+
sourceLink = "https://github.com/CVEProject/cvelistV5/tree/main/" + relPath
136126
}
137127

138128
// Perform the conversion and export the results.
@@ -146,16 +136,3 @@ func worker(wg *sync.WaitGroup, jobs <-chan string, outDir string, cnas []string
146136
osvFile.Close()
147137
}
148138
}
149-
150-
func getHeadCommit(repoDir string) (string, error) {
151-
r, err := git.PlainOpen(repoDir)
152-
if err != nil {
153-
return "", err
154-
}
155-
ref, err := r.Head()
156-
if err != nil {
157-
return "", err
158-
}
159-
160-
return ref.Hash().String(), nil
161-
}

vulnfeeds/cvelist2osv/__snapshots__/converter_test.snap

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
"id": "CVE-2025-9999",
1111
"modified": "2025-05-04T07:20:46.575Z",
1212
"published": "2025-05-04T07:20:46.575Z",
13+
"references": [
14+
{
15+
"type": "ADVISORY",
16+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-9999"
17+
}
18+
],
1319
"schema_version": "1.7.3"
1420
}
1521
---
@@ -75,6 +81,10 @@
7581
{
7682
"type": "EVIDENCE",
7783
"url": "https://hackerone.com/reports/2972576"
84+
},
85+
{
86+
"type": "ADVISORY",
87+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-1110"
7888
}
7989
],
8090
"schema_version": "1.7.3",
@@ -136,6 +146,10 @@
136146
{
137147
"type": "ADVISORY",
138148
"url": "https://github.com/amazon-ion/ion-java/security/advisories/GHSA-264p-99wq-f4j6"
149+
},
150+
{
151+
"type": "ADVISORY",
152+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-21634"
139153
}
140154
],
141155
"schema_version": "1.7.3",
@@ -319,6 +333,10 @@
319333
{
320334
"type": "WEB",
321335
"url": "https://git.kernel.org/stable/c/a3e77da9f843e4ab93917d30c314f0283e28c124"
336+
},
337+
{
338+
"type": "ADVISORY",
339+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-21772"
322340
}
323341
],
324342
"schema_version": "1.7.3",

vulnfeeds/cvelist2osv/converter.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,15 @@ func ConvertAndExportCVEToOSV(cve cves.CVE5, vulnSink io.Writer, metricsSink io.
233233
cveID := cve.Metadata.CVEID
234234
cnaAssigner := cve.Metadata.AssignerShortName
235235
references := identifyPossibleURLs(cve)
236+
237+
// Add NVD and computed source link to references
238+
references = append(references, cves.Reference{URL: fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", cveID)})
239+
if sourceLink != "" {
240+
references = append(references, cves.Reference{URL: sourceLink})
241+
}
242+
243+
references = deduplicateRefs(references)
244+
236245
metrics := ConversionMetrics{CVEID: cveID, CNA: cnaAssigner, UnresolvedRangesCount: 0, ResolvedRangesCount: 0}
237246

238247
// Create a base OSV record from the CVE.
@@ -269,7 +278,7 @@ func ConvertAndExportCVEToOSV(cve cves.CVE5, vulnSink io.Writer, metricsSink io.
269278
return nil
270279
}
271280

272-
// identifyPossibleURLs extracts and deduplicates all URLs from a CVE object.
281+
// identifyPossibleURLs extracts all URLs from a CVE object.
273282
// It searches for URLs in the CNA and ADP reference sections, as well as in
274283
// the 'collectionUrl' and 'repo' fields of the 'affected' entries.
275284
func identifyPossibleURLs(cve cves.CVE5) []cves.Reference {
@@ -290,6 +299,19 @@ func identifyPossibleURLs(cve cves.CVE5) []cves.Reference {
290299
}
291300
}
292301

302+
// Filter out empty URLs from CNA references if any
303+
filteredRefs := make([]cves.Reference, 0, len(refs))
304+
for _, ref := range refs {
305+
if ref.URL != "" {
306+
filteredRefs = append(filteredRefs, ref)
307+
}
308+
}
309+
refs = filteredRefs
310+
311+
return refs
312+
}
313+
314+
func deduplicateRefs(refs []cves.Reference) []cves.Reference {
293315
// Deduplicate references by URL.
294316
slices.SortStableFunc(refs, func(a, b cves.Reference) int {
295317
return strings.Compare(a.URL, b.URL)

vulnfeeds/cvelist2osv/converter_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ func TestIdentifyPossibleURLs(t *testing.T) {
8585
{URL: "http://a.com"},
8686
{URL: "http://b.com"},
8787
{URL: "http://c.com"},
88+
{URL: "http://a.com"},
8889
{URL: "http://d.com"},
90+
{URL: "http://b.com"},
8991
},
9092
},
9193
{
@@ -100,7 +102,7 @@ func TestIdentifyPossibleURLs(t *testing.T) {
100102
},
101103
},
102104
},
103-
expectedRefs: nil,
105+
expectedRefs: []cves.Reference{},
104106
},
105107
{
106108
name: "no references and CNA refs is empty slice",
@@ -137,7 +139,6 @@ func TestIdentifyPossibleURLs(t *testing.T) {
137139
},
138140
},
139141
expectedRefs: []cves.Reference{
140-
{URL: ""},
141142
{URL: "http://a.com"},
142143
},
143144
},

vulnfeeds/vulns/vulns.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,12 @@ func ClassifyReferenceLink(link string, tag string) osvschema.Reference_Type {
425425
// Index 0 will always be "", so the length must be at least 2 to be relevant
426426
if len(pathParts) >= 2 {
427427
if u.Host == "github.com" {
428+
// CVEProject CVEList reference
429+
// Example: https://github.com/CVEProject/cvelistV5/blob/7dd48109ee33618242f36ae1d14d80950f2d0e59/cves/2021/45xxx/CVE-2021-45931.json
430+
if len(pathParts) >= 3 && pathParts[1] == "CVEProject" {
431+
return osvschema.Reference_ADVISORY
432+
}
433+
428434
// Example: https://github.com/google/osv/commit/cd4e934d0527e5010e373e7fed54ef5daefba2f5
429435
if len(pathParts) >= 3 && pathParts[len(pathParts)-2] == "commit" {
430436
return osvschema.Reference_FIX

vulnfeeds/vulns/vulns_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func TestClassifyReferenceLink(t *testing.T) {
3333
{"https://github.com/Netflix/lemur/issues/117", "", osvschema.Reference_REPORT},
3434
{"https://snyk.io/vuln/SNYK-PYTHON-TRYTOND-1730329", "", osvschema.Reference_ADVISORY},
3535
{"https://nvd.nist.gov/vuln/detail/CVE-2021-23336", "", osvschema.Reference_ADVISORY},
36+
{"https://github.com/CVEProject/cvelistV5/blob/545d1041e7c903230240d4c5f86550d266784f99/cves/2025/10xxx/CVE-2025-10316.json", "", osvschema.Reference_ADVISORY},
3637
{"https://www.debian.org/security/2021/dsa-4878", "", osvschema.Reference_ADVISORY},
3738
{"https://usn.ubuntu.com/usn/usn-4661-1", "", osvschema.Reference_ADVISORY},
3839
{"http://www.ubuntu.com/usn/USN-2915-2", "", osvschema.Reference_ADVISORY},

0 commit comments

Comments
 (0)