From 92ae4d44c5df09496e541cae51a11c3caef6ac4e Mon Sep 17 00:00:00 2001 From: Rez Moss Date: Tue, 16 Jun 2026 12:02:42 -0400 Subject: [PATCH] fix: .net deps.json cataloger no longer shows phantom pkgs (#4971) Signed-off-by: Rez Moss --- .../cataloger/dotnet/deps_binary_cataloger.go | 8 ++ .../dotnet/deps_binary_cataloger_test.go | 97 +++++++++++++++++++ .../MyApp.deps.json | 62 ++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 syft/pkg/cataloger/dotnet/deps_binary_cataloger_test.go create mode 100644 syft/pkg/cataloger/dotnet/testdata/deps-with-referenceassembly/MyApp.deps.json diff --git a/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go index 44c0a9e97..105c98c47 100644 --- a/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go +++ b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go @@ -292,6 +292,14 @@ func packagesFromLogicalDepsJSON(doc logicalDepsJSON, config CatalogerConfig) (* } lp := doc.PackagesByNameVersion[nameVersion] + if lp.Library != nil && + lp.Library.Type == "referenceassembly" && + lp.Library.Sha512 == "" && + lp.Library.Path == "" { + skippedDepPkgs[nameVersion] = lp + continue + } + if config.ExcludeProjectReferences && lp.Library != nil && lp.Library.Type == "project" { skippedDepPkgs[nameVersion] = lp continue diff --git a/syft/pkg/cataloger/dotnet/deps_binary_cataloger_test.go b/syft/pkg/cataloger/dotnet/deps_binary_cataloger_test.go new file mode 100644 index 000000000..9ac5bb517 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/deps_binary_cataloger_test.go @@ -0,0 +1,97 @@ +package dotnet + +import ( + "encoding/json" + "os" + "testing" + + "github.com/scylladb/go-set/strset" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" +) + +func Test_packagesFromLogicalDepsJSON_skipsReferenceAssemblies(t *testing.T) { + mkPkg := func(name, version, libType, sha, path string) logicalDepsJSONPackage { + nv := name + "/" + version + return logicalDepsJSONPackage{ + NameVersion: nv, + Library: &depsLibrary{Type: libType, Sha512: sha, Path: path}, + Targets: []depsTarget{ + {Runtime: map[string]map[string]string{"lib/" + name + ".dll": {}}}, + }, + RuntimePathsByRelativeDLLPath: map[string]string{name + ".dll": "lib/" + name + ".dll"}, + ResourcePathsByRelativeDLLPath: map[string]string{}, + CompilePathsByRelativeDLLPath: map[string]string{}, + NativePaths: strset.New(), + } + } + + realPkg := mkPkg("Real", "1.0.0", "package", "abc", "real/1.0.0") + phantom := mkPkg("Phantom", "2.0.0", "referenceassembly", "", "") + keptRef := mkPkg("KeptRef", "3.0.0", "referenceassembly", "xyz", "p") + + doc := logicalDepsJSON{ + Location: file.NewLocation("/app/Test.deps.json"), + PackagesByNameVersion: map[string]logicalDepsJSONPackage{ + realPkg.NameVersion: realPkg, + phantom.NameVersion: phantom, + keptRef.NameVersion: keptRef, + }, + PackageNameVersions: strset.New(realPkg.NameVersion, phantom.NameVersion, keptRef.NameVersion), + } + + cfg := DefaultCatalogerConfig(). + WithDepPackagesMustClaimDLL(false). + WithDepPackagesMustHaveDLL(false) + + _, pkgs, _ := packagesFromLogicalDepsJSON(doc, cfg) + + names := make(map[string]string) + for _, p := range pkgs { + names[p.Name] = p.Version + } + + assert.Equal(t, "1.0.0", names["Real"]) + assert.Equal(t, "3.0.0", names["KeptRef"], "ref-asm w/ sha kept") + assert.NotContains(t, names, "Phantom", "ref-asm w/o evidence skipped") + + for _, p := range pkgs { + assert.Equal(t, pkg.DotnetPkg, p.Type) + } +} + +func Test_packagesFromLogicalDepsJSON_fixture_referenceassembly(t *testing.T) { + const fixturePath = "testdata/deps-with-referenceassembly/MyApp.deps.json" + + raw, err := os.ReadFile(fixturePath) + require.NoError(t, err) + + var deps depsJSON + require.NoError(t, json.Unmarshal(raw, &deps)) + deps.Location = file.NewLocation("/app/MyApp.deps.json") + + doc := getLogicalDepsJSON(deps, nil) + doc.Location = deps.Location + + cfg := DefaultCatalogerConfig(). + WithDepPackagesMustClaimDLL(false). + WithDepPackagesMustHaveDLL(false) + + _, pkgs, _ := packagesFromLogicalDepsJSON(doc, cfg) + + names := make(map[string]string) + for _, p := range pkgs { + names[p.Name] = p.Version + } + + assert.Equal(t, "13.0.3", names["Newtonsoft.Json"]) + assert.NotContains(t, names, "Microsoft.AspNetCore.DataProtection") + assert.NotContains(t, names, "Microsoft.AspNetCore.DataProtection.Abstractions") + + for _, p := range pkgs { + assert.NotEqual(t, "10.0.0.0", p.Version, "no 10.0.0.0 phantom") + } +} diff --git a/syft/pkg/cataloger/dotnet/testdata/deps-with-referenceassembly/MyApp.deps.json b/syft/pkg/cataloger/dotnet/testdata/deps-with-referenceassembly/MyApp.deps.json new file mode 100644 index 000000000..240b2635d --- /dev/null +++ b/syft/pkg/cataloger/dotnet/testdata/deps-with-referenceassembly/MyApp.deps.json @@ -0,0 +1,62 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v10.0": { + "MyApp/1.0.0": { + "dependencies": { + "Newtonsoft.Json": "13.0.3", + "Microsoft.AspNetCore.DataProtection": "10.0.0.0" + }, + "runtime": { + "MyApp.dll": {} + } + }, + "Newtonsoft.Json/13.0.3": { + "runtime": { + "lib/net6.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.3.27908" + } + } + }, + "Microsoft.AspNetCore.DataProtection/10.0.0.0": { + "compile": { + "Microsoft.AspNetCore.DataProtection.dll": {} + } + }, + "Microsoft.AspNetCore.DataProtection.Abstractions/10.0.0.0": { + "compile": { + "Microsoft.AspNetCore.DataProtection.Abstractions.dll": {} + } + } + } + }, + "libraries": { + "MyApp/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Newtonsoft.Json/13.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + "path": "newtonsoft.json/13.0.3", + "hashPath": "newtonsoft.json.13.0.3.nupkg.sha512" + }, + "Microsoft.AspNetCore.DataProtection/10.0.0.0": { + "type": "referenceassembly", + "serviceable": false, + "sha512": "" + }, + "Microsoft.AspNetCore.DataProtection.Abstractions/10.0.0.0": { + "type": "referenceassembly", + "serviceable": false, + "sha512": "" + } + } +}