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 <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2026-09-24 13:38:19 -04:00
parent 22edfe66ca
commit 47d4b665cf
2 changed files with 39 additions and 4 deletions
+10 -2
View File
@@ -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/")
}
+29 -2
View File
@@ -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 {