Skip to content

Commit 0dd498a

Browse files
committed
code review - simplifies state management, adds some tests
1 parent 31af0f1 commit 0dd498a

16 files changed

Lines changed: 401 additions & 104 deletions

internal/core/assetversion/asset_version_service.go

Lines changed: 45 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func (s *service) HandleScanResult(asset models.Asset, assetVersion *models.Asse
183183
Vulnerability: models.Vulnerability{
184184
AssetVersionName: assetVersion.Name,
185185
AssetID: asset.ID,
186-
ScannerIDs: scannerID + " ",
186+
ScannerIDs: scannerID,
187187
},
188188
CVEID: utils.Ptr(v.CVEID),
189189
ComponentPurl: utils.Ptr(v.Purl),
@@ -209,7 +209,6 @@ func (s *service) HandleScanResult(asset models.Asset, assetVersion *models.Asse
209209
devguardScanner := "github.com/l3montree-dev/devguard/cmd/devguard-scanner" + "/"
210210

211211
switch scannerID {
212-
213212
case devguardScanner + "sca":
214213
assetVersion.LastScaScan = utils.Ptr(time.Now())
215214
case devguardScanner + "container-scanning":
@@ -219,12 +218,43 @@ func (s *service) HandleScanResult(asset models.Asset, assetVersion *models.Asse
219218
return amountOpened, amountClosed, amountExisting, nil
220219
}
221220

221+
func diffScanResults(currentScanner string, foundVulnerabilities []models.DependencyVuln, existingDependencyVulns []models.DependencyVuln) ([]models.DependencyVuln, []models.DependencyVuln, []models.DependencyVuln, []models.DependencyVuln) {
222+
comparison := utils.CompareSlices(existingDependencyVulns, foundVulnerabilities, func(dependencyVuln models.DependencyVuln) string {
223+
return dependencyVuln.CalculateHash()
224+
})
225+
226+
foundByScannerAndNotExisting := comparison.OnlyInB //We want to create new vulnerabilities for these
227+
foundByScannerAndExisting := comparison.InBoth //We have to check if it was already found by this scanner or only by other scanners
228+
notFoundByScannerAndExisting := comparison.OnlyInA //We have to update all vulnerabilities which were previously found by this scanner and now aren't
229+
230+
var detectedByOtherScanner []models.DependencyVuln
231+
var notDetectedByScannerAnymore []models.DependencyVuln
232+
233+
var fixedVulns []models.DependencyVuln //We should collect all vulnerabilities we want to fix so we can do it all at once
234+
235+
// Now we work on the vulnerabilities found in both sets -> has the vulnerability this scanner id already in his scanner_ids
236+
for i := range foundByScannerAndExisting {
237+
if !strings.Contains(foundByScannerAndExisting[i].ScannerIDs, currentScanner) {
238+
detectedByOtherScanner = append(detectedByOtherScanner, foundByScannerAndExisting[i])
239+
}
240+
}
241+
242+
// Last we have to change the already existing vulnerabilities which were not found this time
243+
for i := range notFoundByScannerAndExisting {
244+
if strings.TrimSpace(notFoundByScannerAndExisting[i].ScannerIDs) == currentScanner {
245+
fixedVulns = append(fixedVulns, notFoundByScannerAndExisting[i])
246+
} else if strings.Contains(notFoundByScannerAndExisting[i].ScannerIDs, currentScanner) {
247+
notDetectedByScannerAnymore = append(notDetectedByScannerAnymore, notFoundByScannerAndExisting[i])
248+
}
249+
}
250+
251+
return foundByScannerAndNotExisting, fixedVulns, detectedByOtherScanner, notDetectedByScannerAnymore
252+
}
253+
222254
func (s *service) handleScanResult(userID string, scannerID string, assetVersion *models.AssetVersion, dependencyVulns []models.DependencyVuln, doRiskManagement bool, asset models.Asset) (int, int, []models.DependencyVuln, error) {
223255
// get all existing dependencyVulns from the database - this is the old state
224-
225256
//number := rand.IntN(len(dependencyVulns))
226257
//dependencyVulns = dependencyVulns[:0]
227-
scannerID = scannerID + " "
228258
existingDependencyVulns, err := s.dependencyVulnRepository.ListByAssetAndAssetVersion(assetVersion.Name, assetVersion.AssetID)
229259
if err != nil {
230260
slog.Error("could not get existing dependencyVulns", "err", err)
@@ -236,84 +266,36 @@ func (s *service) handleScanResult(userID string, scannerID string, assetVersion
236266
return dependencyVuln.State != models.VulnStateFixed
237267
})
238268

239-
comparison := utils.CompareSlices(existingDependencyVulns, dependencyVulns, func(dependencyVuln models.DependencyVuln) string {
240-
return dependencyVuln.CalculateHash()
241-
})
242-
243-
foundByScannerAndNotExisting := comparison.OnlyInB //We want to create new vulnerabilities for these
244-
foundByScannerAndExisting := comparison.InBoth //We have to check if it was already found by this scanner or only by other scanners
245-
notFoundByScannerAndExisting := comparison.OnlyInA //We have to update all vulnerabilities which were previously found by this scanner and now aren't
269+
newDetectedVulns, fixedVulns, firstTimeDetectedByCurrentScanner, notDetectedByCurrentScannerAnymore := diffScanResults(scannerID, dependencyVulns, existingDependencyVulns)
270+
amountFixed := len(utils.Filter(fixedVulns, func(dependencyVuln models.DependencyVuln) bool {
271+
return dependencyVuln.State == models.VulnStateOpen
272+
}))
246273

247-
var vulnerabilitiesToFix []models.DependencyVuln //We should collect all vulnerabilities we want to fix so we can do it all at once
248-
var vulnerabilitiesToUpdate []models.DependencyVuln //We should do the same
249-
// get a transaction
250274
if err := s.dependencyVulnRepository.Transaction(func(tx core.DB) error {
251275
// We can create the newly found one without checking anything
252-
if err := s.dependencyVulnService.UserDetectedDependencyVulns(tx, userID, foundByScannerAndNotExisting, *assetVersion, asset, true); err != nil {
276+
if err := s.dependencyVulnService.UserDetectedDependencyVulns(tx, userID, newDetectedVulns, *assetVersion, asset, true); err != nil {
253277
return err // this will cancel the transaction
254278
}
255279

256-
// Now we work on the vulnerabilities found in both sets -> has the vulnerability this scanner id already in his scanner_ids
257-
for i := range foundByScannerAndExisting {
258-
if !strings.Contains(foundByScannerAndExisting[i].ScannerIDs, scannerID) {
259-
foundByScannerAndExisting[i].ScannerIDs = foundByScannerAndExisting[i].ScannerIDs + " " + scannerID
260-
vulnerabilitiesToUpdate = append(vulnerabilitiesToUpdate, foundByScannerAndExisting[i])
261-
}
262-
}
263-
err = s.dependencyVulnRepository.SaveBatch(tx, foundByScannerAndExisting)
264-
if err != nil {
265-
slog.Error("error when trying to update vulnerabilities")
266-
return err
267-
}
268-
269-
err = s.dependencyVulnService.MakeAddedScannerEvent(tx, vulnerabilitiesToUpdate, userID)
280+
err = s.dependencyVulnService.UserDetectedDependencyVulnWithAnotherScanner(tx, firstTimeDetectedByCurrentScanner, userID, scannerID)
270281
if err != nil {
271282
slog.Error("error when trying to add events for adding scanner to vulnerability")
272283
return err
273284
}
274285

275-
vulnerabilitiesToUpdate = nil
276-
277-
//Last we have to change the already existing vulnerabilities which were not found this time
278-
for i := range notFoundByScannerAndExisting {
279-
if notFoundByScannerAndExisting[i].ScannerIDs == scannerID {
280-
notFoundByScannerAndExisting[i].ScannerIDs = ""
281-
vulnerabilitiesToFix = append(vulnerabilitiesToFix, notFoundByScannerAndExisting[i])
282-
} else if strings.Contains(notFoundByScannerAndExisting[i].ScannerIDs, scannerID) {
283-
removeScannerFromVulnerability(&notFoundByScannerAndExisting[i], scannerID)
284-
vulnerabilitiesToUpdate = append(vulnerabilitiesToUpdate, notFoundByScannerAndExisting[i])
285-
}
286-
}
287-
288-
err = s.dependencyVulnRepository.SaveBatch(tx, vulnerabilitiesToUpdate)
289-
if err != nil {
290-
slog.Error("error when trying to update vulnerabilities")
291-
return err
292-
}
293-
294-
err := s.dependencyVulnService.MakeRemoveScannerEvent(tx, vulnerabilitiesToUpdate, userID)
286+
err := s.dependencyVulnService.UserDidNotDetectDependencyVulnWithScannerAnymore(tx, notDetectedByCurrentScannerAnymore, userID, scannerID)
295287
if err != nil {
296288
slog.Error("error when trying to add events for removing scanner from vulnerability")
297289
return err
298290
}
299291

300-
return s.dependencyVulnService.UserFixedDependencyVulns(tx, userID, vulnerabilitiesToFix, *assetVersion, asset, true)
292+
return s.dependencyVulnService.UserFixedDependencyVulns(tx, userID, fixedVulns, *assetVersion, asset, true)
301293
}); err != nil {
302294
slog.Error("could not save dependencyVulns", "err", err)
303295
return 0, 0, []models.DependencyVuln{}, err
304296
}
305297

306-
// the amount we actually fixed, is the amount that was open before
307-
vulnerabilitiesToFix = utils.Filter(vulnerabilitiesToFix, func(dependencyVuln models.DependencyVuln) bool {
308-
return dependencyVuln.State == models.VulnStateOpen
309-
})
310-
return len(foundByScannerAndNotExisting /* maybe also return vulns newly found by this scanner*/), len(vulnerabilitiesToFix), append(foundByScannerAndNotExisting, comparison.InBoth...), nil
311-
}
312-
313-
// pass by reference to edit the actual vulnerability and not a copy
314-
func removeScannerFromVulnerability(vulnerability *models.DependencyVuln, scannerID string) {
315-
316-
vulnerability.ScannerIDs = strings.Replace(vulnerability.ScannerIDs, scannerID, "", 1)
298+
return len(newDetectedVulns) + len(firstTimeDetectedByCurrentScanner), amountFixed, append(newDetectedVulns, firstTimeDetectedByCurrentScanner...), nil
317299
}
318300

319301
func recursiveBuildBomRefMap(component cdx.Component) map[string]cdx.Component {
@@ -348,7 +330,6 @@ func buildBomRefMap(bom normalize.SBOM) map[string]cdx.Component {
348330

349331
func (s *service) UpdateSBOM(assetVersion models.AssetVersion, scannerID string, sbom normalize.SBOM) error {
350332
// load the asset components
351-
352333
assetComponents, err := s.componentRepository.LoadComponents(nil, assetVersion.Name, assetVersion.AssetID, "")
353334
if err != nil {
354335
return errors.Wrap(err, "could not load asset components")
@@ -385,7 +366,7 @@ func (s *service) UpdateSBOM(assetVersion models.AssetVersion, scannerID string,
385366
dependencies = append(dependencies,
386367
models.ComponentDependency{
387368
ComponentPurl: nil, // direct dependency - therefore set it to nil
388-
ScannerIDs: scannerID + " ",
369+
ScannerIDs: scannerID,
389370
DependencyPurl: componentPackageUrl,
390371
},
391372
)
@@ -411,7 +392,7 @@ func (s *service) UpdateSBOM(assetVersion models.AssetVersion, scannerID string,
411392
dependencies = append(dependencies,
412393
models.ComponentDependency{
413394
ComponentPurl: utils.EmptyThenNil(compPackageUrl),
414-
ScannerIDs: scannerID + " ",
395+
ScannerIDs: scannerID,
415396
DependencyPurl: depPurlOrName,
416397
},
417398
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package assetversion
2+
3+
import (
4+
"testing"
5+
6+
"github.com/l3montree-dev/devguard/internal/database/models"
7+
"github.com/l3montree-dev/devguard/internal/utils"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestDiffScanResults(t *testing.T) {
12+
13+
t.Run("should correctly identify a vulnerability which now gets found by another scanner", func(t *testing.T) {
14+
currentScanner := "new-scanner"
15+
16+
foundVulnerabilities := []models.DependencyVuln{
17+
{CVEID: utils.Ptr("CVE-1234")},
18+
}
19+
20+
existingDependencyVulns := []models.DependencyVuln{
21+
{CVEID: utils.Ptr("CVE-1234"), Vulnerability: models.Vulnerability{ScannerIDs: "scanner-1"}},
22+
}
23+
24+
foundByScannerAndNotExisting, fixedVulns, detectedByCurrentScanner, notDetectedByCurrentScannerAnymore := diffScanResults(currentScanner, foundVulnerabilities, existingDependencyVulns)
25+
26+
assert.Empty(t, foundByScannerAndNotExisting)
27+
assert.Empty(t, fixedVulns)
28+
assert.Empty(t, notDetectedByCurrentScannerAnymore)
29+
assert.Equal(t, 1, len(detectedByCurrentScanner))
30+
})
31+
32+
t.Run("should correctly identify a vulnerability which now is fixed, since it was not found by the scanner anymore", func(t *testing.T) {
33+
currentScanner := "new-scanner"
34+
35+
foundVulnerabilities := []models.DependencyVuln{}
36+
37+
existingDependencyVulns := []models.DependencyVuln{
38+
{CVEID: utils.Ptr("CVE-1234"), Vulnerability: models.Vulnerability{ScannerIDs: currentScanner}},
39+
}
40+
41+
foundByScannerAndNotExisting, fixedVulns, detectedByCurrentScanner, notDetectedByCurrentScannerAnymore := diffScanResults(currentScanner, foundVulnerabilities, existingDependencyVulns)
42+
43+
assert.Empty(t, foundByScannerAndNotExisting)
44+
assert.Equal(t, 1, len(fixedVulns))
45+
assert.Empty(t, detectedByCurrentScanner)
46+
assert.Empty(t, notDetectedByCurrentScannerAnymore)
47+
})
48+
49+
t.Run("should correctly identify a vulnerability which is not detected by the current scanner anymore", func(t *testing.T) {
50+
currentScanner := "new-scanner"
51+
52+
foundVulnerabilities := []models.DependencyVuln{}
53+
54+
existingDependencyVulns := []models.DependencyVuln{
55+
{CVEID: utils.Ptr("CVE-1234"), Vulnerability: models.Vulnerability{ScannerIDs: currentScanner + " scanner-1"}},
56+
}
57+
58+
foundByScannerAndNotExisting, fixedVulns, detectedByCurrentScanner, notDetectedByCurrentScannerAnymore := diffScanResults(currentScanner, foundVulnerabilities, existingDependencyVulns)
59+
60+
assert.Empty(t, foundByScannerAndNotExisting)
61+
assert.Empty(t, fixedVulns)
62+
assert.Empty(t, detectedByCurrentScanner)
63+
assert.Equal(t, 1, len(notDetectedByCurrentScannerAnymore))
64+
})
65+
66+
t.Run("should identify new vulnerabilities", func(t *testing.T) {
67+
currentScanner := "new-scanner"
68+
69+
foundVulnerabilities := []models.DependencyVuln{
70+
{CVEID: utils.Ptr("CVE-1234")},
71+
{CVEID: utils.Ptr("CVE-5678")},
72+
}
73+
74+
existingDependencyVulns := []models.DependencyVuln{}
75+
76+
foundByScannerAndNotExisting, fixedVulns, detectedByCurrentScanner, notDetectedByCurrentScannerAnymore := diffScanResults(currentScanner, foundVulnerabilities, existingDependencyVulns)
77+
78+
assert.Equal(t, 2, len(foundByScannerAndNotExisting))
79+
assert.Empty(t, fixedVulns)
80+
assert.Empty(t, detectedByCurrentScanner)
81+
assert.Empty(t, notDetectedByCurrentScannerAnymore)
82+
})
83+
}

internal/core/common_interfaces.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ type DependencyVulnService interface {
183183
RecalculateRawRiskAssessment(tx DB, responsible string, dependencyVulns []models.DependencyVuln, justification string, asset models.Asset) error
184184
UserFixedDependencyVulns(tx DB, userID string, dependencyVulns []models.DependencyVuln, assetVersion models.AssetVersion, asset models.Asset, doRiskManagement bool) error
185185
UserDetectedDependencyVulns(tx DB, userID string, dependencyVulns []models.DependencyVuln, assetVersion models.AssetVersion, asset models.Asset, doRiskManagement bool) error
186-
MakeAddedScannerEvent(tx DB, vulnerabilities []models.DependencyVuln, userID string) error
187-
MakeRemoveScannerEvent(tx DB, vulnerabilities []models.DependencyVuln, userID string) error
186+
UserDetectedDependencyVulnWithAnotherScanner(tx DB, vulnerabilities []models.DependencyVuln, userID string, scannerID string) error
187+
UserDidNotDetectDependencyVulnWithScannerAnymore(tx DB, vulnerabilities []models.DependencyVuln, userID string, scannerID string) error
188188
UpdateDependencyVulnState(tx DB, assetID uuid.UUID, userID string, dependencyVuln *models.DependencyVuln, statusType string, justification string, assetVersionName string) (models.VulnEvent, error)
189189
CreateIssuesForVulns(asset models.Asset, vulnList []models.DependencyVuln) error
190190
ShouldCreateIssue(assetVersion models.AssetVersion) bool

internal/core/component/component_dto.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type componentDTO struct {
1414
Dependency models.Component `json:"dependency"`
1515
DependencyPurl string `json:"dependencyPurl"` // will be nil, for direct dependencies
1616
AssetID uuid.UUID `json:"assetVersionId"`
17-
ScannerIDs string `json:"scanner"` // the id of the scanner
17+
ScannerIDs string `json:"scannerIds"` // the id of the scanner
1818
}
1919

2020
func toDTO(m models.ComponentDependency) componentDTO {

internal/core/dependency_vuln/dependency_vuln_controller.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,6 @@ func (c dependencyVulnHttpController) Read(ctx core.Context) error {
226226
}
227227

228228
func (c dependencyVulnHttpController) CreateEvent(ctx core.Context) error {
229-
230229
asset := core.GetAsset(ctx)
231230
assetVersion := core.GetAssetVersion(ctx)
232231
thirdPartyIntegration := core.GetThirdPartyIntegration(ctx)

internal/core/dependency_vuln/dependency_vuln_service.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,26 +153,46 @@ func (s *service) RecalculateAllRawRiskAssessments() error {
153153

154154
}
155155

156-
func (s *service) MakeAddedScannerEvent(tx core.DB, vulnerabilities []models.DependencyVuln, userID string) error {
156+
func (s *service) UserDetectedDependencyVulnWithAnotherScanner(tx core.DB, vulnerabilities []models.DependencyVuln, userID string, scannerID string) error {
157+
if len(vulnerabilities) == 0 {
158+
return nil
159+
}
160+
161+
// create a new VulnEvent for each fixed dependencyVuln
157162
events := make([]models.VulnEvent, len(vulnerabilities))
163+
158164
for i := range vulnerabilities {
159-
ev := models.NewAddedScannerEvent(vulnerabilities[i].CalculateHash(), userID)
165+
ev := models.NewAddedScannerEvent(vulnerabilities[i].CalculateHash(), userID, scannerID)
160166
ev.Apply(&vulnerabilities[i])
161167
events[i] = ev
162168
}
169+
170+
err := s.dependencyVulnRepository.SaveBatch(tx, vulnerabilities)
171+
if err != nil {
172+
return err
173+
}
174+
163175
return s.vulnEventRepository.SaveBatch(tx, events)
164176

165177
}
166178

167-
func (s *service) MakeRemoveScannerEvent(tx core.DB, vulnerabilities []models.DependencyVuln, userID string) error {
179+
func (s *service) UserDidNotDetectDependencyVulnWithScannerAnymore(tx core.DB, vulnerabilities []models.DependencyVuln, userID string, scannerID string) error {
180+
if len(vulnerabilities) == 0 {
181+
return nil
182+
}
183+
168184
events := make([]models.VulnEvent, len(vulnerabilities))
169185
for i := range vulnerabilities {
170-
ev := models.NewRemovedScannerEvent(vulnerabilities[i].CalculateHash(), userID)
186+
ev := models.NewRemovedScannerEvent(vulnerabilities[i].CalculateHash(), userID, scannerID)
171187
ev.Apply(&vulnerabilities[i])
172188
events[i] = ev
173189
}
190+
err := s.dependencyVulnRepository.SaveBatch(tx, vulnerabilities)
191+
if err != nil {
192+
return err
193+
}
194+
// save the events
174195
return s.vulnEventRepository.SaveBatch(tx, events)
175-
176196
}
177197

178198
func (s *service) RecalculateRawRiskAssessment(tx core.DB, userID string, dependencyVulns []models.DependencyVuln, justification string, asset models.Asset) error {
@@ -281,7 +301,6 @@ func (s *service) UpdateDependencyVulnState(tx core.DB, assetID uuid.UUID, userI
281301

282302
func (s *service) updateDependencyVulnState(tx core.DB, userID string, dependencyVuln *models.DependencyVuln, statusType string, justification string) (models.VulnEvent, error) {
283303
var ev models.VulnEvent
284-
fmt.Printf("Called 'ApplyAndSave' with vuln: %s", *dependencyVuln.CVEID)
285304
switch models.VulnEventType(statusType) {
286305
case models.EventTypeAccepted:
287306
ev = models.NewAcceptedEvent(dependencyVuln.CalculateHash(), userID, justification)
@@ -292,7 +311,6 @@ func (s *service) updateDependencyVulnState(tx core.DB, userID string, dependenc
292311
case models.EventTypeComment:
293312
ev = models.NewCommentEvent(dependencyVuln.CalculateHash(), userID, justification)
294313
}
295-
//Found by toher scanner
296314

297315
err := s.dependencyVulnRepository.ApplyAndSave(tx, dependencyVuln, &ev)
298316
return ev, err

internal/core/risk/risk_explaination.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ type Explanation struct {
222222
cveDescription string
223223

224224
affectedComponentName string
225-
scannerID string
225+
scannerIDs string
226226
fixedVersion *string
227227
}
228228

@@ -232,7 +232,7 @@ func (e Explanation) Markdown(baseUrl, orgSlug, projectSlug, assetSlug, assetVer
232232
str.WriteString(e.cveDescription)
233233
str.WriteString("\n")
234234
str.WriteString("### Affected component \n")
235-
str.WriteString(fmt.Sprintf("The vulnerability is in `%s`, detected by the `%s` scan.\n", e.affectedComponentName, e.scannerID))
235+
str.WriteString(fmt.Sprintf("The vulnerability is in `%s`, detected by the `%s` scan.\n", e.affectedComponentName, e.scannerIDs))
236236
str.WriteString("### Recommended fix\n")
237237
if e.fixedVersion != nil {
238238
str.WriteString(fmt.Sprintf("Upgrade to version %s or later.\n", *e.fixedVersion))
@@ -300,7 +300,7 @@ func Explain(dependencyVuln models.DependencyVuln, asset models.Asset, vector st
300300
cveDescription: dependencyVuln.CVE.Description,
301301

302302
affectedComponentName: utils.SafeDereference(dependencyVuln.ComponentPurl),
303-
scannerID: dependencyVuln.ScannerIDs,
303+
scannerIDs: dependencyVuln.ScannerIDs,
304304
fixedVersion: dependencyVuln.ComponentFixedVersion,
305305
}
306306
}

internal/database/models/dependency_vuln_model.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (m DependencyVuln) TableName() string {
6767
}
6868

6969
func (m *DependencyVuln) CalculateHash() string {
70-
hash := utils.HashString(fmt.Sprintf("%s/%s/%s/%s", *m.CVEID, *m.ComponentPurl, m.AssetVersionName, m.AssetID))
70+
hash := utils.HashString(fmt.Sprintf("%s/%s/%s/%s", utils.OrDefault(m.CVEID, ""), utils.OrDefault(m.ComponentPurl, ""), m.AssetVersionName, m.AssetID))
7171
return hash
7272
}
7373

0 commit comments

Comments
 (0)