Skip to content
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
34 changes: 32 additions & 2 deletions cmd/grype/cli/options/database_search_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ func (o *DBSearchPackages) PostLoad() error {
log.Warnf("ignoring version and qualifiers for package URL %q", purl)
}

o.PkgSpecs = append(o.PkgSpecs, &v6.PackageSpecifier{Name: purl.Name, Ecosystem: purl.Type})
o.CPESpecs = append(o.CPESpecs, &v6.PackageSpecifier{CPE: &cpe.Attributes{Part: "a", Product: purl.Name, TargetSW: purl.Type}})
name := packageNameFromPURL(&purl)
o.PkgSpecs = append(o.PkgSpecs, &v6.PackageSpecifier{Name: name, Ecosystem: purl.Type})
o.CPESpecs = append(o.CPESpecs, &v6.PackageSpecifier{CPE: &cpe.Attributes{Part: "a", Product: name, TargetSW: purl.Type}})

default:
o.PkgSpecs = append(o.PkgSpecs, &v6.PackageSpecifier{Name: p, Ecosystem: o.Ecosystem})
Expand All @@ -82,3 +83,32 @@ func (o *DBSearchPackages) PostLoad() error {

return nil
}

// packageNameFromPURL reconstructs the package name as it is stored in the DB
// for the PURL's ecosystem. Most ecosystems are flat-namespaced and use
// purl.Name directly, but some encode part of the name in the PURL namespace:
//
// - golang modules carry the module path across namespace + name, e.g.
// pkg:golang/github.com/gin-gonic/gin parses to Namespace="github.com/gin-gonic"
// and Name="gin", while the DB keys the record under the full module path
// "github.com/gin-gonic/gin".
// - npm scoped packages parse to Namespace="@scope" and Name="name", and are
// stored as "@scope/name".
// - Maven packages parse to Namespace="groupId" and Name="artifactId", and are
// stored as "groupId:artifactId".
//
// Without this, a search for a namespaced PURL only used purl.Name and silently
// failed to match. This mirrors the same reconstruction the openvex build
// transformer performs (grype/db/v6/build/transformers/openvex).
func packageNameFromPURL(purl *packageurl.PackageURL) string {
if purl.Namespace == "" {
return purl.Name
}
switch purl.Type {
case packageurl.TypeMaven:
return purl.Namespace + ":" + purl.Name
case packageurl.TypeGolang, packageurl.TypeNPM:
return purl.Namespace + "/" + purl.Name
}
return purl.Name
}
36 changes: 36 additions & 0 deletions cmd/grype/cli/options/database_search_packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,42 @@ func TestDBSearchPackagesPostLoad(t *testing.T) {
{CPE: &cpe.Attributes{Part: "a", Product: "package-name", TargetSW: "npm"}},
},
},
{
name: "golang PURL keeps the module path",
input: DBSearchPackages{
Packages: []string{"pkg:golang/github.com/gin-gonic/gin@v1.9.0"},
},
expectedPkg: v6.PackageSpecifiers{
{Name: "github.com/gin-gonic/gin", Ecosystem: "golang"},
},
expectedCPE: v6.PackageSpecifiers{
{CPE: &cpe.Attributes{Part: "a", Product: "github.com/gin-gonic/gin", TargetSW: "golang"}},
},
},
{
name: "npm scoped PURL keeps the scope",
input: DBSearchPackages{
Packages: []string{"pkg:npm/%40babel/core@7.0.0"},
},
expectedPkg: v6.PackageSpecifiers{
{Name: "@babel/core", Ecosystem: "npm"},
},
expectedCPE: v6.PackageSpecifiers{
{CPE: &cpe.Attributes{Part: "a", Product: "@babel/core", TargetSW: "npm"}},
},
},
{
name: "maven PURL joins group and artifact",
input: DBSearchPackages{
Packages: []string{"pkg:maven/org.apache.commons/commons-lang3@3.12.0"},
},
expectedPkg: v6.PackageSpecifiers{
{Name: "org.apache.commons:commons-lang3", Ecosystem: "maven"},
},
expectedCPE: v6.PackageSpecifiers{
{CPE: &cpe.Attributes{Part: "a", Product: "org.apache.commons:commons-lang3", TargetSW: "maven"}},
},
},
{
name: "plain package name",
input: DBSearchPackages{
Expand Down