diff --git a/syft/pkg/cataloger/internal/cpegenerate/filter.go b/syft/pkg/cataloger/internal/cpegenerate/filter.go index 8423c320b..2fffe67a8 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/filter.go +++ b/syft/pkg/cataloger/internal/cpegenerate/filter.go @@ -14,6 +14,7 @@ type filterFn func(cpe cpe.Attributes, p pkg.Package) bool var cpeFilters = []filterFn{ disallowJiraClientServerMismatch, + disallowTomcatServerCPEForEmbeddedEL, disallowJenkinsServerCPEForPluginPackage, disallowJenkinsCPEsNotAssociatedWithJenkins, disallowNonParseableCPEs, @@ -71,3 +72,18 @@ func disallowJiraClientServerMismatch(c cpe.Attributes, p pkg.Package) bool { } return false } + +// disallowTomcatServerCPEForEmbeddedEL prevents the server-level Tomcat CPE from being +// assigned to the EL-only embedded artifact. Keep its artifact-specific CPE candidates. +func disallowTomcatServerCPEForEmbeddedEL(c cpe.Attributes, p pkg.Package) bool { + if c.Vendor != "apache" || c.Product != "tomcat" { + return false + } + + metadata, ok := p.Metadata.(pkg.JavaArchive) + if !ok || metadata.PomProperties == nil { + return false + } + + return metadata.PomProperties.GroupID == "org.apache.tomcat.embed" && metadata.PomProperties.ArtifactID == "tomcat-embed-el" +} diff --git a/syft/pkg/cataloger/internal/cpegenerate/generate_test.go b/syft/pkg/cataloger/internal/cpegenerate/generate_test.go index 9a463b6ad..ae03d205b 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/generate_test.go +++ b/syft/pkg/cataloger/internal/cpegenerate/generate_test.go @@ -963,6 +963,39 @@ func TestGeneratePackageCPEs(t *testing.T) { } } +func TestTomcatEmbeddedCPEs(t *testing.T) { + packageFor := func(artifactID string) pkg.Package { + return pkg.Package{ + Name: artifactID, + Version: "11.0.20", + Language: pkg.Java, + Type: pkg.JavaPkg, + Metadata: pkg.JavaArchive{ + PomProperties: &pkg.JavaPomProperties{ + GroupID: "org.apache.tomcat.embed", + ArtifactID: artifactID, + }, + }, + } + } + + hasCPE := func(cpes []cpe.CPE, vendor, product string) bool { + for _, generated := range cpes { + if generated.Attributes.Vendor == vendor && generated.Attributes.Product == product { + return true + } + } + return false + } + + elCPEs := FromPackageAttributes(packageFor("tomcat-embed-el")) + assert.False(t, hasCPE(elCPEs, "apache", "tomcat"), "EL-only module should not inherit the server CPE") + assert.True(t, hasCPE(elCPEs, "apache", "tomcat-embed-el"), "keep the artifact-specific CPE candidate") + + coreCPEs := FromPackageAttributes(packageFor("tomcat-embed-core")) + assert.True(t, hasCPE(coreCPEs, "apache", "tomcat"), "core module should retain the Tomcat server CPE") +} + func TestCandidateProducts(t *testing.T) { tests := []struct { name string