diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index c14c95538c65..6a12eafddfb4 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -1,6 +1,7 @@ package applier import ( + "cmp" "fmt" "strings" "time" @@ -13,7 +14,9 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/scanner/utils" "github.com/aquasecurity/trivy/pkg/types" + xslices "github.com/aquasecurity/trivy/pkg/x/slices" ) type Config struct { @@ -232,6 +235,12 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { } } + // De-duplicate same debian packages from different dirs + // cf. https://github.com/aquasecurity/trivy/issues/8297 + mergedLayer.Packages = xslices.ZeroToNil(lo.UniqBy(mergedLayer.Packages, func(pkg ftypes.Package) string { + return cmp.Or(pkg.ID, fmt.Sprintf("%s@%s", pkg.Name, utils.FormatVersion(pkg))) + })) + for _, app := range mergedLayer.Applications { for i, pkg := range app.Packages { // Skip lookup for SBOM diff --git a/pkg/fanal/applier/docker_test.go b/pkg/fanal/applier/docker_test.go index 93facbd39186..0269bbc7442c 100644 --- a/pkg/fanal/applier/docker_test.go +++ b/pkg/fanal/applier/docker_test.go @@ -254,6 +254,61 @@ func TestApplyLayers(t *testing.T) { }, }, }, + { + name: "happy path with duplicate of debian packages", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 2, + DiffID: "sha256:96e320b34b5478d8b369ca43ffaa88ff6dd9499ec72b792ca21b1e8b0c55670f", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/libssl1", + Packages: types.Packages{ + { + ID: "libssl1.1@1.1.1n-0+deb11u3", + Name: "libssl1.1", + Version: "1.1.1n", + Release: "0+deb11u3", + }, + }, + }, + }, + }, + { + SchemaVersion: 2, + DiffID: "sha256:5e087d956f3e62bd034dd0712bc4cbef8fda55fba0b11a7d0564f294887c7079", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status.d/libssl1.1", + Packages: types.Packages{ + { + ID: "libssl1.1@1.1.1n-0+deb11u3", + Name: "libssl1.1", + Version: "1.1.1n", + Release: "0+deb11u3", + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + Packages: types.Packages{ + { + ID: "libssl1.1@1.1.1n-0+deb11u3", + Name: "libssl1.1", + Version: "1.1.1n", + Release: "0+deb11u3", + Identifier: types.PkgIdentifier{ + UID: "522a5c3b263d1357", + }, + Layer: types.Layer{ + DiffID: "sha256:96e320b34b5478d8b369ca43ffaa88ff6dd9499ec72b792ca21b1e8b0c55670f", + }, + }, + }, + }, + }, { name: "happy path with digests in libs/packages (as for SBOM)", inputLayers: []types.BlobInfo{ diff --git a/pkg/x/slices/slices.go b/pkg/x/slices/slices.go index 8e256814bbc7..81ba7a02f524 100644 --- a/pkg/x/slices/slices.go +++ b/pkg/x/slices/slices.go @@ -1,5 +1,6 @@ package slices +// ZeroToNil returns nil, if slice is empty func ZeroToNil[T any](t []T) []T { if len(t) == 0 { return nil