fix(java): resolve Groovy 4+ group IDs to org.apache.groovy (#5313)

* fix(java): resolve Groovy 4+ group IDs to org.apache.groovy

Groovy 4.0 moved from the Codehaus coordinates (org.codehaus.groovy) to the
Apache Software Foundation (org.apache.groovy). JARs that ship no pom
metadata fall through to the static artifact-to-group map, which carries the
3.x-era org.codehaus.groovy entries for the whole groovy family, so 4.x
artifacts were attributed to an obsolete group even though their manifest
identifies the Apache vendor.

Where the static map attributes an artifact to the historical group, a
major-version migration resolves groovy 4.x and newer to org.apache.groovy,
while 3.x and earlier keep their historical group. The Eclipse-hosted
groovy-eclipse-* artifacts, which share the prefix but kept their Codehaus
coordinates, are excluded.

Fixes #5311

Signed-off-by: texasich <101962694+texasich@users.noreply.github.com>

* refactor(java): share version-aware groovy group ID lookup with CPE generation

- move the artifact-to-group lookup behind `cpegenerate.ArtifactIDToGroupID(artifactID, version)` so the PURL and CPE paths agree; the exported mutable map is now unexported
- version-dependent entries are expressed as a `groupIDMigration{before, after, atMajor}` (groovy moved to `org.apache.groovy` at 4.0); unconditional entries, including `groovy-eclipse-*` and apache-only 4.0+ modules, stay in the plain map
- add apache-only modules (`groovy-ginq`, `groovy-contracts`, `groovy-callsite`, ...) that otherwise took the group from their per-module `Automatic-Module-Name`

This changes CPEs for pom-less groovy 4+ jars (all real ones): vendors are now `apache` (e.g. `cpe:2.3:a:apache:groovy:4.0.33`) instead of `codehaus`, matching the PURL, jars with pom metadata, and NVD.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: texasich <101962694+texasich@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
texasich
2026-09-23 17:20:28 +00:00
committed by GitHub
co-authored by Alex Goodman
parent 41870fec7f
commit 409ac78f62
8 changed files with 311 additions and 122 deletions
@@ -182,6 +182,24 @@ func TestGeneratePackageCPEs(t *testing.T) {
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
},
},
{
// groovy moved to org.apache.groovy at 4.0 and real jars ship no pom metadata, so the known package
// list drives the vendor. See https://github.com/anchore/syft/issues/5311
name: "groovy 4 module without pom metadata",
p: pkg.Package{
Name: "groovy-json",
Version: "4.0.33",
FoundBy: "some-analyzer",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{Manifest: &pkg.JavaManifest{}},
},
expected: []string{
"cpe:2.3:a:apache:groovy-json:4.0.33:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:groovy_json:4.0.33:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:groovy:4.0.33:*:*:*:*:*:*:*",
},
},
{
name: "java language with groupID",
p: pkg.Package{
@@ -190,16 +190,16 @@ func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
return nil
}
return GroupIDsFromJavaMetadata(p.Name, metadata)
return GroupIDsFromJavaMetadata(p.Name, p.Version, metadata)
}
// GroupIDsFromJavaMetadata returns the possible group IDs for a Java package
// This function is similar to GroupIDFromJavaPackage, but returns all possible group IDs and is less strict
// It is used as a way to generate possible candidates for CPE matching.
func GroupIDsFromJavaMetadata(pkgName string, metadata pkg.JavaArchive) (groupIDs []string) {
func GroupIDsFromJavaMetadata(pkgName, version string, metadata pkg.JavaArchive) (groupIDs []string) {
groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...)
groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...)
groupIDs = append(groupIDs, groupIDsFromJavaManifest(pkgName, metadata.Manifest)...)
groupIDs = append(groupIDs, groupIDsFromJavaManifest(pkgName, version, metadata.Manifest)...)
return groupIDs
}
@@ -253,8 +253,8 @@ func addGroupIDsFromGroupIDsAndArtifactID(groupID, artifactID string) (groupIDs
return groupIDs
}
func groupIDsFromJavaManifest(pkgName string, manifest *pkg.JavaManifest) []string {
if groupID, ok := DefaultArtifactIDToGroupID[pkgName]; ok {
func groupIDsFromJavaManifest(pkgName, version string, manifest *pkg.JavaManifest) []string {
if groupID, ok := ArtifactIDToGroupID(pkgName, version); ok {
return []string{groupID}
}
@@ -303,7 +303,7 @@ func GetManifestFieldGroupIDs(manifest *pkg.JavaManifest, fields []string) (grou
func cleanGroupID(groupID string) string {
groupID = strings.TrimSpace(strings.Split(removeOSCIDirectives(groupID), "#")[0])
if replacement, ok := GroupIDCorrections[groupID]; ok {
if replacement, ok := groupIDCorrections[groupID]; ok {
return replacement
}
return groupID
@@ -1,10 +1,17 @@
package cpegenerate
var GroupIDCorrections = map[string]string{
import (
"strconv"
"strings"
)
var groupIDCorrections = map[string]string{
"org.lz4.java": "org.lz4",
}
var DefaultArtifactIDToGroupID = map[string]string{
// defaultArtifactIDToGroupID holds artifacts that always map to the same group ID. Look up group IDs with
// ArtifactIDToGroupID instead of reading this map directly, since migratedArtifactIDToGroupID takes precedence.
var defaultArtifactIDToGroupID = map[string]string{
"ant": "org.apache.ant",
"ant-antlr": "org.apache.ant",
"ant-antunit": "org.apache.ant",
@@ -1832,104 +1839,83 @@ var DefaultArtifactIDToGroupID = map[string]string{
"commons-jelly-tags-velocity": "commons-jelly",
"commons-jelly-tags-xml": "commons-jelly",
"commons-jelly-tags-xmlunit": "commons-jelly",
"groovy": "org.codehaus.groovy",
"groovy-all": "org.codehaus.groovy",
"groovy-all-jdk14": "org.codehaus.groovy",
"groovy-all-minimal": "org.codehaus.groovy",
"groovy-all-tests": "org.codehaus.groovy",
"groovy-ant": "org.codehaus.groovy",
"groovy-astbuilder": "org.codehaus.groovy",
"groovy-backports-compat23": "org.codehaus.groovy",
"groovy-binary": "org.codehaus.groovy",
"groovy-bom": "org.codehaus.groovy",
"groovy-bsf": "org.codehaus.groovy",
"groovy-cli-commons": "org.codehaus.groovy",
"groovy-cli-picocli": "org.codehaus.groovy",
"groovy-console": "org.codehaus.groovy",
"groovy-datetime": "org.codehaus.groovy",
"groovy-dateutil": "org.codehaus.groovy",
"groovy-docgenerator": "org.codehaus.groovy",
"groovy-eclipse-batch": "org.codehaus.groovy",
"groovy-eclipse-compiler": "org.codehaus.groovy",
"groovy-groovydoc": "org.codehaus.groovy",
"groovy-groovysh": "org.codehaus.groovy",
"groovy-jaxb": "org.codehaus.groovy",
"groovy-jdk14": "org.codehaus.groovy",
"groovy-jmx": "org.codehaus.groovy",
"groovy-json": "org.codehaus.groovy",
"groovy-json-direct": "org.codehaus.groovy",
"groovy-jsr223": "org.codehaus.groovy",
"groovy-macro": "org.codehaus.groovy",
"groovy-nio": "org.codehaus.groovy",
"groovy-servlet": "org.codehaus.groovy",
"groovy-sql": "org.codehaus.groovy",
"groovy-swing": "org.codehaus.groovy",
"groovy-templates": "org.codehaus.groovy",
"groovy-test": "org.codehaus.groovy",
"groovy-test-junit5": "org.codehaus.groovy",
"groovy-testng": "org.codehaus.groovy",
"groovy-tests-vm8": "org.codehaus.groovy",
"groovy-xml": "org.codehaus.groovy",
"groovy-xmlrpc": "org.codehaus.groovy",
"groovy-yaml": "org.codehaus.groovy",
"kafka-clients": "org.apache.kafka",
"kafka-examples": "org.apache.kafka",
"kafka-group-coordinator": "org.apache.kafka",
"kafka-hadoop-consumer": "org.apache.kafka",
"kafka-hadoop-producer": "org.apache.kafka",
"kafka-java-examples": "org.apache.kafka",
"kafka-log4j-appender": "org.apache.kafka",
"kafka-metadata": "org.apache.kafka",
"kafka-perf_2.10": "org.apache.kafka",
"kafka-perf_2.8.0": "org.apache.kafka",
"kafka-perf_2.8.2": "org.apache.kafka",
"kafka-perf_2.9.1": "org.apache.kafka",
"kafka-perf_2.9.2": "org.apache.kafka",
"kafka-raft": "org.apache.kafka",
"kafka-server-common": "org.apache.kafka",
"kafka-shell": "org.apache.kafka",
"kafka-storage": "org.apache.kafka",
"kafka-storage-api": "org.apache.kafka",
"kafka-streams": "org.apache.kafka",
"kafka-streams-examples": "org.apache.kafka",
"kafka-streams-scala_2.11": "org.apache.kafka",
"kafka-streams-scala_2.12": "org.apache.kafka",
"kafka-streams-scala_2.13": "org.apache.kafka",
"kafka-streams-test-utils": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-0100": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-0101": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-0102": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-0110": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-10": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-11": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-20": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-21": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-22": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-23": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-24": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-25": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-26": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-27": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-28": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-30": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-31": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-32": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-33": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-34": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-35": "org.apache.kafka",
"kafka-tools": "org.apache.kafka",
"kafka-tools-api": "org.apache.kafka",
"kafka_2.10": "org.apache.kafka",
"kafka_2.11": "org.apache.kafka",
"kafka_2.12": "org.apache.kafka",
"kafka_2.13": "org.apache.kafka",
"kafka_2.8.0": "org.apache.kafka",
"kafka_2.8.2": "org.apache.kafka",
"kafka_2.9.1": "org.apache.kafka",
"kafka_2.9.2": "org.apache.kafka",
// the Eclipse-hosted groovy-eclipse-* artifacts stayed on codehaus past 4.0, and the org.apache.groovy modules
// below were added in 4.0+ and only exist under apache. See https://github.com/anchore/syft/issues/5311
"groovy-eclipse-batch": "org.codehaus.groovy",
"groovy-eclipse-compiler": "org.codehaus.groovy",
"groovy-callsite": "org.apache.groovy",
"groovy-concurrent-java": "org.apache.groovy",
"groovy-contracts": "org.apache.groovy",
"groovy-csv": "org.apache.groovy",
"groovy-ginq": "org.apache.groovy",
"groovy-grape-ivy": "org.apache.groovy",
"groovy-grape-maven": "org.apache.groovy",
"groovy-http-builder": "org.apache.groovy",
"groovy-macro-library": "org.apache.groovy",
"groovy-markdown": "org.apache.groovy",
"groovy-reactor": "org.apache.groovy",
"groovy-rxjava": "org.apache.groovy",
"groovy-test-junit6": "org.apache.groovy",
"groovy-toml": "org.apache.groovy",
"groovy-typecheckers": "org.apache.groovy",
"kafka-clients": "org.apache.kafka",
"kafka-examples": "org.apache.kafka",
"kafka-group-coordinator": "org.apache.kafka",
"kafka-hadoop-consumer": "org.apache.kafka",
"kafka-hadoop-producer": "org.apache.kafka",
"kafka-java-examples": "org.apache.kafka",
"kafka-log4j-appender": "org.apache.kafka",
"kafka-metadata": "org.apache.kafka",
"kafka-perf_2.10": "org.apache.kafka",
"kafka-perf_2.8.0": "org.apache.kafka",
"kafka-perf_2.8.2": "org.apache.kafka",
"kafka-perf_2.9.1": "org.apache.kafka",
"kafka-perf_2.9.2": "org.apache.kafka",
"kafka-raft": "org.apache.kafka",
"kafka-server-common": "org.apache.kafka",
"kafka-shell": "org.apache.kafka",
"kafka-storage": "org.apache.kafka",
"kafka-storage-api": "org.apache.kafka",
"kafka-streams": "org.apache.kafka",
"kafka-streams-examples": "org.apache.kafka",
"kafka-streams-scala_2.11": "org.apache.kafka",
"kafka-streams-scala_2.12": "org.apache.kafka",
"kafka-streams-scala_2.13": "org.apache.kafka",
"kafka-streams-test-utils": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-0100": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-0101": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-0102": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-0110": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-10": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-11": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-20": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-21": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-22": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-23": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-24": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-25": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-26": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-27": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-28": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-30": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-31": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-32": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-33": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-34": "org.apache.kafka",
"kafka-streams-upgrade-system-tests-35": "org.apache.kafka",
"kafka-tools": "org.apache.kafka",
"kafka-tools-api": "org.apache.kafka",
"kafka_2.10": "org.apache.kafka",
"kafka_2.11": "org.apache.kafka",
"kafka_2.12": "org.apache.kafka",
"kafka_2.13": "org.apache.kafka",
"kafka_2.8.0": "org.apache.kafka",
"kafka_2.8.2": "org.apache.kafka",
"kafka_2.9.1": "org.apache.kafka",
"kafka_2.9.2": "org.apache.kafka",
// legacy "Jackson 1.x" (aka "jackson-asl") artifacts predate the convention of embedding
// META-INF/maven/.../pom.properties in the jar, so groupIDFromKnownPackageList is the only
// META-INF/maven/.../pom.properties in the jar, so ArtifactIDToGroupID is the only
// way to recover the correct group ID for jars built without that metadata (e.g. Ant-built
// jars from before ~2014). Without this, the group ID falls back to the artifact name itself,
// producing purls that do not match the vulnerability database's namespace.
@@ -1940,3 +1926,83 @@ var DefaultArtifactIDToGroupID = map[string]string{
"jackson-xc": "org.codehaus.jackson",
"jackson-smile": "org.codehaus.jackson",
}
// groupIDMigration describes an artifact that moved from one group ID to another at a major version.
type groupIDMigration struct {
before, after string
atMajor int
}
// groovy moved from org.codehaus.groovy to org.apache.groovy at 4.0
var groovyMigration = groupIDMigration{before: "org.codehaus.groovy", after: "org.apache.groovy", atMajor: 4}
// migratedArtifactIDToGroupID holds artifacts whose group ID depends on the version. It takes precedence over
// defaultArtifactIDToGroupID; use ArtifactIDToGroupID rather than reading it directly.
var migratedArtifactIDToGroupID = map[string]groupIDMigration{
// groovy artifacts published under both coordinates. Without these, jars that ship no pom metadata get a
// stale codehaus group or one guessed from the per-module Automatic-Module-Name (e.g. org.apache.groovy.json).
// See https://github.com/anchore/syft/issues/5311
"groovy": groovyMigration,
"groovy-all": groovyMigration,
"groovy-all-jdk14": groovyMigration,
"groovy-all-minimal": groovyMigration,
"groovy-all-tests": groovyMigration,
"groovy-ant": groovyMigration,
"groovy-astbuilder": groovyMigration,
"groovy-backports-compat23": groovyMigration,
"groovy-binary": groovyMigration,
"groovy-bom": groovyMigration,
"groovy-bsf": groovyMigration,
"groovy-cli-commons": groovyMigration,
"groovy-cli-picocli": groovyMigration,
"groovy-console": groovyMigration,
"groovy-datetime": groovyMigration,
"groovy-dateutil": groovyMigration,
"groovy-docgenerator": groovyMigration,
"groovy-groovydoc": groovyMigration,
"groovy-groovysh": groovyMigration,
"groovy-jaxb": groovyMigration,
"groovy-jdk14": groovyMigration,
"groovy-jmx": groovyMigration,
"groovy-json": groovyMigration,
"groovy-json-direct": groovyMigration,
"groovy-jsr223": groovyMigration,
"groovy-macro": groovyMigration,
"groovy-nio": groovyMigration,
"groovy-servlet": groovyMigration,
"groovy-sql": groovyMigration,
"groovy-swing": groovyMigration,
"groovy-templates": groovyMigration,
"groovy-test": groovyMigration,
"groovy-test-junit5": groovyMigration,
"groovy-testng": groovyMigration,
"groovy-tests-vm8": groovyMigration,
"groovy-xml": groovyMigration,
"groovy-xmlrpc": groovyMigration,
"groovy-yaml": groovyMigration,
}
// ArtifactIDToGroupID returns the known group ID for the given artifact ID at the given version. For migrated
// artifacts an empty or unparseable version resolves to the pre-migration group ID.
func ArtifactIDToGroupID(artifactID, version string) (string, bool) {
if m, ok := migratedArtifactIDToGroupID[artifactID]; ok {
if majorVersion(version) >= m.atMajor {
return m.after, true
}
return m.before, true
}
groupID, ok := defaultArtifactIDToGroupID[artifactID]
return groupID, ok
}
// majorVersion returns the leading numeric component of a version (e.g. "4.0.33" and "4-SNAPSHOT"
// resolve to 4), or 0 when the version does not start with a number.
func majorVersion(version string) int {
leading, _, _ := strings.Cut(version, ".")
leading, _, _ = strings.Cut(leading, "-")
n, err := strconv.Atoi(leading)
if err != nil {
return 0
}
return n
}
@@ -0,0 +1,49 @@
package cpegenerate
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestArtifactIDToGroupID(t *testing.T) {
tests := []struct {
name string
artifactID string
version string
want string
wantOK bool
}{
{name: "unconditional entry", artifactID: "ant-antlr", version: "1.10.0", want: "org.apache.ant", wantOK: true},
{name: "unknown artifact", artifactID: "not-a-real-artifact", version: "1.0.0"},
{name: "groovy 4 migrates to apache", artifactID: "groovy", version: "4.0.33", want: "org.apache.groovy", wantOK: true},
{name: "groovy 3 stays on codehaus", artifactID: "groovy", version: "3.0.22", want: "org.codehaus.groovy", wantOK: true},
{name: "groovy without a version falls back to codehaus", artifactID: "groovy-json", want: "org.codehaus.groovy", wantOK: true},
{name: "groovy-eclipse stays on codehaus at 4.x", artifactID: "groovy-eclipse-batch", version: "4.0.33-02", want: "org.codehaus.groovy", wantOK: true},
{name: "apache-only groovy module", artifactID: "groovy-ginq", version: "4.0.33", want: "org.apache.groovy", wantOK: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := ArtifactIDToGroupID(tt.artifactID, tt.version)
assert.Equal(t, tt.wantOK, ok)
assert.Equal(t, tt.want, got)
})
}
}
func Test_majorVersion(t *testing.T) {
tests := map[string]int{
"4.0.33": 4,
"4.0.0-alpha-1": 4,
"4-SNAPSHOT": 4,
"4": 4,
"3.0.22": 3,
"v4.0.33": 0,
"": 0,
}
for version, expect := range tests {
t.Run(version, func(t *testing.T) {
assert.Equal(t, expect, majorVersion(version))
})
}
}
@@ -400,6 +400,16 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
},
expects: nil,
},
{
// pom-less groovy 4 jars (all real ones) must resolve to the apache group, same as the purl
name: "groovy 4 without pom metadata",
pkg: pkg.Package{
Name: "groovy",
Version: "4.0.33",
Metadata: pkg.JavaArchive{Manifest: &pkg.JavaManifest{}},
},
expects: []string{"org.apache.groovy"},
},
{
name: "no manifest or pom info",
pkg: pkg.Package{
@@ -562,7 +572,7 @@ func Test_groupIDsFromJavaManifest(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := groupIDsFromJavaManifest(test.name, &test.manifest)
got := groupIDsFromJavaManifest(test.name, "", &test.manifest)
require.Equal(t, test.expected, got)
})
}
+4 -4
View File
@@ -327,7 +327,7 @@ func (j *archiveParser) discoverNameVersionLicense(ctx context.Context, manifest
// findLicenseFromJavaMetadata attempts to find license information from all available maven metadata properties and pom info
func (j *archiveParser) findLicenseFromJavaMetadata(ctx context.Context, groupID, artifactID, version string, parsedPom *parsedPomProject, manifest *pkg.JavaManifest) []pkg.License {
if groupID == "" {
if gID := groupIDFromJavaMetadata(artifactID, pkg.JavaArchive{Manifest: manifest}); gID != "" {
if gID := groupIDFromJavaMetadata(artifactID, version, pkg.JavaArchive{Manifest: manifest}); gID != "" {
groupID = gID
}
}
@@ -786,7 +786,7 @@ func newPackageFromMavenData(ctx context.Context, r *maven.Resolver, pomProperti
vPathSuffix := ""
groupID := ""
if parentMetadata, ok := parentPkg.Metadata.(pkg.JavaArchive); ok {
groupID = groupIDFromJavaMetadata(parentPkg.Name, parentMetadata)
groupID = groupIDFromJavaMetadata(parentPkg.Name, parentPkg.Version, parentMetadata)
}
parentKey := fmt.Sprintf("%s:%s:%s", groupID, parentPkg.Name, parentPkg.Version)
@@ -863,8 +863,8 @@ func packageIdentitiesMatch(p pkg.Package, parentPkg *pkg.Package) bool {
}
// try to determine identity with the metadata
groupID := groupIDFromJavaMetadata(p.Name, metadata)
parentGroupID := groupIDFromJavaMetadata(parentPkg.Name, parentMetadata)
groupID := groupIDFromJavaMetadata(p.Name, p.Version, metadata)
parentGroupID := groupIDFromJavaMetadata(parentPkg.Name, parentPkg.Version, parentMetadata)
if uniquePkgKey(groupID, &p) == uniquePkgKey(parentGroupID, parentPkg) {
return true
}
+5 -12
View File
@@ -12,7 +12,7 @@ import (
func packageURL(name, version string, metadata pkg.JavaArchive) string {
var groupID = name
if gID := groupIDFromJavaMetadata(name, metadata); gID != "" {
if gID := groupIDFromJavaMetadata(name, version, metadata); gID != "" {
groupID = gID
}
@@ -30,9 +30,9 @@ func packageURL(name, version string, metadata pkg.JavaArchive) string {
// The order of precedence is:
// 1. The group ID from the POM properties
// 2. The group ID from the POM project
// 3. The group ID from a select map of known group IDs
// 3. The group ID from a select map of known group IDs (see cpegenerate.ArtifactIDToGroupID)
// 4. The group ID from the Java manifest
func groupIDFromJavaMetadata(pkgName string, metadata pkg.JavaArchive) (groupID string) {
func groupIDFromJavaMetadata(pkgName, version string, metadata pkg.JavaArchive) (groupID string) {
if groupID = groupIDFromPomProperties(metadata.PomProperties); groupID != "" {
return groupID
}
@@ -41,8 +41,8 @@ func groupIDFromJavaMetadata(pkgName string, metadata pkg.JavaArchive) (groupID
return groupID
}
if groupID = groupIDFromKnownPackageList(pkgName); groupID != "" {
return groupID
if gID, ok := cpegenerate.ArtifactIDToGroupID(pkgName, version); ok {
return gID
}
if groupID = groupIDFromJavaManifest(metadata.Manifest); groupID != "" {
@@ -52,13 +52,6 @@ func groupIDFromJavaMetadata(pkgName string, metadata pkg.JavaArchive) (groupID
return groupID
}
func groupIDFromKnownPackageList(pkgName string) (groupID string) {
if groupID, ok := cpegenerate.DefaultArtifactIDToGroupID[pkgName]; ok {
return groupID
}
return groupID
}
func groupIDFromJavaManifest(manifest *pkg.JavaManifest) (groupID string) {
if manifest == nil {
return groupID
+54 -1
View File
@@ -132,6 +132,26 @@ func Test_packageURL(t *testing.T) {
},
expect: "pkg:maven/parent/example-java-app-maven@0.1.0",
},
{
// regression for github.com/anchore/syft/issues/5311
name: "groovy 4 without pom metadata",
pkg: pkg.Package{
Name: "groovy",
Version: "4.0.33",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
VirtualPath: "groovy-4.0.33.jar",
Manifest: &pkg.JavaManifest{
Main: []pkg.KeyValue{
{Key: "Bundle-SymbolicName", Value: "groovy"},
{Key: "Automatic-Module-Name", Value: "org.apache.groovy.core"},
},
},
},
},
expect: "pkg:maven/org.apache.groovy/groovy@4.0.33",
},
}
for _, tt := range tests {
t.Run(tt.expect, func(t *testing.T) {
@@ -144,6 +164,7 @@ func Test_groupIDFromJavaMetadata(t *testing.T) {
tests := []struct {
name string
pkgName string
version string
metadata pkg.JavaArchive
expect string
}{
@@ -218,6 +239,38 @@ func Test_groupIDFromJavaMetadata(t *testing.T) {
metadata: pkg.JavaArchive{},
expect: "org.apache.derby",
},
{
// regression for github.com/anchore/syft/issues/5311: groovy 4+ moved to org.apache.groovy, but
// jars without pom metadata fall through to the known package list, which keeps the codehaus-era
// coordinates. The map must also beat the per-module Automatic-Module-Name in the manifest.
name: "known package list groovy 4 beats the manifest",
pkgName: "groovy",
version: "4.0.33",
metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{
Main: []pkg.KeyValue{
{Key: "Bundle-SymbolicName", Value: "groovy"},
{Key: "Automatic-Module-Name", Value: "org.apache.groovy.core"},
},
},
},
expect: "org.apache.groovy",
},
{
// modules only published under the apache coordinates would otherwise take the group ID
// from the per-module Automatic-Module-Name (org.apache.groovy.ginq)
name: "known package list apache-only groovy module",
pkgName: "groovy-ginq",
version: "4.0.33",
metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{
Main: []pkg.KeyValue{
{Key: "Automatic-Module-Name", Value: "org.apache.groovy.ginq"},
},
},
},
expect: "org.apache.groovy",
},
{
name: "java manifest",
metadata: pkg.JavaArchive{
@@ -241,7 +294,7 @@ func Test_groupIDFromJavaMetadata(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expect, groupIDFromJavaMetadata(tt.pkgName, tt.metadata))
assert.Equal(t, tt.expect, groupIDFromJavaMetadata(tt.pkgName, tt.version, tt.metadata))
})
}
}