diff --git a/internal/relationship/index.go b/internal/relationship/index.go index ab78c62b4f6..39aee4ec15c 100644 --- a/internal/relationship/index.go +++ b/internal/relationship/index.go @@ -79,6 +79,10 @@ func (i *Index) Remove(id artifact.ID) { func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) { for _, mapped := range fromMappedByID(i.fromID, ogID) { + // the stale relationship(i.e. if there's an elder ID in either side) should be discarded + if len(fromMappedByID(i.toID, mapped.relationship.To.ID())) == 0 { + continue + } i.Add(artifact.Relationship{ From: replacement, To: mapped.relationship.To, @@ -87,6 +91,10 @@ func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) { } for _, mapped := range fromMappedByID(i.toID, ogID) { + // same as the above + if len(fromMappedByID(i.fromID, mapped.relationship.To.ID())) == 0 { + continue + } i.Add(artifact.Relationship{ From: mapped.relationship.From, To: replacement, diff --git a/syft/pkg/cataloger/golang/parse_go_binary.go b/syft/pkg/cataloger/golang/parse_go_binary.go index 7a76df875db..9cb25337f31 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary.go +++ b/syft/pkg/cataloger/golang/parse_go_binary.go @@ -98,6 +98,24 @@ func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact. return relationships } +// moduleEqual is used to deduplicate go modules especially the sub module may be identical to the main one +func moduleEqual(lhs, rhs *debug.Module) bool { + if lhs == rhs { + return true + } + if lhs == nil || rhs == nil { + return false + } + + if lhs.Path != rhs.Path || + lhs.Version != rhs.Version || + lhs.Sum != rhs.Sum { + return false + } + + return moduleEqual(lhs.Replace, rhs.Replace) +} + var emptyModule debug.Module var moduleFromPartialPackageBuild = debug.Module{Path: "command-line-arguments"} @@ -115,7 +133,9 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, resolver file.Re if dep == nil { continue } - + if moduleEqual(dep, &mod.Main) { + continue + } lics := c.licenseResolver.getLicenses(ctx, resolver, dep.Path, dep.Version) gover, experiments := getExperimentsFromVersion(mod.GoVersion) diff --git a/syft/pkg/cataloger/golang/parse_go_binary_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go index 3ada19a8af1..ca124208c73 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -847,6 +847,86 @@ func TestBuildGoPkgInfo(t *testing.T) { unmodifiedMain, }, }, + { + name: "parse a populated mod string and returns packages when a replace directive and synthetic main module 'command line arguments' exists", + mod: &extendedBuildInfo{ + BuildInfo: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "command-line-arguments", Version: devel}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "linux"}, + {Key: "GOAMD64", Value: "v1"}, + }, + Path: "command-line-arguments", + Deps: []*debug.Module{ + { + Path: "example.com/mylib", + Version: "v0.0.0", + Replace: &debug.Module{ + Path: "./mylib", + Version: devel, + }, + }, + { + Path: "command-line-arguments", + Version: devel, + }, + }, + }, + cryptoSettings: nil, + arch: archDetails, + }, + expected: []pkg.Package{ + { + Name: "example.com/mylib", + Version: "", + PURL: "pkg:golang/example.com/mylib", + Language: pkg.Go, + Type: pkg.GoModulePkg, + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + Metadata: pkg.GolangBinaryBuildinfoEntry{ + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + H1Digest: "", + MainModule: "command-line-arguments", + }, + }, + { + Name: "command-line-arguments", + Version: "", + PURL: "pkg:golang/command-line-arguments", + Language: pkg.Go, + Type: pkg.GoModulePkg, + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + Metadata: pkg.GolangBinaryBuildinfoEntry{ + BuildSettings: pkg.KeyValues{ + {Key: "GOARCH", Value: "amd64"}, + {Key: "GOOS", Value: "linux"}, + {Key: "GOAMD64", Value: "v1"}, + }, + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + H1Digest: "", + MainModule: "command-line-arguments", + }, + }, + }, + }, { name: "parse main mod and replace devel with pattern from binary contents", cfg: func() *CatalogerConfig {