From 47d4b665cf1bd71238b37b29b787777c783e7b02 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 24 Sep 2026 13:38:19 -0400 Subject: [PATCH] fix: only skip META-INF/maven poms inside an extracted java archive `isArchiveMetaPom` was an unconditional `strings.Contains(path, "META-INF/maven/")`, so the pom cataloger dropped real poms everywhere, even with the feature off: exploded wars (`WEB-INF/classes/META-INF/maven/...`), test fixtures under `src/test/resources`, source tarballs. That's a regression on the default path. the skip exists because inside a jar those poms are raw build copies that java already covers from `pom.properties`. So it now applies only when the traversal on the context is a java archive. Outside one, or inside a tarball java doesn't describe, the poms are cataloged as before this PR. Signed-off-by: Alex Goodman --- syft/pkg/cataloger/java/parse_pom_xml.go | 12 +++++-- syft/pkg/cataloger/java/parse_pom_xml_test.go | 31 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/syft/pkg/cataloger/java/parse_pom_xml.go b/syft/pkg/cataloger/java/parse_pom_xml.go index 8b4ef6d56..5b5e06070 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml.go +++ b/syft/pkg/cataloger/java/parse_pom_xml.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/anchore/syft/internal" + "github.com/anchore/syft/internal/archive" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/unknown" "github.com/anchore/syft/syft/artifact" @@ -40,7 +41,7 @@ func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver var poms []*maven.Project pomLocations := map[*maven.Project]file.Location{} for _, pomLocation := range locations { - if isArchiveMetaPom(pomLocation) { + if isArchiveMetaPom(ctx, pomLocation) { continue } pom, err := readPomFromLocation(fileResolver, pomLocation) @@ -307,7 +308,14 @@ func pomParent(ctx context.Context, r *maven.Resolver, pom *maven.Project) *pkg. // unresolved ${property} references and versions inherited from parent POMs absent from the archive. // The java-archive-cataloger identifies these archives from pom.properties, whose values are always // resolved, so skipping them here avoids phantom dependency packages. -func isArchiveMetaPom(location file.Location) bool { +// +// This only holds inside an extracted java archive. Anywhere else (an exploded war, test fixtures under +// src/test/resources, a tarball of a source tree) nothing else reads these poms, so they are cataloged. +func isArchiveMetaPom(ctx context.Context, location file.Location) bool { + trav := archive.TraversalFromContext(ctx) + if trav == nil || !isJavaArchiveName(trav.Location.Path()) { + return false + } return strings.Contains(location.Path(), "META-INF/maven/") } diff --git a/syft/pkg/cataloger/java/parse_pom_xml_test.go b/syft/pkg/cataloger/java/parse_pom_xml_test.go index dbb3669c6..6e40b760b 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml_test.go +++ b/syft/pkg/cataloger/java/parse_pom_xml_test.go @@ -1,12 +1,14 @@ package java import ( + "context" "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/syft/internal/archive" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cataloging" "github.com/anchore/syft/syft/file" @@ -726,9 +728,21 @@ func Test_isArchiveMetaPom(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { loc := file.NewLocation(test.path) - assert.Equal(t, test.expected, isArchiveMetaPom(loc)) + assert.Equal(t, test.expected, isArchiveMetaPom(insideJar(t), loc)) }) } + + t.Run("only inside an extracted java archive", func(t *testing.T) { + loc := file.NewLocation("WEB-INF/classes/META-INF/maven/com.example/my-lib/pom.xml") + assert.False(t, isArchiveMetaPom(context.Background(), loc), "outside any archive, e.g. an exploded war") + inTarball := archive.WithTraversal(context.Background(), &archive.Traversal{Location: file.NewLocation("/src.tar.gz")}) + assert.False(t, isArchiveMetaPom(inTarball, loc), "inside an archive java does not describe") + }) +} + +// insideJar is a context as the archive cataloger task sets it up while cataloging a jar's contents. +func insideJar(t *testing.T) context.Context { + return archive.WithTraversal(pkgtest.Context(t), &archive.Traversal{Location: file.NewLocation("/app.jar")}) } func Test_pomCatalogerSkipsMetaInfPoms(t *testing.T) { @@ -743,8 +757,17 @@ func Test_pomCatalogerSkipsMetaInfPoms(t *testing.T) { pkgtest.NewCatalogTester(). FromDirectory(t, "testdata/pom/meta-inf-archive"). + WithContext(insideJar(t)). Expects(nil, nil). TestCataloger(t, cat) + + // the same pom outside a jar is a project file like any other + pkgtest.NewCatalogTester(). + FromDirectory(t, "testdata/pom/meta-inf-archive"). + ExpectsAssertion(func(t *testing.T, pkgs []pkg.Package, _ []artifact.Relationship) { + assert.NotEmpty(t, pkgs) + }). + TestCataloger(t, cat) } func Test_pomCatalogerSkipsMetaInfButKeepsProjectPom(t *testing.T) { @@ -803,7 +826,11 @@ func Test_pomCatalogerSkipsMetaInfButKeepsProjectPom(t *testing.T) { }, }) - pkgtest.TestCataloger(t, "testdata/pom/mixed-meta-inf-and-project", cat, expectedPkgs, expectedRelationships) + pkgtest.NewCatalogTester(). + FromDirectory(t, "testdata/pom/mixed-meta-inf-and-project"). + WithContext(insideJar(t)). + Expects(expectedPkgs, expectedRelationships). + TestCataloger(t, cat) } func expectedTransientPackageData() expected {