Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vex): CSAF support #1826

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions cmd/grype/cli/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/anchore/grype/grype/presenter/models"
"github.com/anchore/grype/grype/store"
"github.com/anchore/grype/grype/vex"
vexStatus "github.com/anchore/grype/grype/vex/status"
"github.com/anchore/grype/internal"
"github.com/anchore/grype/internal/bus"
"github.com/anchore/grype/internal/format"
Expand Down Expand Up @@ -95,8 +96,8 @@ var ignoreFixedMatches = []match.IgnoreRule{
}

var ignoreVEXFixedNotAffected = []match.IgnoreRule{
{VexStatus: string(vex.StatusNotAffected)},
{VexStatus: string(vex.StatusFixed)},
{VexStatus: string(vexStatus.NotAffected)},
{VexStatus: string(vexStatus.Fixed)},
}

var ignoreLinuxKernelHeaders = []match.IgnoreRule{
Expand Down Expand Up @@ -180,17 +181,21 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) (errs
}

applyDistroHint(packages, &pkgContext, opts)
vexProcessor, err := vex.NewProcessor(vex.ProcessorOptions{
Documents: opts.VexDocuments,
IgnoreRules: opts.Ignore,
})
if err != nil {
return fmt.Errorf("failed to create VEX processor: %w", err)
}

vulnMatcher := grype.VulnerabilityMatcher{
Store: *str,
IgnoreRules: opts.Ignore,
NormalizeByCVE: opts.ByCVE,
FailSeverity: opts.FailOnSeverity(),
Matchers: getMatchers(opts),
VexProcessor: vex.NewProcessor(vex.ProcessorOptions{
Documents: opts.VexDocuments,
IgnoreRules: opts.Ignore,
}),
VexProcessor: vexProcessor,
}

remainingMatches, ignoredMatches, err := vulnMatcher.FindMatches(packages, pkgContext)
Expand Down Expand Up @@ -356,18 +361,18 @@ func applyVexRules(opts *options.Grype) error {
opts.Ignore = append(opts.Ignore, ignoreVEXFixedNotAffected...)
}

for _, vexStatus := range opts.VexAdd {
switch vexStatus {
case string(vex.StatusAffected):
for _, status := range opts.VexAdd {
switch status {
case string(vexStatus.Affected):
opts.Ignore = append(
opts.Ignore, match.IgnoreRule{VexStatus: string(vex.StatusAffected)},
opts.Ignore, match.IgnoreRule{VexStatus: string(vexStatus.Affected)},
)
case string(vex.StatusUnderInvestigation):
case string(vexStatus.UnderInvestigation):
opts.Ignore = append(
opts.Ignore, match.IgnoreRule{VexStatus: string(vex.StatusUnderInvestigation)},
opts.Ignore, match.IgnoreRule{VexStatus: string(vexStatus.UnderInvestigation)},
)
default:
return fmt.Errorf("invalid VEX status in vex-add setting: %s", vexStatus)
return fmt.Errorf("invalid VEX status in vex-add setting: %s", status)
}
}

Expand Down
14 changes: 11 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ require (
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b
github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
gorm.io/gorm v1.25.10
)

require github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537
require (
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537
github.com/aws/smithy-go v1.6.0
github.com/csaf-poc/csaf_distribution/v3 v3.0.0
)

require (
cloud.google.com/go v0.110.10 // indirect
Expand All @@ -72,6 +76,8 @@ require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/Intevation/gval v1.3.0 // indirect
github.com/Intevation/jsonpath v0.2.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
Expand Down Expand Up @@ -203,8 +209,9 @@ require (
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/sassoftware/go-rpmutils v0.3.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spdx/tools-golang v0.5.4 // indirect
Expand All @@ -228,6 +235,7 @@ require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/zclconf/go-cty v1.14.0 // indirect
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1 // indirect
go.etcd.io/bbolt v1.3.8 // indirect
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect
Expand Down
19 changes: 16 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7B
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Intevation/gval v1.3.0 h1:+Ze5sft5MmGbZrHj06NVUbcxCb67l9RaPTLMNr37mjw=
github.com/Intevation/gval v1.3.0/go.mod h1:xmGyGpP5be12EL0P12h+dqiYG8qn2j3PJxIgkoOHO5o=
github.com/Intevation/jsonpath v0.2.1 h1:rINNQJ0Pts5XTFEG+zamtdL7l9uuE1z0FBA+r55Sw+A=
github.com/Intevation/jsonpath v0.2.1/go.mod h1:WnZ8weMmwAx/fAO3SutjYFU+v7DFreNYnibV7CiaYIw=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
Expand Down Expand Up @@ -282,6 +286,8 @@ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn
github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.288 h1:Ln7fIao/nl0ACtelgR1I4AiEw/GLNkKcXfCaHupUW5Q=
github.com/aws/aws-sdk-go v1.44.288/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/smithy-go v1.6.0 h1:T6puApfBcYiTIsaI+SYWqanjMt5pc3aoyyDrI+0YH54=
github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA=
Expand Down Expand Up @@ -359,6 +365,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/csaf-poc/csaf_distribution/v3 v3.0.0 h1:ob9+Fmpff0YWgTP3dYaw7G2hKQ9cegh9l3zksc+q3sM=
github.com/csaf-poc/csaf_distribution/v3 v3.0.0/go.mod h1:uilCTiNKivq+6zrDvjtZaUeLk70oe21iwKivo6ILwlQ=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE=
Expand Down Expand Up @@ -885,6 +893,8 @@ github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/sassoftware/go-rpmutils v0.3.0 h1:tE4TZ8KcOXay5iIP64P291s6Qxd9MQCYhI7DU+f3gFA=
github.com/sassoftware/go-rpmutils v0.3.0/go.mod h1:hM9wdxFsjUFR/tJ6SMsLrJuChcucCa0DsCzE9RMfwMo=
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ=
Expand All @@ -896,8 +906,9 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
Expand Down Expand Up @@ -1014,6 +1025,8 @@ github.com/zclconf/go-cty v1.14.0 h1:/Xrd39K7DXbHzlisFP9c4pHao4yyf+/Ug9LEz+Y/yhc
github.com/zclconf/go-cty v1.14.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1 h1:V+UsotZpAVvfj3X/LMoEytoLzSiP6Lg0F7wdVyu9gGg=
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1/go.mod h1:ly2RBz4mnz1yeuVbQA/VFwGjK3mnHGRj1JuoG336Bis=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
Expand Down Expand Up @@ -1081,8 +1094,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down
2 changes: 2 additions & 0 deletions grype/match/matcher_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
PortageMatcher MatcherType = "portage-matcher"
GoModuleMatcher MatcherType = "go-module-matcher"
OpenVexMatcher MatcherType = "openvex-matcher"
CsafVexMatcher MatcherType = "csafvex-matcher"
RustMatcher MatcherType = "rust-matcher"
)

Expand All @@ -31,6 +32,7 @@ var AllMatcherTypes = []MatcherType{
PortageMatcher,
GoModuleMatcher,
OpenVexMatcher,
CsafVexMatcher,
RustMatcher,
}

Expand Down
4 changes: 2 additions & 2 deletions grype/presenter/internal/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/presenter/models"
"github.com/anchore/grype/grype/vex"
vexStatus "github.com/anchore/grype/grype/vex/status"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/syft/cpe"
Expand Down Expand Up @@ -213,7 +213,7 @@ func generateIgnoredMatches(t *testing.T, p pkg.Package) []match.IgnoredMatch {
Vulnerability: "CVE-1999-0004",
Namespace: "vex",
Package: match.IgnoreRulePackage{},
VexStatus: string(vex.StatusNotAffected),
VexStatus: string(vexStatus.NotAffected),
VexJustification: "this isn't the vulnerability match you're looking for... *waves hand*",
},
},
Expand Down
128 changes: 128 additions & 0 deletions grype/vex/csaf/csaf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package csaf

import (
"slices"

"github.com/csaf-poc/csaf_distribution/v3/csaf"
)

// advisoryMatch captures the criteria that caused a vulnerability to match a CSAF advisory
type advisoryMatch struct {
Vulnerability *csaf.Vulnerability
Status status
ProductID csaf.ProductID
}

// cve returns the CVE of the vulnerability that matched
func (m *advisoryMatch) cve() string {
if m == nil || m.Vulnerability == nil || m.Vulnerability.CVE == nil {
return ""
}

return string(*m.Vulnerability.CVE)
}

// statement returns the statement of the vulnerability that matched
func (m *advisoryMatch) statement() string {
if m == nil || m.Vulnerability == nil {
return ""
}

// an impact statement SHALL exist as machine readable flag in /vulnerabilities[]/flags (...)
for _, flag := range m.Vulnerability.Flags {
if flag == nil || flag.ProductIds == nil || flag.Label == nil {
continue
}
for _, pID := range *flag.ProductIds {
if pID == nil {
continue
}
if *pID == m.ProductID {
return string(*flag.Label)
}
}
}
// (...) or as human readable justification in /vulnerabilities[]/threats
for _, th := range m.Vulnerability.Threats {
if th == nil || th.Category == nil || th.Details == nil {
continue
}
if *th.Category != csaf.CSAFThreatCategoryImpact {
continue
}
for _, pID := range *th.ProductIds {
if pID == nil {
continue
}
if *pID == m.ProductID {
return string(*th.Details)

Check failure on line 58 in grype/vex/csaf/csaf.go

View workflow job for this annotation

GitHub Actions / Static analysis

unnecessary conversion (unconvert)
}
}
}

return ""
}

type advisories []*csaf.Advisory

// Matches returns the first CSAF advisory to match for a given vulnerability ID and package URL
func (advisories advisories) matches(vulnID, purl string) *advisoryMatch {

Check failure on line 69 in grype/vex/csaf/csaf.go

View workflow job for this annotation

GitHub Actions / Static analysis

cognitive complexity 32 of func `(advisories).matches` is high (> 30) (gocognit)

Check failure on line 69 in grype/vex/csaf/csaf.go

View workflow job for this annotation

GitHub Actions / Static analysis

unnecessary leading newline (whitespace)

for _, adv := range advisories {
if adv == nil || adv.Vulnerabilities == nil {
continue
}

// Auxiliary function to find in the advisory the 1st product ID that matches a given pURL
findProductID := func(products csaf.Products, purl string) csaf.ProductID {
for _, p := range products {
if p == nil {
continue
}
if slices.Contains(purlsFromProductIdentificationHelpers(adv.ProductTree.CollectProductIdentificationHelpers(*p)), purl) {
return *p
}
}
return ""
}

for _, vuln := range adv.Vulnerabilities {
if vuln == nil || vuln.CVE == nil || string(*vuln.CVE) != vulnID {
continue
}

productsByStatus := map[status]*csaf.Products{
firstAffected: vuln.ProductStatus.FirstAffected,
firstFixed: vuln.ProductStatus.FirstFixed,
fixed: vuln.ProductStatus.Fixed,
knownAffected: vuln.ProductStatus.KnownAffected,
knownNotAffected: vuln.ProductStatus.KnownNotAffected,
lastAffected: vuln.ProductStatus.LastAffected,
recommended: vuln.ProductStatus.Recommended,
underInvestigation: vuln.ProductStatus.UnderInvestigation,
}
for status, products := range productsByStatus {
if products == nil {
continue
}
if productID := findProductID(*products, purl); productID != "" {
return &advisoryMatch{vuln, status, productID}
}
}
}
}

return nil
}

// purlsFromProductIdentificationHelpers returns a slice of PackageURLs (string format) given a slice of ProductIdentificationHelpers.
func purlsFromProductIdentificationHelpers(helpers []*csaf.ProductIdentificationHelper) []string {
var purls []string
for _, helper := range helpers {
if helper == nil || helper.PURL == nil {
continue
}
purls = append(purls, string(*helper.PURL))
}
return purls
}
Loading
Loading