From 509f1a1a5baad016d0f33a69cb433aa082888109 Mon Sep 17 00:00:00 2001 From: King Star Date: Thu, 1 Oct 2026 03:34:29 +0800 Subject: [PATCH] fix(java): filter Tomcat server CPE for embedded EL (#5322) Signed-off-by: King Star <54024410+jstar0@users.noreply.github.com> Co-authored-by: Will Murphy --- .../cataloger/internal/cpegenerate/filter.go | 16 +++++++++ .../internal/cpegenerate/generate_test.go | 33 +++++++++++++++++++ 2 files changed, 49 insertions(+) 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