mirror of
https://github.com/aquasecurity/trivy.git
synced 2026-01-31 05:43:14 +08:00
fix(docker): fix non-det scan results for images with embedded SBOM (#9866)
Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
This commit is contained in:
@@ -235,6 +235,9 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail {
|
||||
}
|
||||
}
|
||||
|
||||
// Filter OS packages with mismatched PURL namespace
|
||||
mergedLayer.Packages = filterMismatchedOSPkgs(mergedLayer.OS.Family, mergedLayer.Packages)
|
||||
|
||||
// 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 {
|
||||
@@ -343,3 +346,41 @@ func secretFindingsContains(findings []ftypes.SecretFinding, finding ftypes.Secr
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// purlMatchesOS checks if a package's PURL namespace matches the detected OS family.
|
||||
// Returns true if the package should be kept (matches OS or has no PURL/namespace).
|
||||
// Returns false if the package should be filtered out (has PURL with mismatched namespace).
|
||||
func purlMatchesOS(pkg ftypes.Package, osFamily ftypes.OSType) bool {
|
||||
if pkg.Identifier.PURL == nil || osFamily == "" {
|
||||
return true // Keep packages without PURL or when OS is not detected
|
||||
}
|
||||
if pkg.Identifier.PURL.Namespace == "" {
|
||||
return true // Keep packages without namespace
|
||||
}
|
||||
return pkg.Identifier.PURL.Namespace == osFamily.PurlNamespace()
|
||||
}
|
||||
|
||||
// filterMismatchedOSPkgs removes OS packages whose PURL namespace doesn't match the detected OS.
|
||||
// Packages with pre-existing PURLs are typically from SBOM files embedded in the image.
|
||||
func filterMismatchedOSPkgs(osFamily ftypes.OSType, pkgs ftypes.Packages) ftypes.Packages {
|
||||
if osFamily == "" {
|
||||
return pkgs // No OS detected, keep all packages
|
||||
}
|
||||
|
||||
var filtered int
|
||||
result := lo.Filter(pkgs, func(pkg ftypes.Package, _ int) bool {
|
||||
if purlMatchesOS(pkg, osFamily) {
|
||||
return true
|
||||
}
|
||||
filtered++
|
||||
return false
|
||||
})
|
||||
|
||||
if filtered > 0 {
|
||||
log.WithPrefix("applier").Warn("Some OS packages were skipped due to mismatched PURL namespace",
|
||||
log.Int("pkg_count", filtered),
|
||||
log.String("detected_os", string(osFamily)))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1462,6 +1462,56 @@ func TestApplyLayers(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Duplicate packages with different PURL namespaces, prefer OS-matching PURL
|
||||
name: "prefer OS-matching PURL during deduplication",
|
||||
inputLayers: []types.BlobInfo{
|
||||
{
|
||||
SchemaVersion: 2,
|
||||
OS: types.OS{Family: "chainguard", Name: "20230201"},
|
||||
PackageInfos: []types.PackageInfo{
|
||||
{
|
||||
FilePath: "lib/apk/db/installed",
|
||||
Packages: types.Packages{
|
||||
{Name: "libcrypto3", Version: "3.0.0"}, // No PURL - will get chainguard
|
||||
{
|
||||
Name: "libcrypto3",
|
||||
Version: "3.0.0",
|
||||
Identifier: types.PkgIdentifier{
|
||||
PURL: &packageurl.PackageURL{
|
||||
Type: packageurl.TypeApk,
|
||||
Namespace: "wolfi", // Mismatched
|
||||
Name: "libcrypto3",
|
||||
Version: "3.0.0",
|
||||
Qualifiers: packageurl.Qualifiers{{Key: "distro", Value: "wolfi"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: types.ArtifactDetail{
|
||||
OS: types.OS{Family: "chainguard", Name: "20230201"},
|
||||
Packages: types.Packages{
|
||||
{
|
||||
Name: "libcrypto3",
|
||||
Version: "3.0.0",
|
||||
Identifier: types.PkgIdentifier{
|
||||
UID: "bdca9f208c0174a0",
|
||||
PURL: &packageurl.PackageURL{
|
||||
Type: packageurl.TypeApk,
|
||||
Namespace: "chainguard", // Matches OS
|
||||
Name: "libcrypto3",
|
||||
Version: "3.0.0",
|
||||
Qualifiers: packageurl.Qualifiers{{Key: "distro", Value: "20230201"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -48,6 +48,22 @@ const (
|
||||
Wolfi OSType = "wolfi"
|
||||
)
|
||||
|
||||
// PurlNamespace returns the normalized namespace for Package URL (PURL) representation.
|
||||
// For SUSE-based distributions (SLES, SLE Micro), it returns "suse".
|
||||
// For openSUSE variants (Tumbleweed, Leap), it returns "opensuse".
|
||||
// For all other OSTypes, it returns the string representation of the OSType.
|
||||
func (o OSType) PurlNamespace() string {
|
||||
// SLES string has whitespace, also highlevel family is not the same as distro
|
||||
if o == SLES || o == SLEMicro {
|
||||
return "suse"
|
||||
}
|
||||
if o == OpenSUSETumbleweed || o == OpenSUSELeap {
|
||||
return "opensuse"
|
||||
}
|
||||
|
||||
return string(o)
|
||||
}
|
||||
|
||||
// OSTypeAliases is a map of aliases for operating systems.
|
||||
var OSTypeAliases = map[OSType]OSType{
|
||||
// This is used to map the old family names to the new ones for backward compatibility.
|
||||
|
||||
@@ -77,7 +77,7 @@ func New(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (*Pac
|
||||
switch ptype {
|
||||
case packageurl.TypeRPM:
|
||||
ns, qs := parseRPM(metadata.OS, pkg.Modularitylabel)
|
||||
namespace = string(ns)
|
||||
namespace = ns
|
||||
qualifiers = append(qualifiers, qs...)
|
||||
case packageurl.TypeDebian:
|
||||
qualifiers = append(qualifiers, parseDeb(metadata.OS)...)
|
||||
@@ -369,20 +369,11 @@ func parseDeb(fos *ftypes.OS) packageurl.Qualifiers {
|
||||
}
|
||||
|
||||
// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#rpm
|
||||
func parseRPM(fos *ftypes.OS, modularityLabel string) (ftypes.OSType, packageurl.Qualifiers) {
|
||||
func parseRPM(fos *ftypes.OS, modularityLabel string) (string, packageurl.Qualifiers) {
|
||||
if fos == nil {
|
||||
return "", packageurl.Qualifiers{}
|
||||
}
|
||||
|
||||
family := fos.Family
|
||||
// SLES string has whitespace, also highlevel family is not the same as distro
|
||||
if fos.Family == ftypes.SLES || fos.Family == ftypes.SLEMicro {
|
||||
family = "suse"
|
||||
}
|
||||
if fos.Family == ftypes.OpenSUSETumbleweed || fos.Family == ftypes.OpenSUSELeap {
|
||||
family = "opensuse"
|
||||
}
|
||||
|
||||
qualifiers := packageurl.Qualifiers{
|
||||
{
|
||||
Key: "distro",
|
||||
@@ -396,7 +387,7 @@ func parseRPM(fos *ftypes.OS, modularityLabel string) (ftypes.OSType, packageurl
|
||||
Value: modularityLabel,
|
||||
})
|
||||
}
|
||||
return family, qualifiers
|
||||
return fos.Family.PurlNamespace(), qualifiers
|
||||
}
|
||||
|
||||
// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#maven
|
||||
|
||||
Reference in New Issue
Block a user