diff --git a/.github/scripts/find_cache_paths.py b/.github/scripts/find_cache_paths.py index cc2e4081a..5d66ae589 100755 --- a/.github/scripts/find_cache_paths.py +++ b/.github/scripts/find_cache_paths.py @@ -86,8 +86,10 @@ def main(file_path: str | None): show("The following paths are missing or have no content, but have corresponding .fingerprint files:") for path in sorted(missing_content): show(f"- {path}") - show("Please ensure these paths exist and have content if they are directories.") - exit(1) + # when adding new cache directories there is a time where it is not possible to have this directory without + # running the tests first... but this step is a prerequisite for running the tests. We should not block on this. + # show("Please ensure these paths exist and have content if they are directories.") + # exit(1) sha256_hash = calculate_sha256(fingerprint_contents) diff --git a/cmd/syft/internal/options/catalog.go b/cmd/syft/internal/options/catalog.go index a3fd88c46..5e2901bcb 100644 --- a/cmd/syft/internal/options/catalog.go +++ b/cmd/syft/internal/options/catalog.go @@ -168,7 +168,9 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config { return pkgcataloging.Config{ Binary: binary.DefaultClassifierCatalogerConfig(), Dotnet: dotnet.DefaultCatalogerConfig(). - WithCertificateValidation(cfg.Dotnet.EnableCertificateValidation), + WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL). + WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL). + WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected), Golang: golang.DefaultCatalogerConfig(). WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)). WithLocalModCacheDir(cfg.Golang.LocalModCacheDir). diff --git a/cmd/syft/internal/options/dotnet.go b/cmd/syft/internal/options/dotnet.go index 6d7c1ba1a..08b726324 100644 --- a/cmd/syft/internal/options/dotnet.go +++ b/cmd/syft/internal/options/dotnet.go @@ -6,7 +6,11 @@ import ( ) type dotnetConfig struct { - EnableCertificateValidation bool `json:"enable-certificate-validation" yaml:"enable-certificate-validation" mapstructure:"enable-certificate-validation"` + DepPackagesMustHaveDLL bool `mapstructure:"dep-packages-must-have-dll" json:"dep-packages-must-have-dll" yaml:"dep-packages-must-have-dll"` + + DepPackagesMustClaimDLL bool `mapstructure:"dep-packages-must-claim-dll" json:"dep-packages-must-claim-dll" yaml:"dep-packages-must-claim-dll"` + + RelaxDLLClaimsWhenBundlingDetected bool `mapstructure:"relax-dll-claims-when-bundling-detected" json:"relax-dll-claims-when-bundling-detected" yaml:"relax-dll-claims-when-bundling-detected"` } var _ interface { @@ -14,12 +18,16 @@ var _ interface { } = (*dotnetConfig)(nil) func (o *dotnetConfig) DescribeFields(descriptions clio.FieldDescriptionSet) { - descriptions.Add(&o.EnableCertificateValidation, `enable certificate validation -- this requires an active internet connection to download certificates and CRLs`) + descriptions.Add(&o.DepPackagesMustHaveDLL, `only keep dep.json packages which an executable on disk can be found for`) + descriptions.Add(&o.DepPackagesMustClaimDLL, `only keep dep.json packages which have a runtime/resource DLL claimed in the deps.json targets section (but not necessarily found on disk)`) + descriptions.Add(&o.RelaxDLLClaimsWhenBundlingDetected, `show all packages from the deps.json if bundling tooling is present as a dependency (e.g. ILRepack)`) } func defaultDotnetConfig() dotnetConfig { def := dotnet.DefaultCatalogerConfig() return dotnetConfig{ - EnableCertificateValidation: def.EnableCertificateValidation, + DepPackagesMustHaveDLL: def.DepPackagesMustHaveDLL, + DepPackagesMustClaimDLL: def.DepPackagesMustClaimDLL, + RelaxDLLClaimsWhenBundlingDetected: def.RelaxDLLClaimsWhenBundlingDetected, } } diff --git a/cmd/syft/internal/test/integration/catalog_packages_cases_test.go b/cmd/syft/internal/test/integration/catalog_packages_cases_test.go index 09dbd8bf3..d07785fd3 100644 --- a/cmd/syft/internal/test/integration/catalog_packages_cases_test.go +++ b/cmd/syft/internal/test/integration/catalog_packages_cases_test.go @@ -78,11 +78,27 @@ var imageOnlyTestCases = []testCase{ }, }, { - name: "find dot net executable", + name: "find .NET packages (deps.json + .dlls)", pkgType: pkg.DotnetPkg, pkgLanguage: pkg.Dotnet, pkgInfo: map[string]string{ + // executable "DocuSign.eSign": "6.8.0.0", + // deps.json + "AWSSDK.Core": "3.7.10.6", + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0", + "Microsoft.Extensions.Primitives": "6.0.0", + "Newtonsoft.Json": "13.0.1", + "Serilog": "2.10.0", + "Serilog.Sinks.Console": "4.0.1", + //"System.Diagnostics.DiagnosticSource": "6.0.0", // no dll claims in deps.json targets section + //"System.Runtime.CompilerServices.Unsafe": "6.0.0", // no dll claims in deps.json targets section + "TestCommon": "1.0.0", + "TestLibrary": "1.0.0", }, }, } @@ -241,10 +257,11 @@ var dirOnlyTestCases = []testCase{ }, }, { - name: "find dotnet packages", + name: "find dotnet packages (.deps.json)", pkgType: pkg.DotnetPkg, pkgLanguage: pkg.Dotnet, pkgInfo: map[string]string{ + // all from deps.json "AWSSDK.Core": "3.7.10.6", "Microsoft.Extensions.DependencyInjection": "6.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", @@ -255,10 +272,10 @@ var dirOnlyTestCases = []testCase{ "Newtonsoft.Json": "13.0.1", "Serilog": "2.10.0", "Serilog.Sinks.Console": "4.0.1", - "System.Diagnostics.DiagnosticSource": "6.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "TestCommon": "1.0.0", - "TestLibrary": "1.0.0", + //"System.Diagnostics.DiagnosticSource": "6.0.0", // no dll claims in deps.json targets section + //"System.Runtime.CompilerServices.Unsafe": "6.0.0", // no dll claims in deps.json targets section + "TestCommon": "1.0.0", + "TestLibrary": "1.0.0", }, }, { diff --git a/cmd/syft/internal/test/integration/package_catalogers_represented_test.go b/cmd/syft/internal/test/integration/package_catalogers_represented_test.go index 2375c4290..bca070fc8 100644 --- a/cmd/syft/internal/test/integration/package_catalogers_represented_test.go +++ b/cmd/syft/internal/test/integration/package_catalogers_represented_test.go @@ -52,8 +52,16 @@ func TestAllPackageCatalogersReachableInTasks(t *testing.T) { assert.Equal(t, len(taskTagsByName), constructorCount, "mismatch in number of cataloger constructors and task names") + exceptions := strset.New( + // not reachable since they are deprecated + "dotnet-portable-executable-cataloger", + "dotnet-deps-cataloger", + // not reachable by design + "sbom-cataloger", + ) + for taskName, tags := range taskTagsByName { - if taskName == "sbom-cataloger" { + if exceptions.Has(taskName) { continue // this is a special case } if !strset.New(tags...).HasAny(pkgcataloging.ImageTag, pkgcataloging.DirectoryTag) { diff --git a/go.mod b/go.mod index b80c8b255..a6645400c 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,6 @@ require ( github.com/pelletier/go-toml v1.9.5 github.com/quasilyte/go-ruleguard/dsl v0.3.22 github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c - github.com/saferwall/pe v1.5.6 github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d github.com/sanity-io/litter v1.5.8 github.com/sassoftware/go-rpmutils v0.4.0 @@ -141,7 +140,6 @@ require ( github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect - github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/fgprof v0.9.5 // indirect @@ -209,7 +207,6 @@ require ( github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect - github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect diff --git a/go.sum b/go.sum index fe64b46cf..5804f1111 100644 --- a/go.sum +++ b/go.sum @@ -279,8 +279,6 @@ github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj6 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= -github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY= @@ -725,8 +723,6 @@ github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c h1:8 github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c/go.mod h1:kwM/7r/rVluTE8qJbHAffduuqmSv4knVQT2IajGvSiA= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/saferwall/pe v1.5.6 h1:DrRLnoQFxHWJ5lJUmrH7X2L0xeUu6SUS95Dc61eW2Yc= -github.com/saferwall/pe v1.5.6/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= @@ -743,8 +739,6 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd7 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY= github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= -github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d h1:RQqyEogx5J6wPdoxqL132b100j8KjcVHO1c0KLRoIhc= -github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d/go.mod h1:PegD7EVqlN88z7TpCqH92hHP+GBpfomGCCnw1PFtNOA= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= @@ -1109,7 +1103,6 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/constants.go b/internal/constants.go index dbc0a76f2..5829a5855 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -3,5 +3,5 @@ package internal const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "16.0.23" + JSONSchemaVersion = "16.0.24" ) diff --git a/internal/task/package_tasks.go b/internal/task/package_tasks.go index cd104e9eb..dbb74c072 100644 --- a/internal/task/package_tasks.go +++ b/internal/task/package_tasks.go @@ -76,7 +76,6 @@ func DefaultPackageTaskFactories() Factories { // language-specific package declared catalogers /////////////////////////////////////////////////////////////////////////// newSimplePackageTaskFactory(cpp.NewConanCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "cpp", "conan"), newSimplePackageTaskFactory(dart.NewPubspecLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dart"), - newSimplePackageTaskFactory(dotnet.NewDotnetDepsCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"), newSimplePackageTaskFactory(elixir.NewMixLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "elixir"), newSimplePackageTaskFactory(erlang.NewRebarLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang"), newSimplePackageTaskFactory(erlang.NewOTPCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang", "otp"), @@ -117,11 +116,13 @@ func DefaultPackageTaskFactories() Factories { newSimplePackageTaskFactory(ocaml.NewOpamPackageManagerCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "ocaml", "opam"), // language-specific package for both image and directory scans (but not necessarily declared) //////////////////////////////////////// - newSimplePackageTaskFactory(dotnet.NewDotnetPackagesLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"), newPackageTaskFactory( func(cfg CatalogingFactoryConfig) pkg.Cataloger { - return dotnet.NewDotnetPortableExecutableCataloger(cfg.PackagesConfig.Dotnet) - }, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "dotnet", "c#", "binary"), + return dotnet.NewDotnetDepsBinaryCataloger(cfg.PackagesConfig.Dotnet) + }, + pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#", + ), + newSimplePackageTaskFactory(dotnet.NewDotnetPackagesLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"), newSimplePackageTaskFactory(python.NewInstalledPackageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "python"), newPackageTaskFactory( func(cfg CatalogingFactoryConfig) pkg.Cataloger { @@ -160,5 +161,10 @@ func DefaultPackageTaskFactories() Factories { newSimplePackageTaskFactory(bitnamiSbomCataloger.NewCataloger, "bitnami", pkgcataloging.InstalledTag, pkgcataloging.ImageTag), newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"), newSimplePackageTaskFactory(terraform.NewLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "terraform"), + + // deprecated catalogers //////////////////////////////////////// + // these are catalogers that should not be selectable other than specific inclusion via name or "deprecated" tag (to remain backwards compatible) + newSimplePackageTaskFactory(dotnet.NewDotnetDepsCataloger, pkgcataloging.DeprecatedTag), // TODO: remove in syft v2.0 + newSimplePackageTaskFactory(dotnet.NewDotnetPortableExecutableCataloger, pkgcataloging.DeprecatedTag), // TODO: remove in syft v2.0 } } diff --git a/schema/json/schema-16.0.24.json b/schema/json/schema-16.0.24.json new file mode 100644 index 000000000..c106015ed --- /dev/null +++ b/schema/json/schema-16.0.24.json @@ -0,0 +1,2897 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/16.0.24/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmDbEntry": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "ApkDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "BinarySignature": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "BitnamiSbomEntry": { + "properties": { + "name": { + "type": "string" + }, + "arch": { + "type": "string" + }, + "distro": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "arch", + "distro", + "revision", + "version", + "path", + "files" + ] + }, + "CConanFileEntry": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanInfoEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "build_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "py_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "$ref": "#/$defs/KeyValues" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockV2Entry": { + "properties": { + "ref": { + "type": "string" + }, + "packageID": { + "type": "string" + }, + "username": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "recipeRevision": { + "type": "string" + }, + "packageRevision": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CPE": { + "properties": { + "cpe": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "cpe" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoaPodfileLockEntry": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubspecLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + }, + "executables": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DotnetPackagesLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "contentHash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "contentHash", + "type" + ] + }, + "DotnetPortableExecutableEntry": { + "properties": { + "assemblyVersion": { + "type": "string" + }, + "legalCopyright": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "internalName": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "productVersion": { + "type": "string" + } + }, + "type": "object", + "required": [ + "assemblyVersion", + "legalCopyright", + "companyName", + "productName", + "productVersion" + ] + }, + "DpkgArchiveEntry": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "DpkgDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "ELFSecurityFeatures": { + "properties": { + "symbolTableStripped": { + "type": "boolean" + }, + "stackCanary": { + "type": "boolean" + }, + "nx": { + "type": "boolean" + }, + "relRO": { + "type": "string" + }, + "pie": { + "type": "boolean" + }, + "dso": { + "type": "boolean" + }, + "safeStack": { + "type": "boolean" + }, + "cfi": { + "type": "boolean" + }, + "fortify": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "symbolTableStripped", + "nx", + "relRO", + "pie", + "dso" + ] + }, + "ElfBinaryPackageNoteJsonPayload": { + "properties": { + "type": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "osCPE": { + "type": "string" + }, + "os": { + "type": "string" + }, + "osVersion": { + "type": "string" + }, + "system": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "commit": { + "type": "string" + } + }, + "type": "object" + }, + "ElixirMixLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "ErlangRebarLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "Executable": { + "properties": { + "format": { + "type": "string" + }, + "hasExports": { + "type": "boolean" + }, + "hasEntrypoint": { + "type": "boolean" + }, + "importedLibraries": { + "items": { + "type": "string" + }, + "type": "array" + }, + "elfSecurityFeatures": { + "$ref": "#/$defs/ELFSecurityFeatures" + } + }, + "type": "object", + "required": [ + "format", + "hasExports", + "hasEntrypoint", + "importedLibraries" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/FileLicense" + }, + "type": "array" + }, + "executable": { + "$ref": "#/$defs/Executable" + }, + "unknowns": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileLicense": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "evidence": { + "$ref": "#/$defs/FileLicenseEvidence" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type" + ] + }, + "FileLicenseEvidence": { + "properties": { + "confidence": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "extent": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "confidence", + "offset", + "extent" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ] + }, + "GoModuleBuildinfoEntry": { + "properties": { + "goBuildSettings": { + "$ref": "#/$defs/KeyValues" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + }, + "goCryptoSettings": { + "items": { + "type": "string" + }, + "type": "array" + }, + "goExperiments": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GoModuleEntry": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackEntry": { + "properties": { + "pkgHash": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackLockEntry": { + "properties": { + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object" + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaArchive": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/JavaPomProperties" + }, + "pomProject": { + "$ref": "#/$defs/JavaPomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "JavaJvmInstallation": { + "properties": { + "release": { + "$ref": "#/$defs/JavaVMRelease" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "release", + "files" + ] + }, + "JavaManifest": { + "properties": { + "main": { + "$ref": "#/$defs/KeyValues" + }, + "sections": { + "items": { + "$ref": "#/$defs/KeyValues" + }, + "type": "array" + } + }, + "type": "object" + }, + "JavaPomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "JavaPomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/JavaPomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "JavaPomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "JavaVMRelease": { + "properties": { + "implementor": { + "type": "string" + }, + "implementorVersion": { + "type": "string" + }, + "javaRuntimeVersion": { + "type": "string" + }, + "javaVersion": { + "type": "string" + }, + "javaVersionDate": { + "type": "string" + }, + "libc": { + "type": "string" + }, + "modules": { + "items": { + "type": "string" + }, + "type": "array" + }, + "osArch": { + "type": "string" + }, + "osName": { + "type": "string" + }, + "osVersion": { + "type": "string" + }, + "source": { + "type": "string" + }, + "buildSource": { + "type": "string" + }, + "buildSourceRepo": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "fullVersion": { + "type": "string" + }, + "semanticVersion": { + "type": "string" + }, + "buildInfo": { + "type": "string" + }, + "jvmVariant": { + "type": "string" + }, + "jvmVersion": { + "type": "string" + }, + "imageType": { + "type": "string" + }, + "buildType": { + "type": "string" + } + }, + "type": "object" + }, + "JavascriptNpmPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "homepage", + "description", + "url", + "private" + ] + }, + "JavascriptNpmPackageLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "JavascriptYarnLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "KeyValue": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "key", + "value" + ] + }, + "KeyValues": { + "items": { + "$ref": "#/$defs/KeyValue" + }, + "type": "array" + }, + "License": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "contents": { + "type": "string" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type", + "urls", + "locations" + ] + }, + "LinuxKernelArchive": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModule": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "accessPath": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "accessPath" + ] + }, + "LuarocksPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "dependencies": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "version", + "license", + "homepage", + "description", + "url", + "dependencies" + ] + }, + "MicrosoftKbPatch": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "NixStoreEntry": { + "properties": { + "outputHash": { + "type": "string" + }, + "output": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash", + "files" + ] + }, + "OpamPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "url": { + "type": "string" + }, + "checksum": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "licenses", + "url", + "checksum", + "homepage", + "dependencies" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "$ref": "#/$defs/licenses" + }, + "language": { + "type": "string" + }, + "cpes": { + "$ref": "#/$defs/cpes" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmDbEntry" + }, + { + "$ref": "#/$defs/ApkDbEntry" + }, + { + "$ref": "#/$defs/BinarySignature" + }, + { + "$ref": "#/$defs/BitnamiSbomEntry" + }, + { + "$ref": "#/$defs/CConanFileEntry" + }, + { + "$ref": "#/$defs/CConanInfoEntry" + }, + { + "$ref": "#/$defs/CConanLockEntry" + }, + { + "$ref": "#/$defs/CConanLockV2Entry" + }, + { + "$ref": "#/$defs/CocoaPodfileLockEntry" + }, + { + "$ref": "#/$defs/DartPubspecLockEntry" + }, + { + "$ref": "#/$defs/DotnetDepsEntry" + }, + { + "$ref": "#/$defs/DotnetPackagesLockEntry" + }, + { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + }, + { + "$ref": "#/$defs/DpkgArchiveEntry" + }, + { + "$ref": "#/$defs/DpkgDbEntry" + }, + { + "$ref": "#/$defs/ElfBinaryPackageNoteJsonPayload" + }, + { + "$ref": "#/$defs/ElixirMixLockEntry" + }, + { + "$ref": "#/$defs/ErlangRebarLockEntry" + }, + { + "$ref": "#/$defs/GoModuleBuildinfoEntry" + }, + { + "$ref": "#/$defs/GoModuleEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackLockEntry" + }, + { + "$ref": "#/$defs/JavaArchive" + }, + { + "$ref": "#/$defs/JavaJvmInstallation" + }, + { + "$ref": "#/$defs/JavascriptNpmPackage" + }, + { + "$ref": "#/$defs/JavascriptNpmPackageLockEntry" + }, + { + "$ref": "#/$defs/JavascriptYarnLockEntry" + }, + { + "$ref": "#/$defs/LinuxKernelArchive" + }, + { + "$ref": "#/$defs/LinuxKernelModule" + }, + { + "$ref": "#/$defs/LuarocksPackage" + }, + { + "$ref": "#/$defs/MicrosoftKbPatch" + }, + { + "$ref": "#/$defs/NixStoreEntry" + }, + { + "$ref": "#/$defs/OpamPackage" + }, + { + "$ref": "#/$defs/PhpComposerInstalledEntry" + }, + { + "$ref": "#/$defs/PhpComposerLockEntry" + }, + { + "$ref": "#/$defs/PhpPeclEntry" + }, + { + "$ref": "#/$defs/PortageDbEntry" + }, + { + "$ref": "#/$defs/PythonPackage" + }, + { + "$ref": "#/$defs/PythonPipRequirementsEntry" + }, + { + "$ref": "#/$defs/PythonPipfileLockEntry" + }, + { + "$ref": "#/$defs/PythonPoetryLockEntry" + }, + { + "$ref": "#/$defs/RDescription" + }, + { + "$ref": "#/$defs/RpmArchive" + }, + { + "$ref": "#/$defs/RpmDbEntry" + }, + { + "$ref": "#/$defs/RubyGemspec" + }, + { + "$ref": "#/$defs/RustCargoAuditEntry" + }, + { + "$ref": "#/$defs/RustCargoLockEntry" + }, + { + "$ref": "#/$defs/SwiftPackageManagerLockEntry" + }, + { + "$ref": "#/$defs/SwiplpackPackage" + }, + { + "$ref": "#/$defs/TerraformLockProviderEntry" + }, + { + "$ref": "#/$defs/WordpressPluginEntry" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerInstalledEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpComposerLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpPeclEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "PortageDbEntry": { + "properties": { + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + }, + "requiresPython": { + "type": "string" + }, + "requiresDist": { + "items": { + "type": "string" + }, + "type": "array" + }, + "providesExtra": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipRequirementsEntry": { + "properties": { + "name": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + }, + "versionConstraint": { + "type": "string" + }, + "url": { + "type": "string" + }, + "markers": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "versionConstraint" + ] + }, + "PythonPipfileLockEntry": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "PythonPoetryLockDependencyEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "markers": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "optional" + ] + }, + "PythonPoetryLockEntry": { + "properties": { + "index": { + "type": "string" + }, + "dependencies": { + "items": { + "$ref": "#/$defs/PythonPoetryLockDependencyEntry" + }, + "type": "array" + }, + "extras": { + "items": { + "$ref": "#/$defs/PythonPoetryLockExtraEntry" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "index", + "dependencies" + ] + }, + "PythonPoetryLockExtraEntry": { + "properties": { + "name": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "dependencies" + ] + }, + "RDescription": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "url": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "built": { + "type": "string" + }, + "needsCompilation": { + "type": "boolean" + }, + "imports": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "suggests": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmArchive": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmDbEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "RubyGemspec": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "RustCargoAuditEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source" + ] + }, + "RustCargoLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "metadata" + ] + }, + "SwiftPackageManagerLockEntry": { + "properties": { + "revision": { + "type": "string" + } + }, + "type": "object", + "required": [ + "revision" + ] + }, + "SwiplpackPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "packager": { + "type": "string" + }, + "packagerEmail": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "packager", + "packagerEmail", + "homepage", + "dependencies" + ] + }, + "TerraformLockProviderEntry": { + "properties": { + "url": { + "type": "string" + }, + "constraints": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "url", + "constraints", + "version", + "hashes" + ] + }, + "WordpressPluginEntry": { + "properties": { + "pluginInstallDirectory": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorUri": { + "type": "string" + } + }, + "type": "object", + "required": [ + "pluginInstallDirectory" + ] + }, + "cpes": { + "items": { + "$ref": "#/$defs/CPE" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/License" + }, + "type": "array" + } + } +} diff --git a/schema/json/schema-latest.json b/schema/json/schema-latest.json index ae26bb8be..c106015ed 100644 --- a/schema/json/schema-latest.json +++ b/schema/json/schema-latest.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "anchore.io/schema/syft/json/16.0.23/document", + "$id": "anchore.io/schema/syft/json/16.0.24/document", "$ref": "#/$defs/Document", "$defs": { "AlpmDbEntry": { @@ -519,6 +519,14 @@ }, "hashPath": { "type": "string" + }, + "executables": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + } + }, + "type": "object" } }, "type": "object", diff --git a/syft/cataloging/pkgcataloging/tags.go b/syft/cataloging/pkgcataloging/tags.go index 5c7b46bb0..3120d78ef 100644 --- a/syft/cataloging/pkgcataloging/tags.go +++ b/syft/cataloging/pkgcataloging/tags.go @@ -21,4 +21,7 @@ const ( // LanguageTag should be used to identify catalogers that cataloging language-specific packages. LanguageTag = "language" + + // DeprecatedTag should be used to identify catalogers that are deprecated. + DeprecatedTag = "deprecated" ) diff --git a/syft/create_sbom_config.go b/syft/create_sbom_config.go index 0e7e828fd..fcbcd5f6d 100644 --- a/syft/create_sbom_config.go +++ b/syft/create_sbom_config.go @@ -126,13 +126,13 @@ func (c *CreateSBOMConfig) WithDataGenerationConfig(cfg cataloging.DataGeneratio return c } -// WithPackagesConfig allows for defining any specific behavior for syft-implemented catalogers. +// WithPackagesConfig allows for defining any specific package cataloging behavior for syft-implemented catalogers. func (c *CreateSBOMConfig) WithPackagesConfig(cfg pkgcataloging.Config) *CreateSBOMConfig { c.Packages = cfg return c } -// WithPackagesConfig allows for defining any specific behavior for syft-implemented catalogers. +// WithLicenseConfig allows for defining any specific license cataloging behavior for syft-implemented catalogers. func (c *CreateSBOMConfig) WithLicenseConfig(cfg cataloging.LicenseConfig) *CreateSBOMConfig { c.Licenses = cfg return c @@ -278,6 +278,10 @@ func (c *CreateSBOMConfig) selectTasks(src source.Description) ([]task.Task, []t return nil, nil, nil, err } + if deprecatedNames := deprecatedTasks(finalTaskGroups); len(deprecatedNames) > 0 { + log.WithFields("catalogers", strings.Join(deprecatedNames, ", ")).Warn("deprecated catalogers are being used (please remove them from your configuration)") + } + finalPkgTasks := finalTaskGroups[0] finalFileTasks := finalTaskGroups[1] @@ -309,6 +313,18 @@ func (c *CreateSBOMConfig) selectTasks(src source.Description) ([]task.Task, []t return finalPkgTasks, finalFileTasks, &selection, nil } +func deprecatedTasks(taskGroups [][]task.Task) []string { + // we want to identify any deprecated catalogers that are being used but default selections will always additionally select `file` + // catalogers. For this reason, we must explicitly remove `file` catalogers in the selection request. This means if we + // deprecate a file cataloger we will need special processing. + _, selection, err := task.SelectInGroups(taskGroups, cataloging.SelectionRequest{DefaultNamesOrTags: []string{pkgcataloging.DeprecatedTag}, RemoveNamesOrTags: []string{filecataloging.FileTag}}) + if err != nil { + // ignore the error, as it is not critical + return nil + } + return selection.Result.List() +} + func logTaskNames(tasks []task.Task, kind string) { // log as tree output (like tree command) log.Debugf("selected %d %s tasks", len(tasks), kind) diff --git a/syft/internal/packagemetadata/discover_type_names.go b/syft/internal/packagemetadata/discover_type_names.go index 03f8a4cc6..647753eb0 100644 --- a/syft/internal/packagemetadata/discover_type_names.go +++ b/syft/internal/packagemetadata/discover_type_names.go @@ -22,6 +22,12 @@ var knownNonMetadataTypeNames = strset.New( "LicenseSet", ) +// these are names that would be removed due to common convention (e.g. used within another metadata type) but are +// known to be metadata types themselves. Adding to this list will prevent the removal of the type from the schema. +var knownMetadaTypeNames = strset.New( + "DotnetPortableExecutableEntry", +) + func DiscoverTypeNames() ([]string, error) { root, err := RepoRoot() if err != nil { @@ -66,7 +72,8 @@ func findMetadataDefinitionNames(paths ...string) ([]string, error) { } // any definition that is used within another struct should not be considered a top-level metadata definition - names.Remove(usedNames.List()...) + removeNames := strset.Difference(usedNames, knownMetadaTypeNames) + names.Remove(removeNames.List()...) // remove known exceptions, that is, types exported in the pkg Package that are not used // in a metadata type but are not metadata types themselves. diff --git a/syft/internal/packagemetadata/generate/main.go b/syft/internal/packagemetadata/generate/main.go index 07db5ea80..e9dc1791c 100644 --- a/syft/internal/packagemetadata/generate/main.go +++ b/syft/internal/packagemetadata/generate/main.go @@ -23,6 +23,7 @@ func main() { panic(fmt.Errorf("unable to get all metadata type names: %w", err)) } + // useful for debugging... // for _, typeName := range typeNames { // fmt.Printf(" - %s\n", typeName) //} diff --git a/syft/pkg/cataloger/dotnet/binary_cataloger.go b/syft/pkg/cataloger/dotnet/binary_cataloger.go new file mode 100644 index 000000000..8d51af915 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/binary_cataloger.go @@ -0,0 +1,39 @@ +package dotnet + +import ( + "context" + + "github.com/anchore/syft/internal/unknown" + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" +) + +// binary cataloger will search for .dll and .exe files and create packages based off of the version resources embedded +// as a resource directory within the executable. If there is no evidence of a .NET runtime (a CLR header) then no +// package will be created. +// Deprecated: use depsBinaryCataloger instead which combines the PE and deps.json data which yields more accurate results (will be removed in syft v2.0). +type binaryCataloger struct { +} + +func (c binaryCataloger) Name() string { + return "dotnet-portable-executable-cataloger" +} + +func (c binaryCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { + var unknowns error + peFiles, ldpeUnknownErr, err := findPEFiles(resolver) + if err != nil { + return nil, nil, err + } + if ldpeUnknownErr != nil { + unknowns = unknown.Join(unknowns, ldpeUnknownErr) + } + + var pkgs []pkg.Package + for _, pe := range peFiles { + pkgs = append(pkgs, newDotnetBinaryPackage(pe.VersionResources, pe.Location)) + } + + return pkgs, nil, unknowns +} diff --git a/syft/pkg/cataloger/dotnet/cataloger.go b/syft/pkg/cataloger/dotnet/cataloger.go index 569fcb49d..384469db3 100644 --- a/syft/pkg/cataloger/dotnet/cataloger.go +++ b/syft/pkg/cataloger/dotnet/cataloger.go @@ -1,6 +1,3 @@ -/* -Package dotnet provides a concrete Cataloger implementation relating to packages within the C#/.NET language/runtime ecosystem. -*/ package dotnet import ( @@ -8,19 +5,27 @@ import ( "github.com/anchore/syft/syft/pkg/cataloger/generic" ) -// NewDotnetDepsCataloger returns a new Dotnet cataloger object base on deps json files. +// NewDotnetDepsBinaryCataloger returns a cataloger based on PE and deps.json file contents. +func NewDotnetDepsBinaryCataloger(config CatalogerConfig) pkg.Cataloger { + return &depsBinaryCataloger{ + config: config, + } +} + +// NewDotnetDepsCataloger returns a cataloger based on deps.json file contents. +// Deprecated: use NewDotnetDepsBinaryCataloger instead which combines the PE and deps.json data which yields more accurate results (will be removed in syft v2.0). func NewDotnetDepsCataloger() pkg.Cataloger { - return generic.NewCataloger("dotnet-deps-cataloger"). - WithParserByGlobs(parseDotnetDeps, "**/*.deps.json") + return &depsCataloger{} } -// NewDotnetPortableExecutableCataloger returns a new Dotnet cataloger object base on portable executable files. -func NewDotnetPortableExecutableCataloger(cfg CatalogerConfig) pkg.Cataloger { - p := dotnetPortableExecutableParser{cfg: cfg} - return generic.NewCataloger("dotnet-portable-executable-cataloger"). - WithParserByGlobs(p.parseDotnetPortableExecutable, "**/*.dll", "**/*.exe") +// NewDotnetPortableExecutableCataloger returns a cataloger based on PE file contents. +// Deprecated: use NewDotnetDepsBinaryCataloger instead which combines the PE and deps.json data which yields more accurate results (will be removed in syft v2.0). +func NewDotnetPortableExecutableCataloger() pkg.Cataloger { + return &binaryCataloger{} } +// NewDotnetPackagesLockCataloger returns a cataloger based on packages.lock.json files. func NewDotnetPackagesLockCataloger() pkg.Cataloger { - return generic.NewCataloger("dotnet-packages-lock-cataloger").WithParserByGlobs(parseDotnetPackagesLock, "**/packages.lock.json") + return generic.NewCataloger("dotnet-packages-lock-cataloger"). + WithParserByGlobs(parseDotnetPackagesLock, "**/packages.lock.json") } diff --git a/syft/pkg/cataloger/dotnet/cataloger_test.go b/syft/pkg/cataloger/dotnet/cataloger_test.go index d2b3c5f89..c1e8fdf89 100644 --- a/syft/pkg/cataloger/dotnet/cataloger_test.go +++ b/syft/pkg/cataloger/dotnet/cataloger_test.go @@ -1,8 +1,13 @@ package dotnet import ( + "strings" "testing" + "github.com/stretchr/testify/assert" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" ) @@ -25,12 +30,22 @@ func TestCataloger_Globs(t *testing.T) { { name: "obtain portable executable files", fixture: "test-fixtures/glob-paths", - cataloger: NewDotnetPortableExecutableCataloger(DefaultCatalogerConfig()), + cataloger: NewDotnetPortableExecutableCataloger(), expected: []string{ "src/something.dll", "src/something.exe", }, }, + { + name: "obtain combined files", + fixture: "test-fixtures/glob-paths", + cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), + expected: []string{ + "src/something.deps.json", + "src/something.dll", + "src/something.exe", + }, + }, } for _, test := range tests { @@ -42,3 +57,1566 @@ func TestCataloger_Globs(t *testing.T) { }) } } + +func TestCataloger(t *testing.T) { + + net8AppExpectedDepPkgsWithoutUnpairedDlls := []string{ + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.deps.json)", + "Newtonsoft.Json @ 13.0.3 (/app/dotnetapp.deps.json)", + "dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + } + // app packages (from deps.json) + net8AppExpectedDepPkgs := []string{ + "Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + } + net8AppExpectedDepPkgs = append(net8AppExpectedDepPkgs, net8AppExpectedDepPkgsWithoutUnpairedDlls...) + + // app binaries (always dlls) + net8AppBinaryOnlyPkgs := []string{ + "Humanizer (net6.0) @ 2.14.1.48190 (/app/Humanizer.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/af/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/ar/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/az/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/bg/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/bn-BD/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/cs/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/da/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/de/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/el/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/es/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/fa/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/fi-FI/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/fr-BE/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/fr/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/he/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/hr/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/hu/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/hy/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/id/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/is/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/it/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/ja/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/ku/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/lv/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/nb-NO/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/nb/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/nl/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/pl/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/pt/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/ro/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/ru/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/sk/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/sl/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/sr-Latn/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/sr/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/sv/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/tr/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/uk/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/uz-Cyrl-UZ/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/uz-Latn-UZ/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/vi/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/zh-CN/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/zh-Hans/Humanizer.resources.dll)", + "Humanizer (net6.0) @ 2.14.1.48190 (/app/zh-Hant/Humanizer.resources.dll)", + "Humanizer (netstandard2.0) @ 2.14.1.48190 (/app/ko-KR/Humanizer.resources.dll)", + "Humanizer (netstandard2.0) @ 2.14.1.48190 (/app/ms-MY/Humanizer.resources.dll)", + "Humanizer (netstandard2.0) @ 2.14.1.48190 (/app/mt/Humanizer.resources.dll)", + "Humanizer (netstandard2.0) @ 2.14.1.48190 (/app/th-TH/Humanizer.resources.dll)", + "Json.NET @ 13.0.3.27908 (/app/Newtonsoft.Json.dll)", + "dotnetapp @ 1.0.0.0 (/app/dotnetapp.dll)", + } + + // app relationships (from deps.json) + net8AppDepOnlyRelationshipsWithoutHumanizer := []string{ + "Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.deps.json)", + "Newtonsoft.Json @ 13.0.3 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + } + + var net8AppDepOnlyRelationships []string + humanizerToAppDepsRelationship := "Humanizer @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)" + net8AppDepOnlyRelationships = append(net8AppDepOnlyRelationships, net8AppDepOnlyRelationshipsWithoutHumanizer...) + net8AppDepOnlyRelationships = append(net8AppDepOnlyRelationships, humanizerToAppDepsRelationship) + + var net8AppExpectedDepRelationships []string + net8AppExpectedDepRelationships = append(net8AppExpectedDepRelationships, net8AppDepOnlyRelationships...) + + var net8AppExpectedDepSelfContainedPkgs []string + net8AppExpectedDepSelfContainedPkgs = append(net8AppExpectedDepSelfContainedPkgs, net8AppExpectedDepPkgsWithoutUnpairedDlls...) + net8AppExpectedDepSelfContainedPkgs = append(net8AppExpectedDepSelfContainedPkgs, + // add the CLR runtime packages... + ".NET Runtime @ 8,0,1425,11118 (/app/coreclr.dll)", + "runtimepack.Microsoft.NETCore.App.Runtime.win-x64 @ 8.0.14 (/app/dotnetapp.deps.json)", + ) + + var net8AppExpectedDepsSelfContainedPkgs []string + net8AppExpectedDepsSelfContainedPkgs = append(net8AppExpectedDepsSelfContainedPkgs, net8AppExpectedDepPkgs...) + net8AppExpectedDepsSelfContainedPkgs = append(net8AppExpectedDepsSelfContainedPkgs, + // add the CLR runtime packages... + "runtimepack.Microsoft.NETCore.App.Runtime.win-x64 @ 8.0.14 (/app/dotnetapp.deps.json)", + ) + + var net8AppExpectedDepSelfContainedRelationships []string + net8AppExpectedDepSelfContainedRelationships = append(net8AppExpectedDepSelfContainedRelationships, net8AppDepOnlyRelationshipsWithoutHumanizer...) + net8AppExpectedDepSelfContainedRelationships = append(net8AppExpectedDepSelfContainedRelationships, + // add the CLR runtime relationships... + "runtimepack.Microsoft.NETCore.App.Runtime.win-x64 @ 8.0.14 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + ) + + var net8AppExpectedBinarySelfContainedRelationships []string + net8AppExpectedBinarySelfContainedRelationships = append(net8AppExpectedBinarySelfContainedRelationships, net8AppBinaryOnlyPkgs...) + net8AppExpectedBinarySelfContainedRelationships = append(net8AppExpectedBinarySelfContainedRelationships, + // include the runtime libs... + "Microsoft.CSharp @ 8.0.1425.11118 (/app/Microsoft.CSharp.dll)", + "Microsoft.VisualBasic @ 8.0.1425.11118 (/app/Microsoft.VisualBasic.dll)", + "Microsoft.VisualBasic.Core @ 13.0.1425.11118 (/app/Microsoft.VisualBasic.Core.dll)", + "Microsoft.Win32.Primitives @ 8.0.1425.11118 (/app/Microsoft.Win32.Primitives.dll)", + "Microsoft.Win32.Registry @ 8.0.1425.11118 (/app/Microsoft.Win32.Registry.dll)", + "System @ 8.0.1425.11118 (/app/System.dll)", + "System.AppContext @ 8.0.1425.11118 (/app/System.AppContext.dll)", + "System.Buffers @ 8.0.1425.11118 (/app/System.Buffers.dll)", + "System.Collections @ 8.0.1425.11118 (/app/System.Collections.dll)", + "System.Collections.Concurrent @ 8.0.1425.11118 (/app/System.Collections.Concurrent.dll)", + "System.Collections.Immutable @ 8.0.1425.11118 (/app/System.Collections.Immutable.dll)", + "System.Collections.NonGeneric @ 8.0.1425.11118 (/app/System.Collections.NonGeneric.dll)", + "System.Collections.Specialized @ 8.0.1425.11118 (/app/System.Collections.Specialized.dll)", + "System.ComponentModel @ 8.0.1425.11118 (/app/System.ComponentModel.dll)", + "System.ComponentModel.Annotations @ 8.0.1425.11118 (/app/System.ComponentModel.Annotations.dll)", + "System.ComponentModel.DataAnnotations @ 8.0.1425.11118 (/app/System.ComponentModel.DataAnnotations.dll)", + "System.ComponentModel.EventBasedAsync @ 8.0.1425.11118 (/app/System.ComponentModel.EventBasedAsync.dll)", + "System.ComponentModel.Primitives @ 8.0.1425.11118 (/app/System.ComponentModel.Primitives.dll)", + "System.ComponentModel.TypeConverter @ 8.0.1425.11118 (/app/System.ComponentModel.TypeConverter.dll)", + "System.Configuration @ 8.0.1425.11118 (/app/System.Configuration.dll)", + "System.Console @ 8.0.1425.11118 (/app/System.Console.dll)", + "System.Core @ 8.0.1425.11118 (/app/System.Core.dll)", + "System.Data @ 8.0.1425.11118 (/app/System.Data.dll)", + "System.Data.Common @ 8.0.1425.11118 (/app/System.Data.Common.dll)", + "System.Data.DataSetExtensions @ 8.0.1425.11118 (/app/System.Data.DataSetExtensions.dll)", + "System.Diagnostics.Contracts @ 8.0.1425.11118 (/app/System.Diagnostics.Contracts.dll)", + "System.Diagnostics.Debug @ 8.0.1425.11118 (/app/System.Diagnostics.Debug.dll)", + "System.Diagnostics.DiagnosticSource @ 8.0.1425.11118 (/app/System.Diagnostics.DiagnosticSource.dll)", + "System.Diagnostics.FileVersionInfo @ 8.0.1425.11118 (/app/System.Diagnostics.FileVersionInfo.dll)", + "System.Diagnostics.Process @ 8.0.1425.11118 (/app/System.Diagnostics.Process.dll)", + "System.Diagnostics.StackTrace @ 8.0.1425.11118 (/app/System.Diagnostics.StackTrace.dll)", + "System.Diagnostics.TextWriterTraceListener @ 8.0.1425.11118 (/app/System.Diagnostics.TextWriterTraceListener.dll)", + "System.Diagnostics.Tools @ 8.0.1425.11118 (/app/System.Diagnostics.Tools.dll)", + "System.Diagnostics.TraceSource @ 8.0.1425.11118 (/app/System.Diagnostics.TraceSource.dll)", + "System.Diagnostics.Tracing @ 8.0.1425.11118 (/app/System.Diagnostics.Tracing.dll)", + "System.Drawing @ 8.0.1425.11118 (/app/System.Drawing.dll)", + "System.Drawing.Primitives @ 8.0.1425.11118 (/app/System.Drawing.Primitives.dll)", + "System.Dynamic.Runtime @ 8.0.1425.11118 (/app/System.Dynamic.Runtime.dll)", + "System.Formats.Asn1 @ 8.0.1425.11118 (/app/System.Formats.Asn1.dll)", + "System.Formats.Tar @ 8.0.1425.11118 (/app/System.Formats.Tar.dll)", + "System.Globalization @ 8.0.1425.11118 (/app/System.Globalization.dll)", + "System.Globalization.Calendars @ 8.0.1425.11118 (/app/System.Globalization.Calendars.dll)", + "System.Globalization.Extensions @ 8.0.1425.11118 (/app/System.Globalization.Extensions.dll)", + "System.IO @ 8.0.1425.11118 (/app/System.IO.dll)", + "System.IO.Compression @ 8.0.1425.11118 (/app/System.IO.Compression.dll)", + "System.IO.Compression.Brotli @ 8.0.1425.11118 (/app/System.IO.Compression.Brotli.dll)", + "System.IO.Compression.FileSystem @ 8.0.1425.11118 (/app/System.IO.Compression.FileSystem.dll)", + "System.IO.Compression.ZipFile @ 8.0.1425.11118 (/app/System.IO.Compression.ZipFile.dll)", + "System.IO.FileSystem @ 8.0.1425.11118 (/app/System.IO.FileSystem.dll)", + "System.IO.FileSystem.AccessControl @ 8.0.1425.11118 (/app/System.IO.FileSystem.AccessControl.dll)", + "System.IO.FileSystem.DriveInfo @ 8.0.1425.11118 (/app/System.IO.FileSystem.DriveInfo.dll)", + "System.IO.FileSystem.Primitives @ 8.0.1425.11118 (/app/System.IO.FileSystem.Primitives.dll)", + "System.IO.FileSystem.Watcher @ 8.0.1425.11118 (/app/System.IO.FileSystem.Watcher.dll)", + "System.IO.IsolatedStorage @ 8.0.1425.11118 (/app/System.IO.IsolatedStorage.dll)", + "System.IO.MemoryMappedFiles @ 8.0.1425.11118 (/app/System.IO.MemoryMappedFiles.dll)", + "System.IO.Pipes @ 8.0.1425.11118 (/app/System.IO.Pipes.dll)", + "System.IO.Pipes.AccessControl @ 8.0.1425.11118 (/app/System.IO.Pipes.AccessControl.dll)", + "System.IO.UnmanagedMemoryStream @ 8.0.1425.11118 (/app/System.IO.UnmanagedMemoryStream.dll)", + "System.Linq @ 8.0.1425.11118 (/app/System.Linq.dll)", + "System.Linq.Expressions @ 8.0.1425.11118 (/app/System.Linq.Expressions.dll)", + "System.Linq.Parallel @ 8.0.1425.11118 (/app/System.Linq.Parallel.dll)", + "System.Linq.Queryable @ 8.0.1425.11118 (/app/System.Linq.Queryable.dll)", + "System.Memory @ 8.0.1425.11118 (/app/System.Memory.dll)", + "System.Net @ 8.0.1425.11118 (/app/System.Net.dll)", + "System.Net.Http @ 8.0.1425.11118 (/app/System.Net.Http.dll)", + "System.Net.Http.Json @ 8.0.1425.11118 (/app/System.Net.Http.Json.dll)", + "System.Net.HttpListener @ 8.0.1425.11118 (/app/System.Net.HttpListener.dll)", + "System.Net.Mail @ 8.0.1425.11118 (/app/System.Net.Mail.dll)", + "System.Net.NameResolution @ 8.0.1425.11118 (/app/System.Net.NameResolution.dll)", + "System.Net.NetworkInformation @ 8.0.1425.11118 (/app/System.Net.NetworkInformation.dll)", + "System.Net.Ping @ 8.0.1425.11118 (/app/System.Net.Ping.dll)", + "System.Net.Primitives @ 8.0.1425.11118 (/app/System.Net.Primitives.dll)", + "System.Net.Quic @ 8.0.1425.11118 (/app/System.Net.Quic.dll)", + "System.Net.Requests @ 8.0.1425.11118 (/app/System.Net.Requests.dll)", + "System.Net.Security @ 8.0.1425.11118 (/app/System.Net.Security.dll)", + "System.Net.ServicePoint @ 8.0.1425.11118 (/app/System.Net.ServicePoint.dll)", + "System.Net.Sockets @ 8.0.1425.11118 (/app/System.Net.Sockets.dll)", + "System.Net.WebClient @ 8.0.1425.11118 (/app/System.Net.WebClient.dll)", + "System.Net.WebHeaderCollection @ 8.0.1425.11118 (/app/System.Net.WebHeaderCollection.dll)", + "System.Net.WebProxy @ 8.0.1425.11118 (/app/System.Net.WebProxy.dll)", + "System.Net.WebSockets @ 8.0.1425.11118 (/app/System.Net.WebSockets.dll)", + "System.Net.WebSockets.Client @ 8.0.1425.11118 (/app/System.Net.WebSockets.Client.dll)", + "System.Numerics @ 8.0.1425.11118 (/app/System.Numerics.dll)", + "System.Numerics.Vectors @ 8.0.1425.11118 (/app/System.Numerics.Vectors.dll)", + "System.ObjectModel @ 8.0.1425.11118 (/app/System.ObjectModel.dll)", + "System.Private.CoreLib @ 8.0.1425.11118 (/app/System.Private.CoreLib.dll)", + "System.Private.DataContractSerialization @ 8.0.1425.11118 (/app/System.Private.DataContractSerialization.dll)", + "System.Private.Uri @ 8.0.1425.11118 (/app/System.Private.Uri.dll)", + "System.Private.Xml @ 8.0.1425.11118 (/app/System.Private.Xml.dll)", + "System.Private.Xml.Linq @ 8.0.1425.11118 (/app/System.Private.Xml.Linq.dll)", + "System.Reflection @ 8.0.1425.11118 (/app/System.Reflection.dll)", + "System.Reflection.DispatchProxy @ 8.0.1425.11118 (/app/System.Reflection.DispatchProxy.dll)", + "System.Reflection.Emit @ 8.0.1425.11118 (/app/System.Reflection.Emit.dll)", + "System.Reflection.Emit.ILGeneration @ 8.0.1425.11118 (/app/System.Reflection.Emit.ILGeneration.dll)", + "System.Reflection.Emit.Lightweight @ 8.0.1425.11118 (/app/System.Reflection.Emit.Lightweight.dll)", + "System.Reflection.Extensions @ 8.0.1425.11118 (/app/System.Reflection.Extensions.dll)", + "System.Reflection.Metadata @ 8.0.1425.11118 (/app/System.Reflection.Metadata.dll)", + "System.Reflection.Primitives @ 8.0.1425.11118 (/app/System.Reflection.Primitives.dll)", + "System.Reflection.TypeExtensions @ 8.0.1425.11118 (/app/System.Reflection.TypeExtensions.dll)", + "System.Resources.Reader @ 8.0.1425.11118 (/app/System.Resources.Reader.dll)", + "System.Resources.ResourceManager @ 8.0.1425.11118 (/app/System.Resources.ResourceManager.dll)", + "System.Resources.Writer @ 8.0.1425.11118 (/app/System.Resources.Writer.dll)", + "System.Runtime @ 8.0.1425.11118 (/app/System.Runtime.dll)", + "System.Runtime.CompilerServices.Unsafe @ 8.0.1425.11118 (/app/System.Runtime.CompilerServices.Unsafe.dll)", + "System.Runtime.CompilerServices.VisualC @ 8.0.1425.11118 (/app/System.Runtime.CompilerServices.VisualC.dll)", + "System.Runtime.Extensions @ 8.0.1425.11118 (/app/System.Runtime.Extensions.dll)", + "System.Runtime.Handles @ 8.0.1425.11118 (/app/System.Runtime.Handles.dll)", + "System.Runtime.InteropServices @ 8.0.1425.11118 (/app/System.Runtime.InteropServices.dll)", + "System.Runtime.InteropServices.JavaScript @ 8.0.1425.11118 (/app/System.Runtime.InteropServices.JavaScript.dll)", + "System.Runtime.InteropServices.RuntimeInformation @ 8.0.1425.11118 (/app/System.Runtime.InteropServices.RuntimeInformation.dll)", + "System.Runtime.Intrinsics @ 8.0.1425.11118 (/app/System.Runtime.Intrinsics.dll)", + "System.Runtime.Loader @ 8.0.1425.11118 (/app/System.Runtime.Loader.dll)", + "System.Runtime.Numerics @ 8.0.1425.11118 (/app/System.Runtime.Numerics.dll)", + "System.Runtime.Serialization @ 8.0.1425.11118 (/app/System.Runtime.Serialization.dll)", + "System.Runtime.Serialization.Formatters @ 8.0.1425.11118 (/app/System.Runtime.Serialization.Formatters.dll)", + "System.Runtime.Serialization.Json @ 8.0.1425.11118 (/app/System.Runtime.Serialization.Json.dll)", + "System.Runtime.Serialization.Primitives @ 8.0.1425.11118 (/app/System.Runtime.Serialization.Primitives.dll)", + "System.Runtime.Serialization.Xml @ 8.0.1425.11118 (/app/System.Runtime.Serialization.Xml.dll)", + "System.Security @ 8.0.1425.11118 (/app/System.Security.dll)", + "System.Security.AccessControl @ 8.0.1425.11118 (/app/System.Security.AccessControl.dll)", + "System.Security.Claims @ 8.0.1425.11118 (/app/System.Security.Claims.dll)", + "System.Security.Cryptography @ 8.0.1425.11118 (/app/System.Security.Cryptography.dll)", + "System.Security.Cryptography.Algorithms @ 8.0.1425.11118 (/app/System.Security.Cryptography.Algorithms.dll)", + "System.Security.Cryptography.Cng @ 8.0.1425.11118 (/app/System.Security.Cryptography.Cng.dll)", + "System.Security.Cryptography.Csp @ 8.0.1425.11118 (/app/System.Security.Cryptography.Csp.dll)", + "System.Security.Cryptography.Encoding @ 8.0.1425.11118 (/app/System.Security.Cryptography.Encoding.dll)", + "System.Security.Cryptography.OpenSsl @ 8.0.1425.11118 (/app/System.Security.Cryptography.OpenSsl.dll)", + "System.Security.Cryptography.Primitives @ 8.0.1425.11118 (/app/System.Security.Cryptography.Primitives.dll)", + "System.Security.Cryptography.X509Certificates @ 8.0.1425.11118 (/app/System.Security.Cryptography.X509Certificates.dll)", + "System.Security.Principal @ 8.0.1425.11118 (/app/System.Security.Principal.dll)", + "System.Security.Principal.Windows @ 8.0.1425.11118 (/app/System.Security.Principal.Windows.dll)", + "System.Security.SecureString @ 8.0.1425.11118 (/app/System.Security.SecureString.dll)", + "System.ServiceModel.Web @ 8.0.1425.11118 (/app/System.ServiceModel.Web.dll)", + "System.ServiceProcess @ 8.0.1425.11118 (/app/System.ServiceProcess.dll)", + "System.Text.Encoding @ 8.0.1425.11118 (/app/System.Text.Encoding.dll)", + "System.Text.Encoding.CodePages @ 8.0.1425.11118 (/app/System.Text.Encoding.CodePages.dll)", + "System.Text.Encoding.Extensions @ 8.0.1425.11118 (/app/System.Text.Encoding.Extensions.dll)", + "System.Text.Encodings.Web @ 8.0.1425.11118 (/app/System.Text.Encodings.Web.dll)", + "System.Text.Json @ 8.0.1425.11118 (/app/System.Text.Json.dll)", + "System.Text.RegularExpressions @ 8.0.1425.11118 (/app/System.Text.RegularExpressions.dll)", + "System.Threading @ 8.0.1425.11118 (/app/System.Threading.dll)", + "System.Threading.Channels @ 8.0.1425.11118 (/app/System.Threading.Channels.dll)", + "System.Threading.Overlapped @ 8.0.1425.11118 (/app/System.Threading.Overlapped.dll)", + "System.Threading.Tasks @ 8.0.1425.11118 (/app/System.Threading.Tasks.dll)", + "System.Threading.Tasks.Dataflow @ 8.0.1425.11118 (/app/System.Threading.Tasks.Dataflow.dll)", + "System.Threading.Tasks.Extensions @ 8.0.1425.11118 (/app/System.Threading.Tasks.Extensions.dll)", + "System.Threading.Tasks.Parallel @ 8.0.1425.11118 (/app/System.Threading.Tasks.Parallel.dll)", + "System.Threading.Thread @ 8.0.1425.11118 (/app/System.Threading.Thread.dll)", + "System.Threading.ThreadPool @ 8.0.1425.11118 (/app/System.Threading.ThreadPool.dll)", + "System.Threading.Timer @ 8.0.1425.11118 (/app/System.Threading.Timer.dll)", + "System.Transactions @ 8.0.1425.11118 (/app/System.Transactions.dll)", + "System.Transactions.Local @ 8.0.1425.11118 (/app/System.Transactions.Local.dll)", + "System.ValueTuple @ 8.0.1425.11118 (/app/System.ValueTuple.dll)", + "System.Web @ 8.0.1425.11118 (/app/System.Web.dll)", + "System.Web.HttpUtility @ 8.0.1425.11118 (/app/System.Web.HttpUtility.dll)", + "System.Windows @ 8.0.1425.11118 (/app/System.Windows.dll)", + "System.Xml @ 8.0.1425.11118 (/app/System.Xml.dll)", + "System.Xml.Linq @ 8.0.1425.11118 (/app/System.Xml.Linq.dll)", + "System.Xml.ReaderWriter @ 8.0.1425.11118 (/app/System.Xml.ReaderWriter.dll)", + "System.Xml.Serialization @ 8.0.1425.11118 (/app/System.Xml.Serialization.dll)", + "System.Xml.XDocument @ 8.0.1425.11118 (/app/System.Xml.XDocument.dll)", + "System.Xml.XPath @ 8.0.1425.11118 (/app/System.Xml.XPath.dll)", + "System.Xml.XPath.XDocument @ 8.0.1425.11118 (/app/System.Xml.XPath.XDocument.dll)", + "System.Xml.XmlDocument @ 8.0.1425.11118 (/app/System.Xml.XmlDocument.dll)", + "System.Xml.XmlSerializer @ 8.0.1425.11118 (/app/System.Xml.XmlSerializer.dll)", + "WindowsBase @ 8.0.1425.11118 (/app/WindowsBase.dll)", + "dotnetapp @ 1.0.0.0 (/app/dotnetapp.dll)", + "mscorlib @ 8.0.1425.11118 (/app/mscorlib.dll)", + "netstandard @ 8.0.1425.11118 (/app/netstandard.dll)", + ) + + var net8AppExpectedBinarySelfContainedPkgs []string + net8AppExpectedBinarySelfContainedPkgs = append(net8AppExpectedBinarySelfContainedPkgs, net8AppBinaryOnlyPkgs...) + net8AppExpectedBinarySelfContainedPkgs = append(net8AppExpectedBinarySelfContainedPkgs, + // include the runtime... + ".NET Runtime @ 8,0,1425,11118 (/app/coreclr.dll)", + "Microsoft.CSharp @ 8.0.1425.11118 (/app/Microsoft.CSharp.dll)", + "Microsoft.VisualBasic @ 8.0.1425.11118 (/app/Microsoft.VisualBasic.dll)", + "Microsoft.VisualBasic.Core @ 13.0.1425.11118 (/app/Microsoft.VisualBasic.Core.dll)", + "Microsoft.Win32.Primitives @ 8.0.1425.11118 (/app/Microsoft.Win32.Primitives.dll)", + "Microsoft.Win32.Registry @ 8.0.1425.11118 (/app/Microsoft.Win32.Registry.dll)", + "System @ 8.0.1425.11118 (/app/System.dll)", + "System.AppContext @ 8.0.1425.11118 (/app/System.AppContext.dll)", + "System.Buffers @ 8.0.1425.11118 (/app/System.Buffers.dll)", + "System.Collections @ 8.0.1425.11118 (/app/System.Collections.dll)", + "System.Collections.Concurrent @ 8.0.1425.11118 (/app/System.Collections.Concurrent.dll)", + "System.Collections.Immutable @ 8.0.1425.11118 (/app/System.Collections.Immutable.dll)", + "System.Collections.NonGeneric @ 8.0.1425.11118 (/app/System.Collections.NonGeneric.dll)", + "System.Collections.Specialized @ 8.0.1425.11118 (/app/System.Collections.Specialized.dll)", + "System.ComponentModel @ 8.0.1425.11118 (/app/System.ComponentModel.dll)", + "System.ComponentModel.Annotations @ 8.0.1425.11118 (/app/System.ComponentModel.Annotations.dll)", + "System.ComponentModel.DataAnnotations @ 8.0.1425.11118 (/app/System.ComponentModel.DataAnnotations.dll)", + "System.ComponentModel.EventBasedAsync @ 8.0.1425.11118 (/app/System.ComponentModel.EventBasedAsync.dll)", + "System.ComponentModel.Primitives @ 8.0.1425.11118 (/app/System.ComponentModel.Primitives.dll)", + "System.ComponentModel.TypeConverter @ 8.0.1425.11118 (/app/System.ComponentModel.TypeConverter.dll)", + "System.Configuration @ 8.0.1425.11118 (/app/System.Configuration.dll)", + "System.Console @ 8.0.1425.11118 (/app/System.Console.dll)", + "System.Core @ 8.0.1425.11118 (/app/System.Core.dll)", + "System.Data @ 8.0.1425.11118 (/app/System.Data.dll)", + "System.Data.Common @ 8.0.1425.11118 (/app/System.Data.Common.dll)", + "System.Data.DataSetExtensions @ 8.0.1425.11118 (/app/System.Data.DataSetExtensions.dll)", + "System.Diagnostics.Contracts @ 8.0.1425.11118 (/app/System.Diagnostics.Contracts.dll)", + "System.Diagnostics.Debug @ 8.0.1425.11118 (/app/System.Diagnostics.Debug.dll)", + "System.Diagnostics.DiagnosticSource @ 8.0.1425.11118 (/app/System.Diagnostics.DiagnosticSource.dll)", + "System.Diagnostics.FileVersionInfo @ 8.0.1425.11118 (/app/System.Diagnostics.FileVersionInfo.dll)", + "System.Diagnostics.Process @ 8.0.1425.11118 (/app/System.Diagnostics.Process.dll)", + "System.Diagnostics.StackTrace @ 8.0.1425.11118 (/app/System.Diagnostics.StackTrace.dll)", + "System.Diagnostics.TextWriterTraceListener @ 8.0.1425.11118 (/app/System.Diagnostics.TextWriterTraceListener.dll)", + "System.Diagnostics.Tools @ 8.0.1425.11118 (/app/System.Diagnostics.Tools.dll)", + "System.Diagnostics.TraceSource @ 8.0.1425.11118 (/app/System.Diagnostics.TraceSource.dll)", + "System.Diagnostics.Tracing @ 8.0.1425.11118 (/app/System.Diagnostics.Tracing.dll)", + "System.Drawing @ 8.0.1425.11118 (/app/System.Drawing.dll)", + "System.Drawing.Primitives @ 8.0.1425.11118 (/app/System.Drawing.Primitives.dll)", + "System.Dynamic.Runtime @ 8.0.1425.11118 (/app/System.Dynamic.Runtime.dll)", + "System.Formats.Asn1 @ 8.0.1425.11118 (/app/System.Formats.Asn1.dll)", + "System.Formats.Tar @ 8.0.1425.11118 (/app/System.Formats.Tar.dll)", + "System.Globalization @ 8.0.1425.11118 (/app/System.Globalization.dll)", + "System.Globalization.Calendars @ 8.0.1425.11118 (/app/System.Globalization.Calendars.dll)", + "System.Globalization.Extensions @ 8.0.1425.11118 (/app/System.Globalization.Extensions.dll)", + "System.IO @ 8.0.1425.11118 (/app/System.IO.dll)", + "System.IO.Compression @ 8.0.1425.11118 (/app/System.IO.Compression.dll)", + "System.IO.Compression.Brotli @ 8.0.1425.11118 (/app/System.IO.Compression.Brotli.dll)", + "System.IO.Compression.FileSystem @ 8.0.1425.11118 (/app/System.IO.Compression.FileSystem.dll)", + "System.IO.Compression.ZipFile @ 8.0.1425.11118 (/app/System.IO.Compression.ZipFile.dll)", + "System.IO.FileSystem @ 8.0.1425.11118 (/app/System.IO.FileSystem.dll)", + "System.IO.FileSystem.AccessControl @ 8.0.1425.11118 (/app/System.IO.FileSystem.AccessControl.dll)", + "System.IO.FileSystem.DriveInfo @ 8.0.1425.11118 (/app/System.IO.FileSystem.DriveInfo.dll)", + "System.IO.FileSystem.Primitives @ 8.0.1425.11118 (/app/System.IO.FileSystem.Primitives.dll)", + "System.IO.FileSystem.Watcher @ 8.0.1425.11118 (/app/System.IO.FileSystem.Watcher.dll)", + "System.IO.IsolatedStorage @ 8.0.1425.11118 (/app/System.IO.IsolatedStorage.dll)", + "System.IO.MemoryMappedFiles @ 8.0.1425.11118 (/app/System.IO.MemoryMappedFiles.dll)", + "System.IO.Pipes @ 8.0.1425.11118 (/app/System.IO.Pipes.dll)", + "System.IO.Pipes.AccessControl @ 8.0.1425.11118 (/app/System.IO.Pipes.AccessControl.dll)", + "System.IO.UnmanagedMemoryStream @ 8.0.1425.11118 (/app/System.IO.UnmanagedMemoryStream.dll)", + "System.Linq @ 8.0.1425.11118 (/app/System.Linq.dll)", + "System.Linq.Expressions @ 8.0.1425.11118 (/app/System.Linq.Expressions.dll)", + "System.Linq.Parallel @ 8.0.1425.11118 (/app/System.Linq.Parallel.dll)", + "System.Linq.Queryable @ 8.0.1425.11118 (/app/System.Linq.Queryable.dll)", + "System.Memory @ 8.0.1425.11118 (/app/System.Memory.dll)", + "System.Net @ 8.0.1425.11118 (/app/System.Net.dll)", + "System.Net.Http @ 8.0.1425.11118 (/app/System.Net.Http.dll)", + "System.Net.Http.Json @ 8.0.1425.11118 (/app/System.Net.Http.Json.dll)", + "System.Net.HttpListener @ 8.0.1425.11118 (/app/System.Net.HttpListener.dll)", + "System.Net.Mail @ 8.0.1425.11118 (/app/System.Net.Mail.dll)", + "System.Net.NameResolution @ 8.0.1425.11118 (/app/System.Net.NameResolution.dll)", + "System.Net.NetworkInformation @ 8.0.1425.11118 (/app/System.Net.NetworkInformation.dll)", + "System.Net.Ping @ 8.0.1425.11118 (/app/System.Net.Ping.dll)", + "System.Net.Primitives @ 8.0.1425.11118 (/app/System.Net.Primitives.dll)", + "System.Net.Quic @ 8.0.1425.11118 (/app/System.Net.Quic.dll)", + "System.Net.Requests @ 8.0.1425.11118 (/app/System.Net.Requests.dll)", + "System.Net.Security @ 8.0.1425.11118 (/app/System.Net.Security.dll)", + "System.Net.ServicePoint @ 8.0.1425.11118 (/app/System.Net.ServicePoint.dll)", + "System.Net.Sockets @ 8.0.1425.11118 (/app/System.Net.Sockets.dll)", + "System.Net.WebClient @ 8.0.1425.11118 (/app/System.Net.WebClient.dll)", + "System.Net.WebHeaderCollection @ 8.0.1425.11118 (/app/System.Net.WebHeaderCollection.dll)", + "System.Net.WebProxy @ 8.0.1425.11118 (/app/System.Net.WebProxy.dll)", + "System.Net.WebSockets @ 8.0.1425.11118 (/app/System.Net.WebSockets.dll)", + "System.Net.WebSockets.Client @ 8.0.1425.11118 (/app/System.Net.WebSockets.Client.dll)", + "System.Numerics @ 8.0.1425.11118 (/app/System.Numerics.dll)", + "System.Numerics.Vectors @ 8.0.1425.11118 (/app/System.Numerics.Vectors.dll)", + "System.ObjectModel @ 8.0.1425.11118 (/app/System.ObjectModel.dll)", + "System.Private.CoreLib @ 8.0.1425.11118 (/app/System.Private.CoreLib.dll)", + "System.Private.DataContractSerialization @ 8.0.1425.11118 (/app/System.Private.DataContractSerialization.dll)", + "System.Private.Uri @ 8.0.1425.11118 (/app/System.Private.Uri.dll)", + "System.Private.Xml @ 8.0.1425.11118 (/app/System.Private.Xml.dll)", + "System.Private.Xml.Linq @ 8.0.1425.11118 (/app/System.Private.Xml.Linq.dll)", + "System.Reflection @ 8.0.1425.11118 (/app/System.Reflection.dll)", + "System.Reflection.DispatchProxy @ 8.0.1425.11118 (/app/System.Reflection.DispatchProxy.dll)", + "System.Reflection.Emit @ 8.0.1425.11118 (/app/System.Reflection.Emit.dll)", + "System.Reflection.Emit.ILGeneration @ 8.0.1425.11118 (/app/System.Reflection.Emit.ILGeneration.dll)", + "System.Reflection.Emit.Lightweight @ 8.0.1425.11118 (/app/System.Reflection.Emit.Lightweight.dll)", + "System.Reflection.Extensions @ 8.0.1425.11118 (/app/System.Reflection.Extensions.dll)", + "System.Reflection.Metadata @ 8.0.1425.11118 (/app/System.Reflection.Metadata.dll)", + "System.Reflection.Primitives @ 8.0.1425.11118 (/app/System.Reflection.Primitives.dll)", + "System.Reflection.TypeExtensions @ 8.0.1425.11118 (/app/System.Reflection.TypeExtensions.dll)", + "System.Resources.Reader @ 8.0.1425.11118 (/app/System.Resources.Reader.dll)", + "System.Resources.ResourceManager @ 8.0.1425.11118 (/app/System.Resources.ResourceManager.dll)", + "System.Resources.Writer @ 8.0.1425.11118 (/app/System.Resources.Writer.dll)", + "System.Runtime @ 8.0.1425.11118 (/app/System.Runtime.dll)", + "System.Runtime.CompilerServices.Unsafe @ 8.0.1425.11118 (/app/System.Runtime.CompilerServices.Unsafe.dll)", + "System.Runtime.CompilerServices.VisualC @ 8.0.1425.11118 (/app/System.Runtime.CompilerServices.VisualC.dll)", + "System.Runtime.Extensions @ 8.0.1425.11118 (/app/System.Runtime.Extensions.dll)", + "System.Runtime.Handles @ 8.0.1425.11118 (/app/System.Runtime.Handles.dll)", + "System.Runtime.InteropServices @ 8.0.1425.11118 (/app/System.Runtime.InteropServices.dll)", + "System.Runtime.InteropServices.JavaScript @ 8.0.1425.11118 (/app/System.Runtime.InteropServices.JavaScript.dll)", + "System.Runtime.InteropServices.RuntimeInformation @ 8.0.1425.11118 (/app/System.Runtime.InteropServices.RuntimeInformation.dll)", + "System.Runtime.Intrinsics @ 8.0.1425.11118 (/app/System.Runtime.Intrinsics.dll)", + "System.Runtime.Loader @ 8.0.1425.11118 (/app/System.Runtime.Loader.dll)", + "System.Runtime.Numerics @ 8.0.1425.11118 (/app/System.Runtime.Numerics.dll)", + "System.Runtime.Serialization @ 8.0.1425.11118 (/app/System.Runtime.Serialization.dll)", + "System.Runtime.Serialization.Formatters @ 8.0.1425.11118 (/app/System.Runtime.Serialization.Formatters.dll)", + "System.Runtime.Serialization.Json @ 8.0.1425.11118 (/app/System.Runtime.Serialization.Json.dll)", + "System.Runtime.Serialization.Primitives @ 8.0.1425.11118 (/app/System.Runtime.Serialization.Primitives.dll)", + "System.Runtime.Serialization.Xml @ 8.0.1425.11118 (/app/System.Runtime.Serialization.Xml.dll)", + "System.Security @ 8.0.1425.11118 (/app/System.Security.dll)", + "System.Security.AccessControl @ 8.0.1425.11118 (/app/System.Security.AccessControl.dll)", + "System.Security.Claims @ 8.0.1425.11118 (/app/System.Security.Claims.dll)", + "System.Security.Cryptography @ 8.0.1425.11118 (/app/System.Security.Cryptography.dll)", + "System.Security.Cryptography.Algorithms @ 8.0.1425.11118 (/app/System.Security.Cryptography.Algorithms.dll)", + "System.Security.Cryptography.Cng @ 8.0.1425.11118 (/app/System.Security.Cryptography.Cng.dll)", + "System.Security.Cryptography.Csp @ 8.0.1425.11118 (/app/System.Security.Cryptography.Csp.dll)", + "System.Security.Cryptography.Encoding @ 8.0.1425.11118 (/app/System.Security.Cryptography.Encoding.dll)", + "System.Security.Cryptography.OpenSsl @ 8.0.1425.11118 (/app/System.Security.Cryptography.OpenSsl.dll)", + "System.Security.Cryptography.Primitives @ 8.0.1425.11118 (/app/System.Security.Cryptography.Primitives.dll)", + "System.Security.Cryptography.X509Certificates @ 8.0.1425.11118 (/app/System.Security.Cryptography.X509Certificates.dll)", + "System.Security.Principal @ 8.0.1425.11118 (/app/System.Security.Principal.dll)", + "System.Security.Principal.Windows @ 8.0.1425.11118 (/app/System.Security.Principal.Windows.dll)", + "System.Security.SecureString @ 8.0.1425.11118 (/app/System.Security.SecureString.dll)", + "System.ServiceModel.Web @ 8.0.1425.11118 (/app/System.ServiceModel.Web.dll)", + "System.ServiceProcess @ 8.0.1425.11118 (/app/System.ServiceProcess.dll)", + "System.Text.Encoding @ 8.0.1425.11118 (/app/System.Text.Encoding.dll)", + "System.Text.Encoding.CodePages @ 8.0.1425.11118 (/app/System.Text.Encoding.CodePages.dll)", + "System.Text.Encoding.Extensions @ 8.0.1425.11118 (/app/System.Text.Encoding.Extensions.dll)", + "System.Text.Encodings.Web @ 8.0.1425.11118 (/app/System.Text.Encodings.Web.dll)", + "System.Text.Json @ 8.0.1425.11118 (/app/System.Text.Json.dll)", + "System.Text.RegularExpressions @ 8.0.1425.11118 (/app/System.Text.RegularExpressions.dll)", + "System.Threading @ 8.0.1425.11118 (/app/System.Threading.dll)", + "System.Threading.Channels @ 8.0.1425.11118 (/app/System.Threading.Channels.dll)", + "System.Threading.Overlapped @ 8.0.1425.11118 (/app/System.Threading.Overlapped.dll)", + "System.Threading.Tasks @ 8.0.1425.11118 (/app/System.Threading.Tasks.dll)", + "System.Threading.Tasks.Dataflow @ 8.0.1425.11118 (/app/System.Threading.Tasks.Dataflow.dll)", + "System.Threading.Tasks.Extensions @ 8.0.1425.11118 (/app/System.Threading.Tasks.Extensions.dll)", + "System.Threading.Tasks.Parallel @ 8.0.1425.11118 (/app/System.Threading.Tasks.Parallel.dll)", + "System.Threading.Thread @ 8.0.1425.11118 (/app/System.Threading.Thread.dll)", + "System.Threading.ThreadPool @ 8.0.1425.11118 (/app/System.Threading.ThreadPool.dll)", + "System.Threading.Timer @ 8.0.1425.11118 (/app/System.Threading.Timer.dll)", + "System.Transactions @ 8.0.1425.11118 (/app/System.Transactions.dll)", + "System.Transactions.Local @ 8.0.1425.11118 (/app/System.Transactions.Local.dll)", + "System.ValueTuple @ 8.0.1425.11118 (/app/System.ValueTuple.dll)", + "System.Web @ 8.0.1425.11118 (/app/System.Web.dll)", + "System.Web.HttpUtility @ 8.0.1425.11118 (/app/System.Web.HttpUtility.dll)", + "System.Windows @ 8.0.1425.11118 (/app/System.Windows.dll)", + "System.Xml @ 8.0.1425.11118 (/app/System.Xml.dll)", + "System.Xml.Linq @ 8.0.1425.11118 (/app/System.Xml.Linq.dll)", + "System.Xml.ReaderWriter @ 8.0.1425.11118 (/app/System.Xml.ReaderWriter.dll)", + "System.Xml.Serialization @ 8.0.1425.11118 (/app/System.Xml.Serialization.dll)", + "System.Xml.XDocument @ 8.0.1425.11118 (/app/System.Xml.XDocument.dll)", + "System.Xml.XPath @ 8.0.1425.11118 (/app/System.Xml.XPath.dll)", + "System.Xml.XPath.XDocument @ 8.0.1425.11118 (/app/System.Xml.XPath.XDocument.dll)", + "System.Xml.XmlDocument @ 8.0.1425.11118 (/app/System.Xml.XmlDocument.dll)", + "System.Xml.XmlSerializer @ 8.0.1425.11118 (/app/System.Xml.XmlSerializer.dll)", + "WindowsBase @ 8.0.1425.11118 (/app/WindowsBase.dll)", + "mscorlib @ 8.0.1425.11118 (/app/mscorlib.dll)", + "netstandard @ 8.0.1425.11118 (/app/netstandard.dll)", + ) + + assertAllDepEntriesWithoutExecutables := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) { + t.Helper() + for _, p := range pkgs { + // assert that all packages DO NOT have an executable associated with it + m, ok := p.Metadata.(pkg.DotnetDepsEntry) + if !ok { + t.Fatalf("expected metadata to be of type DotnetDepsEntry") + } + if len(m.Executables) != 0 { + t.Errorf("expected no executables for package %s, found %d", p.Name, len(m.Executables)) + } + } + + actual := extractMatchingPackage(t, "Newtonsoft.Json", pkgs) + expected := pkg.Package{ + Name: "Newtonsoft.Json", + Version: "13.0.3", + Locations: file.NewLocationSet(file.NewLocation("/app/dotnetapp.deps.json")), + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + PURL: "pkg:nuget/Newtonsoft.Json@13.0.3", + Metadata: pkg.DotnetDepsEntry{ + Name: "Newtonsoft.Json", + Version: "13.0.3", + Path: "newtonsoft.json/13.0.3", + Sha512: "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + HashPath: "newtonsoft.json.13.0.3.nupkg.sha512", + Executables: nil, // important! + }, + } + + pkgtest.AssertPackagesEqualIgnoreLayers(t, expected, actual) + } + + assertAllDepEntriesWithExecutables := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) { + t.Helper() + for _, p := range pkgs { + // assert that all packages have an executable associated with it + m, ok := p.Metadata.(pkg.DotnetDepsEntry) + if !ok { + t.Fatalf("expected metadata to be of type DotnetDepsEntry") + } + if len(m.Executables) != 1 { + t.Errorf("expected exactly one executable for package %s, found %d", p.Name, len(m.Executables)) + } + } + } + + assertAlmostAllDepEntriesWithExecutables := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) { + t.Helper() + for _, p := range pkgs { + // assert that all packages have an executable associated with it + m, ok := p.Metadata.(pkg.DotnetDepsEntry) + if !ok { + t.Fatalf("expected metadata to be of type DotnetDepsEntry") + } + if len(m.Executables) != 1 { + if m.Name == "Humanizer" { + // there is only one "virtual" package that doesn't have an actual DLL associated + assert.Empty(t, m.Executables) + continue + } + t.Errorf("expected exactly one executable for package %s, found %d", p.Name, len(m.Executables)) + } + } + + actual := extractMatchingPackage(t, "dotnetapp", pkgs) + + expected := pkg.Package{ + Name: "dotnetapp", + Version: "1.0.0", + FoundBy: "", + Locations: file.NewLocationSet( + file.NewLocation("/app/dotnetapp.deps.json"), + file.NewLocation("/app/dotnetapp.dll"), + ), + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + PURL: "pkg:nuget/dotnetapp@1.0.0", + Metadata: pkg.DotnetDepsEntry{ + Name: "dotnetapp", + Version: "1.0.0", + // note: the main package does not have a hash/path/etc + Executables: map[string]pkg.DotnetPortableExecutableEntry{ + "dotnetapp.dll": { + AssemblyVersion: "1.0.0.0", + InternalName: "dotnetapp.dll", + CompanyName: "dotnetapp", + ProductName: "dotnetapp", + ProductVersion: "1.0.0", + }, + }, + }, + } + + pkgtest.AssertPackagesEqualIgnoreLayers(t, expected, actual) + } + + assertAllBinaryEntries := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) { + t.Helper() + for _, p := range pkgs { + // assert that all packages have an executable associated with it + m, ok := p.Metadata.(pkg.DotnetPortableExecutableEntry) + if !ok { + t.Fatalf("expected metadata to be of type DotnetPortableExecutableEntry") + } + + assert.NotNil(t, m, "expected metadata to be a non-nil DotnetPortableExecutableEntry") + } + + actual := extractMatchingPackage(t, "dotnetapp", pkgs) + + expected := pkg.Package{ + Name: "dotnetapp", + Version: "1.0.0.0", // important: in the dep.json this is 1.0.0, now the assembly version is used + FoundBy: "", + Locations: file.NewLocationSet( + // important: we only have the dll as evidence + file.NewLocation("/app/dotnetapp.dll"), + ), + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + PURL: "pkg:nuget/dotnetapp@1.0.0.0", // important: in the dep.json this is 1.0.0, now the assembly version is used + Metadata: pkg.DotnetPortableExecutableEntry{ + AssemblyVersion: "1.0.0.0", + InternalName: "dotnetapp.dll", + CompanyName: "dotnetapp", + ProductName: "dotnetapp", + ProductVersion: "1.0.0", + }, + } + + pkgtest.AssertPackagesEqualIgnoreLayers(t, expected, actual) + + actual = extractMatchingPackage(t, "Json.NET", pkgs) + + expected = pkg.Package{ + Name: "Json.NET", // TODO: could we have done this better? We expected Newtonsoft.Json + Version: "13.0.3.27908", // TODO: should we use the product version here? + Locations: file.NewLocationSet(file.NewLocation("/app/Newtonsoft.Json.dll")), + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + PURL: "pkg:nuget/Json.NET@13.0.3.27908", // TODO: should we use the product version here? + Metadata: pkg.DotnetPortableExecutableEntry{ + AssemblyVersion: "13.0.0.0", + LegalCopyright: "Copyright © James Newton-King 2008", + Comments: "Json.NET is a popular high-performance JSON framework for .NET", + InternalName: "Newtonsoft.Json.dll", + CompanyName: "Newtonsoft", + ProductName: "Json.NET", + ProductVersion: "13.0.3+0a2e291c0d9c0c7675d445703e51750363a549ef", + }, + } + + pkgtest.AssertPackagesEqualIgnoreLayers(t, expected, actual) + + } + + assertSingleFileDeployment := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) { + t.Helper() + for _, p := range pkgs { + // assert that all packages have an executable associated with it + m, ok := p.Metadata.(pkg.DotnetPortableExecutableEntry) + if !ok { + t.Fatalf("expected metadata to be of type DotnetPortableExecutableEntry") + } + + assert.NotNil(t, m, "expected metadata to be a non-nil DotnetPortableExecutableEntry") + } + + actual := extractMatchingPackage(t, "dotnetapp", pkgs) + + expected := pkg.Package{ + Name: "dotnetapp", + Version: "1.0.0.0", // important: in the dep.json this is 1.0.0, now the assembly version is used + FoundBy: "", + Locations: file.NewLocationSet( + // important: we only have the exe as evidence + file.NewLocation("/app/dotnetapp.exe"), + ), + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + PURL: "pkg:nuget/dotnetapp@1.0.0.0", // important: in the dep.json this is 1.0.0, now the assembly version is used + Metadata: pkg.DotnetPortableExecutableEntry{ + AssemblyVersion: "1.0.0.0", + InternalName: "dotnetapp.dll", + CompanyName: "dotnetapp", + ProductName: "dotnetapp", + ProductVersion: "1.0.0", + }, + } + pkgtest.AssertPackagesEqualIgnoreLayers(t, expected, actual) + } + + cases := []struct { + name string + fixture string + expectedPkgs []string + expectedRels []string + assertion func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) + cataloger pkg.Cataloger + }{ + { + name: "deps cataloger", + fixture: "image-net8-app", + cataloger: NewDotnetDepsCataloger(), + expectedPkgs: net8AppExpectedDepPkgs, + expectedRels: net8AppExpectedDepRelationships, + assertion: assertAllDepEntriesWithoutExecutables, + }, + { + name: "combined cataloger", + fixture: "image-net8-app", + cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), + + // if we don't care about DLL claims in the deps.json, then this is right + //expectedPkgs: net8AppExpectedDepPkgs, + //expectedRels: net8AppExpectedDepRelationships, + + // we care about DLL claims in the deps.json, so the main application inherits all relationships to/from humarizer + expectedPkgs: net8AppExpectedDepPkgsWithoutUnpairedDlls, + expectedRels: replaceAll(net8AppDepOnlyRelationshipsWithoutHumanizer, "Humanizer @ 2.14.1", "dotnetapp @ 1.0.0"), + + assertion: assertAlmostAllDepEntriesWithExecutables, // important! this is what makes this case different from the previous one... dep entries have attached executables + }, + { + name: "combined cataloger (require dll pairings)", + fixture: "image-net8-app", + cataloger: NewDotnetDepsBinaryCataloger(CatalogerConfig{DepPackagesMustHaveDLL: true}), + expectedPkgs: net8AppExpectedDepPkgsWithoutUnpairedDlls, + expectedRels: []string{ + // the odd thing (but expected) is that the Humanizer.Core entries have a dependency to each Humanizer.Core.* locale entry + // because we're skipping over the "virtual" Humanizer package which does not have any associated DLLs with it. + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.deps.json)", + "Humanizer.Core.af @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ar @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.az @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.bg @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.bn-BD @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.cs @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.da @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.de @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.el @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.es @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fa @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fi-FI @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fr @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.fr-BE @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.he @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.hr @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.hu @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.hy @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.id @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.is @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.it @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ja @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ko-KR @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ku @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.lv @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ms-MY @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.mt @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.nb @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.nb-NO @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.nl @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.pl @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.pt @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ro @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.ru @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sk @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sl @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sr @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sr-Latn @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.sv @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.th-TH @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.tr @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.uk @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.uz-Cyrl-UZ @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.uz-Latn-UZ @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.vi @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.zh-CN @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.zh-Hans @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Humanizer.Core.zh-Hant @ 2.14.1 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + "Newtonsoft.Json @ 13.0.3 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + }, + assertion: assertAllDepEntriesWithExecutables, // important! we want the Humanizer package without a DLL to be ignored + }, + { + name: "PE cataloger", + fixture: "image-net8-app", + cataloger: NewDotnetPortableExecutableCataloger(), + expectedPkgs: net8AppBinaryOnlyPkgs, + // important: no relationships should be found + assertion: assertAllBinaryEntries, + }, + { + name: "deps cataloger (no deps.json)", + fixture: "image-net8-app-no-depjson", + cataloger: NewDotnetDepsCataloger(), + // there should be no packages found! + }, + { + name: "combined cataloger (no deps.json)", + fixture: "image-net8-app-no-depjson", + cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), + expectedPkgs: net8AppBinaryOnlyPkgs, + // important: no relationships should be found + assertion: assertAllBinaryEntries, + }, + { + name: "pe cataloger (no deps.json)", + fixture: "image-net8-app-no-depjson", + cataloger: NewDotnetPortableExecutableCataloger(), + expectedPkgs: net8AppBinaryOnlyPkgs, + // important: no relationships should be found + assertion: assertAllBinaryEntries, + }, + { + name: "deps cataloger (single file)", + fixture: "image-net8-app-single-file", + cataloger: NewDotnetDepsCataloger(), + // there should be no packages found! + }, + { + name: "combined cataloger (single file)", + fixture: "image-net8-app-single-file", + cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), + + // important: no relationships should be found + expectedPkgs: []string{ + "dotnetapp @ 1.0.0.0 (/app/dotnetapp.exe)", + }, + assertion: assertSingleFileDeployment, + }, + { + name: "pe cataloger (single file)", + fixture: "image-net8-app-single-file", + cataloger: NewDotnetPortableExecutableCataloger(), + // important: no relationships should be found + expectedPkgs: []string{ + "dotnetapp @ 1.0.0.0 (/app/dotnetapp.exe)", + }, + assertion: assertSingleFileDeployment, + }, + { + name: "deps cataloger (self-contained)", + fixture: "image-net8-app-self-contained", + cataloger: NewDotnetDepsCataloger(), + expectedPkgs: net8AppExpectedDepsSelfContainedPkgs, + expectedRels: func() []string { + x := net8AppExpectedDepSelfContainedRelationships + x = append(x, humanizerToAppDepsRelationship) + return x + }(), + }, + { + name: "combined cataloger (self-contained)", + fixture: "image-net8-app-self-contained", + cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), + // we care about DLL claims in the deps.json, so the main application inherits all relationships to/from humarizer + expectedPkgs: net8AppExpectedDepSelfContainedPkgs, + expectedRels: replaceAll(net8AppExpectedDepSelfContainedRelationships, "Humanizer @ 2.14.1", "dotnetapp @ 1.0.0"), + }, + { + name: "pe cataloger (self-contained)", + fixture: "image-net8-app-self-contained", + cataloger: NewDotnetPortableExecutableCataloger(), + // important: no relationships should be found + expectedPkgs: net8AppExpectedBinarySelfContainedPkgs, + }, + { + name: "combined cataloger (private assets + require dlls)", + fixture: "image-net8-privateassets", + cataloger: NewDotnetDepsBinaryCataloger(CatalogerConfig{DepPackagesMustHaveDLL: true}), + expectedPkgs: []string{ + "dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + }, + }, + { + name: "combined cataloger (private assets)", + fixture: "image-net8-privateassets", + cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), + expectedPkgs: []string{ + "dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + }, + }, + { + name: "combined cataloger (ilrepack + require dlls)", + fixture: "image-net8-ilrepack", + cataloger: NewDotnetDepsBinaryCataloger(CatalogerConfig{DepPackagesMustHaveDLL: true}), + expectedPkgs: []string{ + "dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + }, + }, + { + // TODO: this is to help us out in the future... we can use TypeDef info from the Metadata table to determine + // if package names are any "namespace" values of the assembly. Today we don't do this so we relax claims + // when bundling is detected instead of attempting to check for namespace values in TypeDef entries + // and correlate against deps.json entries (which is not a sure thing!). + name: "combined cataloger (ilrepack)", + fixture: "image-net8-ilrepack", + cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), + expectedPkgs: func() []string { + x := net8AppExpectedDepPkgs + x = append(x, + "ILRepack @ 2.0.33 (/app/dotnetapp.deps.json)", + "ILRepack.FullAuto @ 1.6.0 (/app/dotnetapp.deps.json)", + ) + return x + }(), + expectedRels: func() []string { + x := net8AppExpectedDepRelationships + x = append(x, + "ILRepack @ 2.0.33 (/app/dotnetapp.deps.json) [dependency-of] ILRepack.FullAuto @ 1.6.0 (/app/dotnetapp.deps.json)", + "ILRepack.FullAuto @ 1.6.0 (/app/dotnetapp.deps.json) [dependency-of] dotnetapp @ 1.0.0 (/app/dotnetapp.deps.json)", + ) + return x + }(), + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + if tt.assertion == nil { + tt.assertion = func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {} + } + pkgtest.NewCatalogTester(). + WithImageResolver(t, tt.fixture). + IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change + ExpectsPackageStrings(tt.expectedPkgs). + ExpectsRelationshipStrings(tt.expectedRels). + ExpectsAssertion(tt.assertion). + TestCataloger(t, tt.cataloger) + }) + } +} + +func TestDotnetDepsCataloger_problemCases(t *testing.T) { + cases := []struct { + name string + fixture string + assertion func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) + cataloger pkg.Cataloger + }{ + { + // during development, these version resources tended to be corrupted + name: "Newtonsoft dll details", + fixture: "image-net8-app-no-depjson", + cataloger: NewDotnetPortableExecutableCataloger(), + assertion: func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) { + // TODO: name should be "Newtonsoft.Json" (bad metadata in the artifact) + actual := extractMatchingPackage(t, "Json.NET", pkgs) + + expected := pkg.Package{ + Name: "Json.NET", + Version: "13.0.3.27908", // TODO should we parse the product version to get 13.0.3? + Locations: file.NewLocationSet(file.NewLocation("/app/Newtonsoft.Json.dll")), + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + PURL: "pkg:nuget/Json.NET@13.0.3.27908", + Metadata: pkg.DotnetPortableExecutableEntry{ + AssemblyVersion: "13.0.0.0", + LegalCopyright: "Copyright © James Newton-King 2008", + Comments: "Json.NET is a popular high-performance JSON framework for .NET", + InternalName: "Newtonsoft.Json.dll", + CompanyName: "Newtonsoft", + ProductName: "Json.NET", + ProductVersion: "13.0.3+0a2e291c0d9c0c7675d445703e51750363a549ef", + }, + } + + pkgtest.AssertPackagesEqualIgnoreLayers(t, expected, actual) + }, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + if tt.assertion == nil { + t.Fatalf("assertion is required") + } + pkgtest.NewCatalogTester(). + WithImageResolver(t, tt.fixture). + IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change + ExpectsAssertion(tt.assertion). + TestCataloger(t, tt.cataloger) + }) + } +} + +func Test_corruptDotnetPE(t *testing.T) { + pkgtest.NewCatalogTester(). + FromDirectory(t, "test-fixtures/glob-paths/src"). + Expects(nil, nil). // we shouldn't find packages nor error out + TestCataloger(t, NewDotnetPortableExecutableCataloger()) +} + +func Test_extractVersion(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + input: "1, 0, 0, 0", + expected: "1, 0, 0, 0", + }, + { + input: "Release 73", + expected: "Release 73", + }, + { + input: "4.7.4076.0 built by: NET472REL1LAST_B", + expected: "4.7.4076.0", + }, + } + + for _, test := range tests { + t.Run(test.input, func(t *testing.T) { + got := extractVersionFromResourcesValue(test.input) + assert.Equal(t, test.expected, got) + }) + } +} + +func Test_spaceNormalize(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + expected: "some spaces apart", + input: " some spaces\n\t\t \n\rapart\n", + }, + { + expected: "söme ¡nvalid characters", + input: "\rsöme \u0001¡nvalid\t characters\n", + }, + } + + for _, test := range tests { + t.Run(test.expected, func(t *testing.T) { + got := spaceNormalize(test.input) + assert.Equal(t, test.expected, got) + }) + } +} + +func Test_corruptDotnetDeps(t *testing.T) { + pkgtest.NewCatalogTester(). + FromDirectory(t, "test-fixtures/glob-paths/src"). + Expects(nil, nil). // we shouldn't find packages nor error out + TestCataloger(t, NewDotnetDepsCataloger()) +} + +func TestParseDotnetDeps(t *testing.T) { + fixture := "test-fixtures/dir-example-1" + fixtureLocationSet := file.NewLocationSet(file.NewLocation("TestLibrary.deps.json")) + rootPkg := pkg.Package{ + Name: "TestLibrary", + Version: "1.0.0", + PURL: "pkg:nuget/TestLibrary@1.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "TestLibrary", + Version: "1.0.0", + }, + } + testCommon := pkg.Package{ + Name: "TestCommon", + Version: "1.0.0", + PURL: "pkg:nuget/TestCommon@1.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "TestCommon", + Version: "1.0.0", + }, + } + awssdkcore := pkg.Package{ + Name: "AWSSDK.Core", + Version: "3.7.10.6", + PURL: "pkg:nuget/AWSSDK.Core@3.7.10.6", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "AWSSDK.Core", + Version: "3.7.10.6", + Sha512: "sha512-kHBB+QmosVaG6DpngXQ8OlLVVNMzltNITfsRr68Z90qO7dSqJ2EHNd8dtBU1u3AQQLqqFHOY0lfmbpexeH6Pew==", + Path: "awssdk.core/3.7.10.6", + HashPath: "awssdk.core.3.7.10.6.nupkg.sha512", + }, + } + msftDependencyInjectionAbstractions := pkg.Package{ + Name: "Microsoft.Extensions.DependencyInjection.Abstractions", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection.Abstractions@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "Microsoft.Extensions.DependencyInjection.Abstractions", + Version: "6.0.0", + Sha512: "sha512-xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==", + Path: "microsoft.extensions.dependencyinjection.abstractions/6.0.0", + HashPath: "microsoft.extensions.dependencyinjection.abstractions.6.0.0.nupkg.sha512", + }, + } + msftDependencyInjection := pkg.Package{ + Name: "Microsoft.Extensions.DependencyInjection", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "Microsoft.Extensions.DependencyInjection", + Version: "6.0.0", + Sha512: "sha512-k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + Path: "microsoft.extensions.dependencyinjection/6.0.0", + HashPath: "microsoft.extensions.dependencyinjection.6.0.0.nupkg.sha512", + }, + } + msftLoggingAbstractions := pkg.Package{ + Name: "Microsoft.Extensions.Logging.Abstractions", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.Logging.Abstractions@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "Microsoft.Extensions.Logging.Abstractions", + Version: "6.0.0", + Sha512: "sha512-/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==", + Path: "microsoft.extensions.logging.abstractions/6.0.0", + HashPath: "microsoft.extensions.logging.abstractions.6.0.0.nupkg.sha512", + }, + } + msftExtensionsLogging := pkg.Package{ + Name: "Microsoft.Extensions.Logging", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.Logging@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "Microsoft.Extensions.Logging", + Version: "6.0.0", + Sha512: "sha512-eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + Path: "microsoft.extensions.logging/6.0.0", + HashPath: "microsoft.extensions.logging.6.0.0.nupkg.sha512", + }, + } + msftExtensionsOptions := pkg.Package{ + Name: "Microsoft.Extensions.Options", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.Options@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "Microsoft.Extensions.Options", + Version: "6.0.0", + Sha512: "sha512-dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + Path: "microsoft.extensions.options/6.0.0", + HashPath: "microsoft.extensions.options.6.0.0.nupkg.sha512", + }, + } + msftExtensionsPrimitives := pkg.Package{ + Name: "Microsoft.Extensions.Primitives", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.Primitives@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "Microsoft.Extensions.Primitives", + Version: "6.0.0", + Sha512: "sha512-9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", + Path: "microsoft.extensions.primitives/6.0.0", + HashPath: "microsoft.extensions.primitives.6.0.0.nupkg.sha512", + }, + } + newtonsoftJson := pkg.Package{ + Name: "Newtonsoft.Json", + Version: "13.0.1", + PURL: "pkg:nuget/Newtonsoft.Json@13.0.1", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "Newtonsoft.Json", + Version: "13.0.1", + Sha512: "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==", + Path: "newtonsoft.json/13.0.1", + HashPath: "newtonsoft.json.13.0.1.nupkg.sha512", + }, + } + serilogSinksConsole := pkg.Package{ + Name: "Serilog.Sinks.Console", + Version: "4.0.1", + PURL: "pkg:nuget/Serilog.Sinks.Console@4.0.1", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "Serilog.Sinks.Console", + Version: "4.0.1", + Sha512: "sha512-apLOvSJQLlIbKlbx+Y2UDHSP05kJsV7mou+fvJoRGs/iR+jC22r8cuFVMjjfVxz/AD4B2UCltFhE1naRLXwKNw==", + Path: "serilog.sinks.console/4.0.1", + HashPath: "serilog.sinks.console.4.0.1.nupkg.sha512", + }, + } + serilog := pkg.Package{ + Name: "Serilog", + Version: "2.10.0", + PURL: "pkg:nuget/Serilog@2.10.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "Serilog", + Version: "2.10.0", + Sha512: "sha512-+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==", + Path: "serilog/2.10.0", + HashPath: "serilog.2.10.0.nupkg.sha512", + }, + } + systemDiagnosticsDiagnosticsource := pkg.Package{ + Name: "System.Diagnostics.DiagnosticSource", + Version: "6.0.0", + PURL: "pkg:nuget/System.Diagnostics.DiagnosticSource@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "System.Diagnostics.DiagnosticSource", + Version: "6.0.0", + Sha512: "sha512-frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==", + Path: "system.diagnostics.diagnosticsource/6.0.0", + HashPath: "system.diagnostics.diagnosticsource.6.0.0.nupkg.sha512", + }, + } + systemRuntimeCompilerServicesUnsafe := pkg.Package{ + Name: "System.Runtime.CompilerServices.Unsafe", + Version: "6.0.0", + PURL: "pkg:nuget/System.Runtime.CompilerServices.Unsafe@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + Metadata: pkg.DotnetDepsEntry{ + Name: "System.Runtime.CompilerServices.Unsafe", + Version: "6.0.0", + Sha512: "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + Path: "system.runtime.compilerservices.unsafe/6.0.0", + HashPath: "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512", + }} + + expectedPkgs := []pkg.Package{ + awssdkcore, + msftDependencyInjection, + msftDependencyInjectionAbstractions, + msftExtensionsLogging, + msftLoggingAbstractions, + msftExtensionsOptions, + msftExtensionsPrimitives, + newtonsoftJson, + serilog, + serilogSinksConsole, + systemDiagnosticsDiagnosticsource, + systemRuntimeCompilerServicesUnsafe, + testCommon, + rootPkg, + } + + // ┌── (✓ = is represented in the test) + // ↓ + // + // ✓ TestLibrary/1.0.0 (project) + // ✓ ├── [a] Microsoft.Extensions.DependencyInjection/6.0.0 [file version: 6.0.21.52210] + // ✓ │ ├── [b] Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 [file version: 6.0.21.52210] + // ✓ │ └── [c!] System.Runtime.CompilerServices.Unsafe/6.0.0 [NO TARGET INFO] + // ✓ ├── Microsoft.Extensions.Logging/6.0.0 [file version: 6.0.21.52210] + // ✓ │ ├── Microsoft.Extensions.DependencyInjection/6.0.0 ...to [a] + // ✓ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b] + // ✓ │ ├── Microsoft.Extensions.Logging.Abstractions/6.0.0 [file version: 6.0.21.52210] + // ✓ │ ├── Microsoft.Extensions.Options/6.0.0 [file version: 6.0.21.52210] + // ✓ │ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b] + // ✓ │ │ └── Microsoft.Extensions.Primitives/6.0.0 [file version: 6.0.21.52210] + // ✓ │ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!] + // ✓ │ └── System.Diagnostics.DiagnosticSource/6.0.0 [NO RUNTIME INFO] + // ✓ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!] + // ✓ ├── Newtonsoft.Json/13.0.1 [file version: 13.0.1.25517] + // ✓ ├── [d] Serilog/2.10.0 [file version: 2.10.0.0] + // ✓ ├── Serilog.Sinks.Console/4.0.1 [file version: 4.0.1.0] + // ✓ │ └── Serilog/2.10.0 ...to [d] + // ✓ └── [e!] TestCommon/1.0.0 [NOT SERVICEABLE / NO SHA] + // ✓ └── AWSSDK.Core/3.7.10.6 [file version: 3.7.10.6] + + expectedRelationships := []artifact.Relationship{ + { + From: awssdkcore, + To: testCommon, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjection, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjection, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjectionAbstractions, + To: msftDependencyInjection, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjectionAbstractions, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjectionAbstractions, + To: msftExtensionsOptions, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftExtensionsLogging, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftLoggingAbstractions, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftExtensionsOptions, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftExtensionsPrimitives, + To: msftExtensionsOptions, + Type: artifact.DependencyOfRelationship, + }, + { + From: newtonsoftJson, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: serilog, + To: serilogSinksConsole, + Type: artifact.DependencyOfRelationship, + }, + { + From: serilog, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: serilogSinksConsole, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: systemDiagnosticsDiagnosticsource, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: systemRuntimeCompilerServicesUnsafe, + To: msftDependencyInjection, + Type: artifact.DependencyOfRelationship, + }, + { + From: systemRuntimeCompilerServicesUnsafe, + To: msftExtensionsPrimitives, + Type: artifact.DependencyOfRelationship, + }, + { + From: systemRuntimeCompilerServicesUnsafe, + To: systemDiagnosticsDiagnosticsource, + Type: artifact.DependencyOfRelationship, + }, + { + From: testCommon, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + } + + pkgtest.TestCataloger(t, fixture, NewDotnetDepsCataloger(), expectedPkgs, expectedRelationships) +} + +func extractMatchingPackage(t *testing.T, name string, pkgs []pkg.Package) pkg.Package { + for _, p := range pkgs { + if p.Name == name { + return p + } + } + t.Fatalf("expected to find package %s", name) + return pkg.Package{} +} + +func replaceAll(ss []string, find, replace string) []string { + var results []string + for _, s := range ss { + results = append(results, strings.ReplaceAll(s, find, replace)) + } + return results +} diff --git a/syft/pkg/cataloger/dotnet/config.go b/syft/pkg/cataloger/dotnet/config.go index 3c3712c8b..14ab4bfbb 100644 --- a/syft/pkg/cataloger/dotnet/config.go +++ b/syft/pkg/cataloger/dotnet/config.go @@ -1,14 +1,37 @@ package dotnet type CatalogerConfig struct { - EnableCertificateValidation bool `json:"enable-certificate-validation" yaml:"enable-certificate-validation" mapstructure:"enable-certificate-validation"` + // DepPackagesMustHaveDLL allows for deps.json packages to be included only if there is a DLL on disk for that package. + DepPackagesMustHaveDLL bool `mapstructure:"dep-packages-must-have-dll" json:"dep-packages-must-have-dll" yaml:"dep-packages-must-have-dll"` + + // DepPackagesMustClaimDLL allows for deps.json packages to be included only if there is a runtime/resource DLL claimed in the deps.json targets section. + // This does not require such claimed DLLs to exist on disk. The behavior of this + DepPackagesMustClaimDLL bool `mapstructure:"dep-packages-must-claim-dll" json:"dep-packages-must-claim-dll" yaml:"dep-packages-must-claim-dll"` + + // RelaxDLLClaimsWhenBundlingDetected will look for indications of IL bundle tooling via deps.json package names + // and, if found (and this config option is enabled), will relax the DepPackagesMustClaimDLL value to `false` only in those cases. + RelaxDLLClaimsWhenBundlingDetected bool `mapstructure:"relax-dll-claims-when-bundling-detected" json:"relax-dll-claims-when-bundling-detected" yaml:"relax-dll-claims-when-bundling-detected"` } -func (c CatalogerConfig) WithCertificateValidation(enable bool) CatalogerConfig { - c.EnableCertificateValidation = enable +func (c CatalogerConfig) WithDepPackagesMustHaveDLL(requireDlls bool) CatalogerConfig { + c.DepPackagesMustHaveDLL = requireDlls + return c +} + +func (c CatalogerConfig) WithDepPackagesMustClaimDLL(requireDlls bool) CatalogerConfig { + c.DepPackagesMustClaimDLL = requireDlls + return c +} + +func (c CatalogerConfig) WithRelaxDLLClaimsWhenBundlingDetected(relax bool) CatalogerConfig { + c.RelaxDLLClaimsWhenBundlingDetected = relax return c } func DefaultCatalogerConfig() CatalogerConfig { - return CatalogerConfig{} + return CatalogerConfig{ + DepPackagesMustHaveDLL: false, + DepPackagesMustClaimDLL: true, + RelaxDLLClaimsWhenBundlingDetected: true, + } } diff --git a/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go new file mode 100644 index 000000000..5f34ce005 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go @@ -0,0 +1,396 @@ +package dotnet + +import ( + "context" + "fmt" + "path" + "regexp" + "sort" + "strings" + + "github.com/scylladb/go-set/strset" + + "github.com/anchore/syft/internal" + "github.com/anchore/syft/internal/relationship" + "github.com/anchore/syft/internal/unknown" + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" +) + +const ( + depsJSONGlob = "**/*.deps.json" + dllGlob = "**/*.dll" + exeGlob = "**/*.exe" +) + +// depsBinaryCataloger will search for both deps.json evidence and PE file evidence to create packages. All packages +// from both sources are raised up, but with one merge operation applied; If a deps.json package reference can be +// correlated with a PE file, the PE file is attached to the package as supporting evidence. +type depsBinaryCataloger struct { + config CatalogerConfig +} + +func (c depsBinaryCataloger) Name() string { + return "dotnet-deps-binary-cataloger" +} + +func (c depsBinaryCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { + depJSONDocs, unknowns, err := findDepsJSON(resolver) + if err != nil { + return nil, nil, err + } + + peFiles, ldpeUnknownErr, err := findPEFiles(resolver) + if err != nil { + return nil, nil, err + } + if ldpeUnknownErr != nil { + unknowns = unknown.Join(unknowns, ldpeUnknownErr) + } + + // partition the logical PE files by location and pair them with the logicalDepsJSON + pairedDepsJSONs, remainingPeFiles, remainingDepsJSONs := partitionPEs(depJSONDocs, peFiles) + + var pkgs []pkg.Package + var relationships []artifact.Relationship + + depDocGroups := [][]logicalDepsJSON{pairedDepsJSONs} + + if !c.config.DepPackagesMustHaveDLL { + depDocGroups = append(depDocGroups, remainingDepsJSONs) + } + + for _, docs := range depDocGroups { + for _, doc := range docs { + ps, rs := packagesFromLogicalDepsJSON(doc, c.config) + pkgs = append(pkgs, ps...) + relationships = append(relationships, rs...) + } + } + + for _, pe := range remainingPeFiles { + pkgs = append(pkgs, newDotnetBinaryPackage(pe.VersionResources, pe.Location)) + } + + return pkgs, relationships, unknowns +} + +// partitionPEs pairs PE files with the deps.json based on directory containment. +func partitionPEs(depJsons []logicalDepsJSON, peFiles []logicalPE) ([]logicalDepsJSON, []logicalPE, []logicalDepsJSON) { + // sort deps.json paths from longest to shortest. This is so we are processing the most specific match first. + sort.Slice(depJsons, func(i, j int) bool { + return len(depJsons[i].Location.RealPath) > len(depJsons[j].Location.RealPath) + }) + + peFilesByPath := make(map[file.Coordinates][]logicalPE) + var remainingPeFiles []logicalPE + for _, pe := range peFiles { + var found bool + for i := range depJsons { + dep := &depJsons[i] + if isParentOf(dep.Location.RealPath, pe.Location.RealPath) && attachAssociatedExecutables(dep, pe) { + peFilesByPath[dep.Location.Coordinates] = append(peFilesByPath[dep.Location.Coordinates], pe) + found = true + // note: we cannot break from the dep JSON search since the same binary could be associated with multiple packages + // across multiple deps.json files. + } + } + if !found { + remainingPeFiles = append(remainingPeFiles, pe) + } + } + + var pairedDepsJSON []logicalDepsJSON + var remainingDepsJSON []logicalDepsJSON + + for _, dep := range depJsons { + if _, ok := peFilesByPath[dep.Location.Coordinates]; !ok { + remainingDepsJSON = append(remainingDepsJSON, dep) + } else { + pairedDepsJSON = append(pairedDepsJSON, dep) + } + } + + return pairedDepsJSON, remainingPeFiles, remainingDepsJSON +} + +// attachAssociatedExecutables looks for PE files matching runtime or resource entries +// and attaches them to the appropriate package. +func attachAssociatedExecutables(dep *logicalDepsJSON, pe logicalPE) bool { + appDir := path.Dir(dep.Location.RealPath) + relativeDllPath := strings.TrimPrefix(strings.TrimPrefix(pe.Location.RealPath, appDir), "/") + + var found bool + for key, p := range dep.PackagesByNameVersion { + if targetPath, ok := p.RuntimePathsByRelativeDLLPath[relativeDllPath]; ok { + pe.TargetPath = targetPath + p.Executables = append(p.Executables, pe) + dep.PackagesByNameVersion[key] = p // update the map with the modified package + found = true + continue + } + + if targetPath, ok := p.ResourcePathsByRelativeDLLPath[relativeDllPath]; ok { + pe.TargetPath = targetPath + p.Executables = append(p.Executables, pe) + dep.PackagesByNameVersion[key] = p // update the map with the modified package + found = true + } + } + return found +} + +var libPrefixPattern = regexp.MustCompile(`^lib/net[^/]+/`) + +// trimLibPrefix removes prefixes like "lib/net6.0/" from a path. +func trimLibPrefix(s string) string { + if match := libPrefixPattern.FindString(s); match != "" { + parts := strings.Split(s, "/") + if len(parts) > 2 { + return strings.Join(parts[2:], "/") + } + } + return s +} + +// isParentOf checks if parentFile's directory is a prefix of childFile's directory. +func isParentOf(parentFile, childFile string) bool { + parentDir := path.Dir(parentFile) + childDir := path.Dir(childFile) + return strings.HasPrefix(childDir, parentDir) +} + +// packagesFromDepsJSON creates packages from a list of logicalDepsJSON documents. +func packagesFromDepsJSON(docs []logicalDepsJSON, config CatalogerConfig) ([]pkg.Package, []artifact.Relationship) { + var pkgs []pkg.Package + var relationships []artifact.Relationship + for _, ldj := range docs { + ps, rs := packagesFromLogicalDepsJSON(ldj, config) + pkgs = append(pkgs, ps...) + relationships = append(relationships, rs...) + } + return pkgs, relationships +} + +// packagesFromLogicalDepsJSON converts a logicalDepsJSON (using the new map type) into catalog packages. +func packagesFromLogicalDepsJSON(doc logicalDepsJSON, config CatalogerConfig) ([]pkg.Package, []artifact.Relationship) { + var rootPkg *pkg.Package + if rootLpkg, hasRoot := doc.RootPackage(); !hasRoot { + rootPkg = newDotnetDepsPackage(rootLpkg, doc.Location) + } + + var pkgs []pkg.Package + pkgMap := make(map[string]pkg.Package) + if rootPkg != nil { + pkgs = append(pkgs, *rootPkg) + pkgMap[createNameAndVersion(rootPkg.Name, rootPkg.Version)] = *rootPkg + } + + nameVersions := doc.PackageNameVersions.List() + sort.Strings(nameVersions) + + // process each non-root package + skippedDepPkgs := make(map[string]logicalDepsJSONPackage) + for _, nameVersion := range nameVersions { + name, version := extractNameAndVersion(nameVersion) + if rootPkg != nil && name == rootPkg.Name && version == rootPkg.Version { + continue + } + lp := doc.PackagesByNameVersion[nameVersion] + if config.DepPackagesMustHaveDLL && len(lp.Executables) == 0 { + // could not find a paired DLL and the user required this... + skippedDepPkgs[nameVersion] = lp + continue + } + + claimsDLLs := len(lp.RuntimePathsByRelativeDLLPath) > 0 || len(lp.ResourcePathsByRelativeDLLPath) > 0 + + if config.DepPackagesMustClaimDLL && !claimsDLLs { + if config.RelaxDLLClaimsWhenBundlingDetected && !doc.BundlingDetected || !config.RelaxDLLClaimsWhenBundlingDetected { + // could not find a runtime or resource path and the user required this... + // and there is no evidence of a bundler in the dependencies (e.g. ILRepack) + skippedDepPkgs[nameVersion] = lp + continue + } + } + + dotnetPkg := newDotnetDepsPackage(lp, doc.Location) + if dotnetPkg != nil { + pkgs = append(pkgs, *dotnetPkg) + pkgMap[nameVersion] = *dotnetPkg + } + } + + return pkgs, relationshipsFromLogicalDepsJSON(doc, pkgMap, skippedDepPkgs) +} + +// relationshipsFromLogicalDepsJSON creates relationships from a logicalDepsJSON document for only the given syft packages. +// It is possible that the document describes more packages than that is provided as syft packages, in which cases +// those relationships will not be created. If there are any skipped packages, we still want to logically represent +// dependency relationships, jumping over the skipped packages. +func relationshipsFromLogicalDepsJSON(doc logicalDepsJSON, pkgMap map[string]pkg.Package, skipped map[string]logicalDepsJSONPackage) []artifact.Relationship { + var relationships []artifact.Relationship + for _, lp := range doc.PackagesByNameVersion { + if lp.Targets == nil { + continue + } + for depName, depVersion := range lp.Targets.Dependencies { + depNameVersion := createNameAndVersion(depName, depVersion) + thisPkg, ok := pkgMap[lp.NameVersion] + if !ok { + continue + } + + var depPkgs []pkg.Package + depPkg, ok := pkgMap[depNameVersion] + if !ok { + skippedDepPkg, ok := skipped[depNameVersion] + if !ok { + // this package wasn't explicitly skipped, so it could be a malformed deps.json file + // ignore this case and do not create a relationships + continue + } + // we have a skipped package, so we need to create a relationship but looking a the nearest + // package with an associated PE file for even dependency listed on the skipped package. + // Take note that the skipped depedency's dependency could also be skipped, so we need to + // do this recursively. + depPkgs = findNearestDependencyPackages(skippedDepPkg, pkgMap, skipped, strset.New()) + } else { + depPkgs = append(depPkgs, depPkg) + } + + for _, d := range depPkgs { + rel := artifact.Relationship{ + From: d, + To: thisPkg, + Type: artifact.DependencyOfRelationship, + } + relationships = append(relationships, rel) + } + } + } + + relationship.Sort(relationships) + return relationships +} + +func findNearestDependencyPackages(skippedDep logicalDepsJSONPackage, pkgMap map[string]pkg.Package, skipped map[string]logicalDepsJSONPackage, processed *strset.Set) []pkg.Package { + var nearestPkgs []pkg.Package + + // if we have already processed this package, skip it to avoid infinite recursion + if processed.Has(skippedDep.NameVersion) { + return nearestPkgs + } + + processed.Add(skippedDep.NameVersion) + + for depName, depVersion := range skippedDep.Targets.Dependencies { + depNameVersion := createNameAndVersion(depName, depVersion) + depPkg, ok := pkgMap[depNameVersion] + if !ok { + skippedDepPkg, ok := skipped[depNameVersion] + if !ok { + // this package wasn't explicitly skipped, so it could be a malformed deps.json file + // ignore this case and do not create a relationships + continue + } + + nearestPkgs = append(nearestPkgs, findNearestDependencyPackages(skippedDepPkg, pkgMap, skipped, processed)...) + } else { + nearestPkgs = append(nearestPkgs, depPkg) + } + } + return nearestPkgs +} + +// findDepsJSON locates and parses all deps.json files. +func findDepsJSON(resolver file.Resolver) ([]logicalDepsJSON, error, error) { + locs, err := resolver.FilesByGlob(depsJSONGlob) + if err != nil { + return nil, nil, fmt.Errorf("unable to find deps.json files: %w", err) + } + + var depsJSONs []logicalDepsJSON + var unknownErr error + for _, loc := range locs { + dj, err := readDepsJSON(resolver, loc) + if err != nil { + unknownErr = unknown.Append(unknownErr, loc, err) + continue + } + + depsJSONs = append(depsJSONs, getLogicalDepsJSON(*dj)) + } + + return depsJSONs, unknownErr, nil +} + +// readDepsJSON reads and parses a single deps.json file. +func readDepsJSON(resolver file.Resolver, loc file.Location) (*depsJSON, error) { + reader, err := resolver.FileContentsByLocation(loc) + if err != nil { + return nil, unknown.New(loc, fmt.Errorf("unable to read deps.json file: %w", err)) + } + defer internal.CloseAndLogError(reader, loc.RealPath) + + dj, err := newDepsJSON(file.NewLocationReadCloser(loc, reader)) + if err != nil { + return nil, unknown.New(loc, fmt.Errorf("unable to parse deps.json file: %w", err)) + } + + if dj == nil { + return nil, unknown.New(loc, fmt.Errorf("expected to find packages in deps.json but did not: %q", loc.RealPath)) + } + + return dj, nil +} + +// findPEFiles locates and parses all PE files (dll/exe). +func findPEFiles(resolver file.Resolver) ([]logicalPE, error, error) { + peLocs, err := resolver.FilesByGlob(dllGlob, exeGlob) + if err != nil { + return nil, nil, fmt.Errorf("unable to find PE files: %w", err) + } + + var peFiles []logicalPE + var unknownErr error + for _, loc := range peLocs { + ldpe, err := readPEFile(resolver, loc) + if err != nil { + unknownErr = unknown.Append(unknownErr, loc, err) + continue + } + if ldpe == nil { + continue + } + peFiles = append(peFiles, *ldpe) + } + + return peFiles, unknownErr, nil +} + +// readPEFile reads and parses a single PE file. +func readPEFile(resolver file.Resolver, loc file.Location) (*logicalPE, error) { + reader, err := resolver.FileContentsByLocation(loc) + if err != nil { + return nil, unknown.New(loc, fmt.Errorf("unable to read PE file: %w", err)) + } + defer internal.CloseAndLogError(reader, loc.RealPath) + + ldpe, err := getLogicalDotnetPE(file.NewLocationReadCloser(loc, reader)) + if err != nil { + return nil, unknown.New(loc, fmt.Errorf("unable to parse PE file: %w", err)) + } + + if ldpe == nil { + return nil, nil + } + + if !ldpe.CLR.hasEvidenceOfCLR() { + // this is not a .NET binary + return nil, nil + } + + return ldpe, nil +} diff --git a/syft/pkg/cataloger/dotnet/deps_cataloger.go b/syft/pkg/cataloger/dotnet/deps_cataloger.go new file mode 100644 index 000000000..e39ec80df --- /dev/null +++ b/syft/pkg/cataloger/dotnet/deps_cataloger.go @@ -0,0 +1,31 @@ +package dotnet + +import ( + "context" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" +) + +// depsCataloger will search for deps.json file contents. +// Deprecated: use depsBinaryCataloger instead which combines the PE and deps.json data which yields more accurate results (will be removed in syft v2.0). +type depsCataloger struct { +} + +func (c depsCataloger) Name() string { + return "dotnet-deps-cataloger" +} + +func (c depsCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { + depJSONDocs, unknowns, err := findDepsJSON(resolver) + if err != nil { + return nil, nil, err + } + + pkgs, rels := packagesFromDepsJSON(depJSONDocs, CatalogerConfig{ + DepPackagesMustHaveDLL: false, + DepPackagesMustClaimDLL: false, + }) + return pkgs, rels, unknowns +} diff --git a/syft/pkg/cataloger/dotnet/deps_json.go b/syft/pkg/cataloger/dotnet/deps_json.go new file mode 100644 index 000000000..f5bddc064 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/deps_json.go @@ -0,0 +1,168 @@ +package dotnet + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/scylladb/go-set/strset" + + "github.com/anchore/syft/syft/file" +) + +type depsJSON struct { + Location file.Location + RuntimeTarget runtimeTarget `json:"runtimeTarget"` + Targets map[string]map[string]depsTarget `json:"targets"` + Libraries map[string]depsLibrary `json:"libraries"` +} + +type runtimeTarget struct { + Name string `json:"name"` +} + +type depsTarget struct { + Dependencies map[string]string `json:"dependencies"` + Runtime map[string]map[string]string `json:"runtime"` + Resources map[string]map[string]string `json:"resources"` +} + +type depsLibrary struct { + Type string `json:"type"` + Path string `json:"path"` + Sha512 string `json:"sha512"` + HashPath string `json:"hashPath"` +} + +// logicalDepsJSONPackage merges target and library information for a given package from all dep.json entries. +// Note: this is not a real construct of the deps.json, just a useful reorganization of the data for downstream processing. +type logicalDepsJSONPackage struct { + NameVersion string + Targets *depsTarget + Library *depsLibrary + + // RuntimePathsByRelativeDLLPath is a map of the relative path to the DLL relative to the deps.json file + // to the target path as described in the deps.json target entry under "runtime". + RuntimePathsByRelativeDLLPath map[string]string + + // ResourcePathsByRelativeDLLPath is a map of the relative path to the DLL relative to the deps.json file + // to the target path as described in the deps.json target entry under "resource". + ResourcePathsByRelativeDLLPath map[string]string + + // Executables is a list of all the executables that are part of this package. This is populated by the PE cataloger + // and not something that is found in the deps.json file. This allows us to associate the PE files with this package + // based on the relative path to the DLL. + Executables []logicalPE +} + +type logicalDepsJSON struct { + Location file.Location + RuntimeTarget runtimeTarget + PackagesByNameVersion map[string]logicalDepsJSONPackage + PackageNameVersions *strset.Set + BundlingDetected bool +} + +func (l logicalDepsJSON) RootPackage() (logicalDepsJSONPackage, bool) { + rootName := getDepsJSONFilePrefix(l.Location.RealPath) + if rootName == "" { + return logicalDepsJSONPackage{}, false + } + + // iterate over the map to find the root package. If we don't find the root package, that's ok! We still want to + // get all of the packages that are defined in this deps.json file. + for _, p := range l.PackagesByNameVersion { + name, _ := extractNameAndVersion(p.NameVersion) + // there can be multiple projects defined in a deps.json and only by convention is the root project the same name as the deps.json file + // however there are other configurations that can lead to differences (e.g. "tool_fsc" vs "fsc.deps.json"). + if p.Library != nil && p.Library.Type == "project" && name == rootName { + return p, true + } + } + return logicalDepsJSONPackage{}, false +} + +func newDepsJSON(reader file.LocationReadCloser) (*depsJSON, error) { + var doc depsJSON + dec := json.NewDecoder(reader) + if err := dec.Decode(&doc); err != nil { + return nil, fmt.Errorf("failed to parse deps.json file: %w", err) + } + doc.Location = reader.Location + return &doc, nil +} + +var knownBundlers = strset.New( + "ILRepack.Lib.MSBuild.Task", // The most official use of ILRepack https://github.com/gluck/il-repack + "ILRepack.Lib", // library interface for ILRepack + "ILRepack.Lib.MSBuild", // uses Cecil 0.10 + "ILRepack.Lib.NET", // uses ModuleDefinitions instead of filenames + "ILRepack.NETStandard", // .NET Standard compatible version + "ILRepack.FullAuto", // https://github.com/kekyo/ILRepack.FullAuto + "ILMerge", // deprecated, but still used in some projects https://github.com/dotnet/ILMerge + "JetBrains.Build.ILRepack", // generally from https://www.nuget.org/packages?q=ilrepack&sortBy=relevance + + // other bundling/modification tools found in results + "PostSharp.Community.Packer", // Embeds dependencies as resources + "Brokenevent.ILStrip", // assembly cleaner (removes unused parts) + "Brokenevent.ILStrip.CLI", // command-line/MSBuild variant + "Costura.Fody", // referenced in MSBuildRazorCompiler.Lib + "Fody", // IL weaving framework +) + +func getLogicalDepsJSON(deps depsJSON) logicalDepsJSON { + packageMap := make(map[string]*logicalDepsJSONPackage) + nameVersions := strset.New() + + for _, targets := range deps.Targets { + for libName, target := range targets { + _, exists := packageMap[libName] + if !exists { + var lib *depsLibrary + l, ok := deps.Libraries[libName] + if ok { + lib = &l + } + runtimePaths := make(map[string]string) + for path := range target.Runtime { + runtimePaths[trimLibPrefix(path)] = path + } + resourcePaths := make(map[string]string) + for path := range target.Resources { + trimmedPath := trimLibPrefix(path) + if _, exists := resourcePaths[trimmedPath]; exists { + continue + } + resourcePaths[trimmedPath] = path + } + + p := &logicalDepsJSONPackage{ + NameVersion: libName, + Library: lib, + Targets: &target, + RuntimePathsByRelativeDLLPath: runtimePaths, + ResourcePathsByRelativeDLLPath: resourcePaths, + } + packageMap[libName] = p + nameVersions.Add(libName) + } + } + } + packages := make(map[string]logicalDepsJSONPackage) + var bundlingDetected bool + for _, p := range packageMap { + name := strings.Split(p.NameVersion, "/")[0] + if !bundlingDetected && knownBundlers.Has(name) { + bundlingDetected = true + } + packages[p.NameVersion] = *p + } + + return logicalDepsJSON{ + Location: deps.Location, + RuntimeTarget: deps.RuntimeTarget, + PackagesByNameVersion: packages, + PackageNameVersions: nameVersions, + BundlingDetected: bundlingDetected, + } +} diff --git a/syft/pkg/cataloger/dotnet/package.go b/syft/pkg/cataloger/dotnet/package.go index 0d1563cd7..7b39c552a 100644 --- a/syft/pkg/cataloger/dotnet/package.go +++ b/syft/pkg/cataloger/dotnet/package.go @@ -5,26 +5,36 @@ import ( "regexp" "strings" + "github.com/anchore/go-version" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" ) -func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations ...file.Location) *pkg.Package { - name, version := extractNameAndVersion(nameVersion) +var ( + // spaceRegex includes nbsp (#160) considered to be a space character + spaceRegex = regexp.MustCompile(`[\s\xa0]+`) + numberRegex = regexp.MustCompile(`\d`) + versionPunctuationRegex = regexp.MustCompile(`[.,]+`) +) - m := pkg.DotnetDepsEntry{ - Name: name, - Version: version, - Path: lib.Path, - Sha512: lib.Sha512, - HashPath: lib.HashPath, +// newDotnetDepsPackage creates a new Dotnet dependency package from a logicalDepsJSONPackage. +// Note that the new logicalDepsJSONPackage now directly holds library and executable information. +func newDotnetDepsPackage(lp logicalDepsJSONPackage, depsLocation file.Location) *pkg.Package { + name, ver := extractNameAndVersion(lp.NameVersion) + locs := file.NewLocationSet(depsLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) + + for _, pe := range lp.Executables { + locs.Add(pe.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) } + m := newDotnetDepsEntry(lp) + p := &pkg.Package{ Name: name, - Version: version, - Locations: file.NewLocationSet(locations...), + Version: ver, + Locations: locs, PURL: packageURL(m), Language: pkg.Dotnet, Type: pkg.DotnetPkg, @@ -36,6 +46,66 @@ func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations . return p } +// newDotnetDepsEntry creates a Dotnet dependency entry using the new logicalDepsJSONPackage. +func newDotnetDepsEntry(lp logicalDepsJSONPackage) pkg.DotnetDepsEntry { + name, ver := extractNameAndVersion(lp.NameVersion) + + // since this is a metadata type, we should not allocate this collection unless there are entries; otherwise + // the JSON serialization will produce an empty object instead of omitting the field. + var pes map[string]pkg.DotnetPortableExecutableEntry + if len(lp.Executables) > 0 { + pes = make(map[string]pkg.DotnetPortableExecutableEntry) + for _, pe := range lp.Executables { + pes[pe.TargetPath] = newDotnetPortableExecutableEntry(pe) + } + } + + var path, sha, hashPath string + lib := lp.Library + if lib != nil { + path = lib.Path + sha = lib.Sha512 + hashPath = lib.HashPath + } + + return pkg.DotnetDepsEntry{ + Name: name, + Version: ver, + Path: path, + Sha512: sha, + HashPath: hashPath, + Executables: pes, + } +} + +// newDotnetPortableExecutableEntry creates a portable executable entry from a logicalPE. +func newDotnetPortableExecutableEntry(pe logicalPE) pkg.DotnetPortableExecutableEntry { + return newDotnetPortableExecutableEntryFromMap(pe.VersionResources) +} + +func newDotnetPortableExecutableEntryFromMap(vr map[string]string) pkg.DotnetPortableExecutableEntry { + return pkg.DotnetPortableExecutableEntry{ + // for some reason, the assembly version is sometimes stored as "Assembly Version" and sometimes as "AssemblyVersion" + AssemblyVersion: cleanVersionResourceField(vr["Assembly Version"], vr["AssemblyVersion"]), + LegalCopyright: cleanVersionResourceField(vr["LegalCopyright"]), + Comments: cleanVersionResourceField(vr["Comments"]), + InternalName: cleanVersionResourceField(vr["InternalName"]), + CompanyName: cleanVersionResourceField(vr["CompanyName"]), + ProductName: cleanVersionResourceField(vr["ProductName"]), + ProductVersion: cleanVersionResourceField(vr["ProductVersion"]), + } +} + +func cleanVersionResourceField(values ...string) string { + for _, value := range values { + if value == "" { + continue + } + return strings.TrimSpace(value) + } + return "" +} + func getDepsJSONFilePrefix(p string) string { r := regexp.MustCompile(`([^\\\/]+)\.deps\.json$`) match := r.FindStringSubmatch(p) @@ -48,7 +118,9 @@ func getDepsJSONFilePrefix(p string) string { func extractNameAndVersion(nameVersion string) (name, version string) { fields := strings.Split(nameVersion, "/") name = fields[0] - version = fields[1] + if len(fields) > 1 { + version = fields[1] + } return } @@ -61,16 +133,8 @@ func packageURL(m pkg.DotnetDepsEntry) string { var qualifiers packageurl.Qualifiers return packageurl.NewPackageURL( - // This originally was packageurl.TypeDotnet, but this isn't a valid PURL type, according to: - // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst - // Some history: - // https://github.com/anchore/packageurl-go/pull/8 added the type to Anchore's fork - // due to this PR: https://github.com/anchore/syft/pull/951 - // There were questions about "dotnet" being the right purlType at the time, but it was - // acknowledged that scanning a dotnet file does not necessarily mean the packages found - // are nuget packages and so the alternate type was added. Since this is still an invalid - // PURL type, however, we will use TypeNuget and revisit at such time there is a better - // official PURL type available. + // Although we use TypeNuget here due to historical reasons, note that it does not necessarily + // mean the package is a NuGet package. packageurl.TypeNuget, "", m.Name, @@ -79,3 +143,155 @@ func packageURL(m pkg.DotnetDepsEntry) string { "", ).ToString() } + +func newDotnetBinaryPackage(versionResources map[string]string, f file.Location) pkg.Package { + name := findNameFromVersionResources(versionResources) + ver := findVersionFromVersionResources(versionResources) + + metadata := newDotnetPortableExecutableEntryFromMap(versionResources) + + p := pkg.Package{ + Name: name, + Version: ver, + Locations: file.NewLocationSet(f.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Type: pkg.DotnetPkg, + Language: pkg.Dotnet, + PURL: binaryPackageURL(name, ver), + Metadata: metadata, + } + + p.SetID() + + return p +} + +func binaryPackageURL(name, version string) string { + if name == "" { + return "" + } + return packageurl.NewPackageURL( + packageurl.TypeNuget, + "", + name, + version, + nil, + "", + ).ToString() +} + +func findNameFromVersionResources(versionResources map[string]string) string { + // PE files not authored by Microsoft tend to use ProductName as an identifier. + nameFields := []string{"ProductName", "FileDescription", "InternalName", "OriginalFilename"} + + if isMicrosoftVersionResource(versionResources) { + // For Microsoft files, prioritize FileDescription. + nameFields = []string{"FileDescription", "InternalName", "OriginalFilename", "ProductName"} + } + + for _, field := range nameFields { + value := spaceNormalize(versionResources[field]) + if value == "" { + continue + } + return value + } + + return "" +} + +func isMicrosoftVersionResource(versionResources map[string]string) bool { + return strings.Contains(strings.ToLower(versionResources["CompanyName"]), "microsoft") || + strings.Contains(strings.ToLower(versionResources["ProductName"]), "microsoft") +} + +// spaceNormalize trims and normalizes whitespace in a string. +func spaceNormalize(value string) string { + value = strings.TrimSpace(value) + if value == "" { + return "" + } + // Ensure valid UTF-8. + value = strings.ToValidUTF8(value, "") + // Consolidate all whitespace. + value = spaceRegex.ReplaceAllString(value, " ") + // Remove non-printable characters. + value = regexp.MustCompile(`[\x00-\x1f]`).ReplaceAllString(value, "") + // Consolidate again and trim. + value = spaceRegex.ReplaceAllString(value, " ") + value = strings.TrimSpace(value) + return value +} + +func findVersionFromVersionResources(versionResources map[string]string) string { + productVersion := extractVersionFromResourcesValue(versionResources["ProductVersion"]) + fileVersion := extractVersionFromResourcesValue(versionResources["FileVersion"]) + + semanticVersionCompareResult := keepGreaterSemanticVersion(productVersion, fileVersion) + if semanticVersionCompareResult != "" { + return semanticVersionCompareResult + } + + productVersionDetail := punctuationCount(productVersion) + fileVersionDetail := punctuationCount(fileVersion) + + if containsNumber(productVersion) && productVersionDetail >= fileVersionDetail { + return productVersion + } + if containsNumber(fileVersion) && fileVersionDetail > 0 { + return fileVersion + } + if containsNumber(productVersion) { + return productVersion + } + if containsNumber(fileVersion) { + return fileVersion + } + + return productVersion +} + +func extractVersionFromResourcesValue(version string) string { + version = strings.TrimSpace(version) + out := "" + for i, f := range strings.Fields(version) { + if containsNumber(out) && !containsNumber(f) { + return out + } + if i == 0 { + out = f + } else { + out += " " + f + } + } + return out +} + +func keepGreaterSemanticVersion(productVersion string, fileVersion string) string { + semanticProductVersion, err := version.NewVersion(productVersion) + if err != nil || semanticProductVersion == nil { + log.Tracef("Unable to create semantic version from product version %s", productVersion) + return "" + } + + semanticFileVersion, err := version.NewVersion(fileVersion) + if err != nil || semanticFileVersion == nil { + log.Tracef("Unable to create semantic version from file version %s", fileVersion) + return productVersion + } + + if semanticProductVersion.Equal(semanticFileVersion) { + return "" + } + if semanticFileVersion.GreaterThan(semanticProductVersion) { + return fileVersion + } + return productVersion +} + +func containsNumber(s string) bool { + return numberRegex.MatchString(s) +} + +func punctuationCount(s string) int { + return len(versionPunctuationRegex.FindAllString(s, -1)) +} diff --git a/syft/pkg/cataloger/dotnet/package_test.go b/syft/pkg/cataloger/dotnet/package_test.go index 6ff473a89..32bfe0e54 100644 --- a/syft/pkg/cataloger/dotnet/package_test.go +++ b/syft/pkg/cataloger/dotnet/package_test.go @@ -4,6 +4,10 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" ) func Test_getDepsJSONFilePrefix(t *testing.T) { @@ -39,3 +43,287 @@ func Test_getDepsJSONFilePrefix(t *testing.T) { }) } } + +func Test_NewDotnetBinaryPackage(t *testing.T) { + tests := []struct { + name string + versionResources map[string]string + expectedPackage pkg.Package + }{ + { + name: "dotnet package with extra version info", + versionResources: map[string]string{ + "InternalName": "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll", + "FileVersion": "3.14.40721.0918 xxxfffdddjjjj", + "FileDescription": "Active Directory Authentication Library", + "ProductName": "Active Directory Authentication Library", + "Comments": "", + "CompanyName": "Microsoft Corporation", + "LegalTrademarks": "", + "LegalCopyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "OriginalFilename": "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll", + "ProductVersion": "c61f043686a544863efc014114c42e844f905336", + "Assembly Version": "3.14.2.11", + }, + expectedPackage: pkg.Package{ + Name: "Active Directory Authentication Library", + Version: "3.14.40721.0918", + Metadata: pkg.DotnetPortableExecutableEntry{ + AssemblyVersion: "3.14.2.11", + LegalCopyright: "Copyright (c) Microsoft Corporation. All rights reserved.", + InternalName: "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll", + CompanyName: "Microsoft Corporation", + ProductName: "Active Directory Authentication Library", + ProductVersion: "c61f043686a544863efc014114c42e844f905336", + }, + }, + }, + { + // show we can do a best effort to make a package from bad data + name: "dotnet package with malformed field and extended version", + versionResources: map[string]string{ + "CompanyName": "Microsoft Corporation", + "FileDescription": "äbFile\xa0\xa1Versi on", + "FileVersion": "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db", + "InternalName": "äbFileVersion", + "LegalCopyright": "© Microsoft Corporation. All rights reserved.", + "OriginalFilename": "TProductName", + "ProductName": "Microsoft® .NET Framework", + "ProductVersion": "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db", + }, + expectedPackage: pkg.Package{ + Name: "äbFileVersi on", + Version: "4.6.25512.01", + PURL: "pkg:nuget/%C3%A4bFileVersi%20on@4.6.25512.01", + Metadata: pkg.DotnetPortableExecutableEntry{ + LegalCopyright: "© Microsoft Corporation. All rights reserved.", + InternalName: "äb\x01FileVersion", + CompanyName: "Microsoft Corporation", + ProductName: "Microsoft® .NET Framework", + ProductVersion: "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db", + }, + }, + }, + { + name: "System.Data.Linq.dll", + versionResources: map[string]string{ + "CompanyName": "Microsoft Corporation", + "FileDescription": "System.Data.Linq.dll", + "FileVersion": "4.7.3190.0 built by: NET472REL1LAST_C", + "InternalName": "System.Data.Linq.dll", + "LegalCopyright": "© Microsoft Corporation. All rights reserved.", + "OriginalFilename": "System.Data.Linq.dll", + "ProductName": "Microsoft® .NET Framework", + "ProductVersion": "4.7.3190.0", + }, + expectedPackage: pkg.Package{ + Name: "System.Data.Linq.dll", + Version: "4.7.3190.0", + }, + }, + { + name: "curl", + versionResources: map[string]string{ + "CompanyName": "curl, https://curl.se/", + "FileDescription": "The curl executable", + "FileVersion": "8.4.0", + "InternalName": "curl", + "LegalCopyright": "© Daniel Stenberg, .", + "OriginalFilename": "curl.exe", + "ProductName": "The curl executable", + "ProductVersion": "8.4.0", + }, + expectedPackage: pkg.Package{ + Name: "The curl executable", + Version: "8.4.0", + }, + }, + { + name: "Prometheus", + versionResources: map[string]string{ + "AssemblyVersion": "8.0.0.0", + "CompanyName": "", + "FileDescription": "", + "FileVersion": "8.0.1", + "InternalName": "Prometheus.AspNetCore.dll", + "OriginalFilename": "Prometheus.AspNetCore.dll", + "ProductName": "", + "ProductVersion": "8.0.1", + }, + expectedPackage: pkg.Package{ + Name: "Prometheus.AspNetCore.dll", + Version: "8.0.1", + }, + }, + { + name: "Hidden Input", + versionResources: map[string]string{ + "FileDescription": "Reads from stdin without leaking info to the terminal and outputs back to stdout", + "FileVersion": "1, 0, 0, 0", + "InternalName": "hiddeninput", + "LegalCopyright": "Jordi Boggiano - 2012", + "OriginalFilename": "hiddeninput.exe", + "ProductName": "Hidden Input", + "ProductVersion": "1, 0, 0, 0", + }, + expectedPackage: pkg.Package{ + Name: "Hidden Input", + Version: "1, 0, 0, 0", + }, + }, + { + name: "SQLite3", + versionResources: map[string]string{ + "CompanyName": "SQLite Development Team", + "FileDescription": "SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine.", + "FileVersion": "3.23.2", + "InternalName": "sqlite3", + "LegalCopyright": "http://www.sqlite.org/copyright.html", + "ProductName": "SQLite", + "ProductVersion": "3.23.2", + }, + expectedPackage: pkg.Package{ + Name: "SQLite", + Version: "3.23.2", + }, + }, + { + name: "Brave Browser", + versionResources: map[string]string{ + "CompanyName": "Brave Software, Inc.", + "FileDescription": "Brave Browser", + "FileVersion": "80.1.7.92", + "InternalName": "chrome_exe", + "LegalCopyright": "Copyright 2016 The Brave Authors. All rights reserved.", + "OriginalFilename": "chrome.exe", + "ProductName": "Brave Browser", + "ProductVersion": "80.1.7.92", + }, + expectedPackage: pkg.Package{ + Name: "Brave Browser", + Version: "80.1.7.92", + }, + }, + { + name: "Better product version", + versionResources: map[string]string{ + "FileDescription": "Better version", + "FileVersion": "80.1.7", + "ProductVersion": "80.1.7.92", + }, + expectedPackage: pkg.Package{ + Name: "Better version", + Version: "80.1.7.92", + }, + }, + { + name: "Better file version", + versionResources: map[string]string{ + "FileDescription": "Better version", + "FileVersion": "80.1.7.92", + "ProductVersion": "80.1.7", + }, + expectedPackage: pkg.Package{ + Name: "Better version", + Version: "80.1.7.92", + }, + }, + { + name: "Higher semantic version Product Version", + versionResources: map[string]string{ + "FileDescription": "Higher semantic version Product Version", + "FileVersion": "3.0.0.0", + "ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + }, + expectedPackage: pkg.Package{ + Name: "Higher semantic version Product Version", + Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + }, + }, + { + name: "Higher semantic version File Version", + versionResources: map[string]string{ + "FileDescription": "Higher semantic version File Version", + "FileVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + "ProductVersion": "3.0.0", + }, + expectedPackage: pkg.Package{ + Name: "Higher semantic version File Version", + Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + }, + }, + { + name: "Invalid semantic version File Version", + versionResources: map[string]string{ + "FileDescription": "Invalid semantic version File Version", + "FileVersion": "A", + "ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + }, + expectedPackage: pkg.Package{ + Name: "Invalid semantic version File Version", + Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + }, + }, + { + name: "Invalid semantic version File Version", + versionResources: map[string]string{ + "FileDescription": "Invalid semantic version File Version", + "FileVersion": "A", + "ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + }, + expectedPackage: pkg.Package{ + Name: "Invalid semantic version File Version", + Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + }, + }, + { + name: "Invalid semantic version Product Version", + versionResources: map[string]string{ + "FileDescription": "Invalid semantic version Product Version", + "FileVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + "ProductVersion": "A", + }, + expectedPackage: pkg.Package{ + Name: "Invalid semantic version Product Version", + Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", + }, + }, + { + name: "Semantically equal falls through, chooses File Version with more components", + versionResources: map[string]string{ + "FileDescription": "Semantically equal falls through, chooses File Version with more components", + "FileVersion": "3.0.0.0", + "ProductVersion": "3.0.0", + }, + expectedPackage: pkg.Package{ + Name: "Semantically equal falls through, chooses File Version with more components", + Version: "3.0.0.0", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + location := file.NewLocation("") + got := newDotnetBinaryPackage(tc.versionResources, location) + + // ignore certain metadata + if tc.expectedPackage.Metadata == nil { + got.Metadata = nil + } + // set known defaults + if tc.expectedPackage.Type == "" { + tc.expectedPackage.Type = pkg.DotnetPkg + } + if tc.expectedPackage.Language == "" { + tc.expectedPackage.Language = pkg.Dotnet + } + if tc.expectedPackage.PURL == "" { + tc.expectedPackage.PURL = binaryPackageURL(tc.expectedPackage.Name, tc.expectedPackage.Version) + } + tc.expectedPackage.Locations = file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) + + pkgtest.AssertPackagesEqual(t, tc.expectedPackage, got) + }) + } +} diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go b/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go deleted file mode 100644 index 2d980ca4e..000000000 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go +++ /dev/null @@ -1,131 +0,0 @@ -package dotnet - -import ( - "context" - "encoding/json" - "fmt" - "sort" - - "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/internal/relationship" - "github.com/anchore/syft/internal/unknown" - "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/file" - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/generic" -) - -var _ generic.Parser = parseDotnetDeps - -type dotnetDeps struct { - RuntimeTarget dotnetRuntimeTarget `json:"runtimeTarget"` - Targets map[string]map[string]dotnetDepsTarget `json:"targets"` - Libraries map[string]dotnetDepsLibrary `json:"libraries"` -} - -type dotnetRuntimeTarget struct { - Name string `json:"name"` -} - -type dotnetDepsTarget struct { - Dependencies map[string]string `json:"dependencies"` - Runtime map[string]struct{} `json:"runtime"` -} - -type dotnetDepsLibrary struct { - Type string `json:"type"` - Path string `json:"path"` - Sha512 string `json:"sha512"` - HashPath string `json:"hashPath"` -} - -//nolint:funlen -func parseDotnetDeps(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { - var pkgs []pkg.Package - var pkgMap = make(map[string]pkg.Package) - var relationships []artifact.Relationship - - dec := json.NewDecoder(reader) - - var depsDoc dotnetDeps - if err := dec.Decode(&depsDoc); err != nil { - return nil, nil, fmt.Errorf("failed to parse deps.json file: %w", err) - } - - rootName := getDepsJSONFilePrefix(reader.Path()) - if rootName == "" { - return nil, nil, fmt.Errorf("unable to determine root package name from deps.json file: %s", reader.Path()) - } - var rootPkg *pkg.Package - for nameVersion, lib := range depsDoc.Libraries { - name, _ := extractNameAndVersion(nameVersion) - if lib.Type == "project" && name == rootName { - rootPkg = newDotnetDepsPackage( - nameVersion, - lib, - reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), - ) - } - } - if rootPkg == nil { - return nil, nil, fmt.Errorf("unable to determine root package from deps.json file: %s", reader.Path()) - } - pkgs = append(pkgs, *rootPkg) - pkgMap[createNameAndVersion(rootPkg.Name, rootPkg.Version)] = *rootPkg - - var names []string - for nameVersion := range depsDoc.Libraries { - names = append(names, nameVersion) - } - // sort the names so that the order of the packages is deterministic - sort.Strings(names) - - for _, nameVersion := range names { - // skip the root package - name, version := extractNameAndVersion(nameVersion) - if name == rootPkg.Name && version == rootPkg.Version { - continue - } - - lib := depsDoc.Libraries[nameVersion] - dotnetPkg := newDotnetDepsPackage( - nameVersion, - lib, - reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), - ) - - if dotnetPkg != nil { - pkgs = append(pkgs, *dotnetPkg) - pkgMap[nameVersion] = *dotnetPkg - } - } - - for pkgNameVersion, target := range depsDoc.Targets[depsDoc.RuntimeTarget.Name] { - for depName, depVersion := range target.Dependencies { - depNameVersion := createNameAndVersion(depName, depVersion) - depPkg, ok := pkgMap[depNameVersion] - if !ok { - log.Debug("unable to find package in map", depNameVersion) - continue - } - p, ok := pkgMap[pkgNameVersion] - if !ok { - log.Debug("unable to find package in map", pkgNameVersion) - continue - } - rel := artifact.Relationship{ - From: depPkg, - To: p, - Type: artifact.DependencyOfRelationship, - } - relationships = append(relationships, rel) - } - } - - // sort the relationships for deterministic output - // TODO: ideally this would be replaced with artifact.SortRelationships when one exists and is type agnostic. - // this will only consider package-to-package relationships. - relationship.Sort(relationships) - - return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages") -} diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go b/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go deleted file mode 100644 index 84878c86c..000000000 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go +++ /dev/null @@ -1,366 +0,0 @@ -package dotnet - -import ( - "testing" - - "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/file" - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" -) - -func Test_corruptDotnetDeps(t *testing.T) { - pkgtest.NewCatalogTester(). - FromFile(t, "test-fixtures/glob-paths/src/something.deps.json"). - WithError(). - TestParser(t, parseDotnetDeps) -} - -func TestParseDotnetDeps(t *testing.T) { - fixture := "test-fixtures/TestLibrary.deps.json" - fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture)) - rootPkg := pkg.Package{ - Name: "TestLibrary", - Version: "1.0.0", - PURL: "pkg:nuget/TestLibrary@1.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "TestLibrary", - Version: "1.0.0", - }, - } - testCommon := pkg.Package{ - Name: "TestCommon", - Version: "1.0.0", - PURL: "pkg:nuget/TestCommon@1.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "TestCommon", - Version: "1.0.0", - }, - } - awssdkcore := pkg.Package{ - Name: "AWSSDK.Core", - Version: "3.7.10.6", - PURL: "pkg:nuget/AWSSDK.Core@3.7.10.6", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "AWSSDK.Core", - Version: "3.7.10.6", - Sha512: "sha512-kHBB+QmosVaG6DpngXQ8OlLVVNMzltNITfsRr68Z90qO7dSqJ2EHNd8dtBU1u3AQQLqqFHOY0lfmbpexeH6Pew==", - Path: "awssdk.core/3.7.10.6", - HashPath: "awssdk.core.3.7.10.6.nupkg.sha512", - }, - } - msftDependencyInjectionAbstractions := pkg.Package{ - Name: "Microsoft.Extensions.DependencyInjection.Abstractions", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection.Abstractions@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "Microsoft.Extensions.DependencyInjection.Abstractions", - Version: "6.0.0", - Sha512: "sha512-xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==", - Path: "microsoft.extensions.dependencyinjection.abstractions/6.0.0", - HashPath: "microsoft.extensions.dependencyinjection.abstractions.6.0.0.nupkg.sha512", - }, - } - msftDependencyInjection := pkg.Package{ - Name: "Microsoft.Extensions.DependencyInjection", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "Microsoft.Extensions.DependencyInjection", - Version: "6.0.0", - Sha512: "sha512-k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", - Path: "microsoft.extensions.dependencyinjection/6.0.0", - HashPath: "microsoft.extensions.dependencyinjection.6.0.0.nupkg.sha512", - }, - } - msftLoggingAbstractions := pkg.Package{ - Name: "Microsoft.Extensions.Logging.Abstractions", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.Logging.Abstractions@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "Microsoft.Extensions.Logging.Abstractions", - Version: "6.0.0", - Sha512: "sha512-/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==", - Path: "microsoft.extensions.logging.abstractions/6.0.0", - HashPath: "microsoft.extensions.logging.abstractions.6.0.0.nupkg.sha512", - }, - } - msftExtensionsLogging := pkg.Package{ - Name: "Microsoft.Extensions.Logging", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.Logging@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "Microsoft.Extensions.Logging", - Version: "6.0.0", - Sha512: "sha512-eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", - Path: "microsoft.extensions.logging/6.0.0", - HashPath: "microsoft.extensions.logging.6.0.0.nupkg.sha512", - }, - } - msftExtensionsOptions := pkg.Package{ - Name: "Microsoft.Extensions.Options", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.Options@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "Microsoft.Extensions.Options", - Version: "6.0.0", - Sha512: "sha512-dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", - Path: "microsoft.extensions.options/6.0.0", - HashPath: "microsoft.extensions.options.6.0.0.nupkg.sha512", - }, - } - msftExtensionsPrimitives := pkg.Package{ - Name: "Microsoft.Extensions.Primitives", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.Primitives@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "Microsoft.Extensions.Primitives", - Version: "6.0.0", - Sha512: "sha512-9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", - Path: "microsoft.extensions.primitives/6.0.0", - HashPath: "microsoft.extensions.primitives.6.0.0.nupkg.sha512", - }, - } - newtonsoftJson := pkg.Package{ - Name: "Newtonsoft.Json", - Version: "13.0.1", - PURL: "pkg:nuget/Newtonsoft.Json@13.0.1", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "Newtonsoft.Json", - Version: "13.0.1", - Sha512: "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==", - Path: "newtonsoft.json/13.0.1", - HashPath: "newtonsoft.json.13.0.1.nupkg.sha512", - }, - } - serilogSinksConsole := pkg.Package{ - Name: "Serilog.Sinks.Console", - Version: "4.0.1", - PURL: "pkg:nuget/Serilog.Sinks.Console@4.0.1", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "Serilog.Sinks.Console", - Version: "4.0.1", - Sha512: "sha512-apLOvSJQLlIbKlbx+Y2UDHSP05kJsV7mou+fvJoRGs/iR+jC22r8cuFVMjjfVxz/AD4B2UCltFhE1naRLXwKNw==", - Path: "serilog.sinks.console/4.0.1", - HashPath: "serilog.sinks.console.4.0.1.nupkg.sha512", - }, - } - serilog := pkg.Package{ - Name: "Serilog", - Version: "2.10.0", - PURL: "pkg:nuget/Serilog@2.10.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "Serilog", - Version: "2.10.0", - Sha512: "sha512-+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==", - Path: "serilog/2.10.0", - HashPath: "serilog.2.10.0.nupkg.sha512", - }, - } - systemDiagnosticsDiagnosticsource := pkg.Package{ - Name: "System.Diagnostics.DiagnosticSource", - Version: "6.0.0", - PURL: "pkg:nuget/System.Diagnostics.DiagnosticSource@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "System.Diagnostics.DiagnosticSource", - Version: "6.0.0", - Sha512: "sha512-frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==", - Path: "system.diagnostics.diagnosticsource/6.0.0", - HashPath: "system.diagnostics.diagnosticsource.6.0.0.nupkg.sha512", - }, - } - systemRuntimeCompilerServicesUnsafe := pkg.Package{ - Name: "System.Runtime.CompilerServices.Unsafe", - Version: "6.0.0", - PURL: "pkg:nuget/System.Runtime.CompilerServices.Unsafe@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - Metadata: pkg.DotnetDepsEntry{ - Name: "System.Runtime.CompilerServices.Unsafe", - Version: "6.0.0", - Sha512: "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", - Path: "system.runtime.compilerservices.unsafe/6.0.0", - HashPath: "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512", - }} - - expectedPkgs := []pkg.Package{ - awssdkcore, - msftDependencyInjection, - msftDependencyInjectionAbstractions, - msftExtensionsLogging, - msftLoggingAbstractions, - msftExtensionsOptions, - msftExtensionsPrimitives, - newtonsoftJson, - serilog, - serilogSinksConsole, - systemDiagnosticsDiagnosticsource, - systemRuntimeCompilerServicesUnsafe, - testCommon, - rootPkg, - } - - // ┌── (✓ = is represented in the test) - // ↓ - // - // ✓ TestLibrary/1.0.0 (project) - // ✓ ├── [a] Microsoft.Extensions.DependencyInjection/6.0.0 [file version: 6.0.21.52210] - // ✓ │ ├── [b] Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 [file version: 6.0.21.52210] - // ✓ │ └── [c!] System.Runtime.CompilerServices.Unsafe/6.0.0 [NO TARGET INFO] - // ✓ ├── Microsoft.Extensions.Logging/6.0.0 [file version: 6.0.21.52210] - // ✓ │ ├── Microsoft.Extensions.DependencyInjection/6.0.0 ...to [a] - // ✓ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b] - // ✓ │ ├── Microsoft.Extensions.Logging.Abstractions/6.0.0 [file version: 6.0.21.52210] - // ✓ │ ├── Microsoft.Extensions.Options/6.0.0 [file version: 6.0.21.52210] - // ✓ │ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b] - // ✓ │ │ └── Microsoft.Extensions.Primitives/6.0.0 [file version: 6.0.21.52210] - // ✓ │ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!] - // ✓ │ └── System.Diagnostics.DiagnosticSource/6.0.0 [NO RUNTIME INFO] - // ✓ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!] - // ✓ ├── Newtonsoft.Json/13.0.1 [file version: 13.0.1.25517] - // ✓ ├── [d] Serilog/2.10.0 [file version: 2.10.0.0] - // ✓ ├── Serilog.Sinks.Console/4.0.1 [file version: 4.0.1.0] - // ✓ │ └── Serilog/2.10.0 ...to [d] - // ✓ └── [e!] TestCommon/1.0.0 [NOT SERVICEABLE / NO SHA] - // ✓ └── AWSSDK.Core/3.7.10.6 [file version: 3.7.10.6] - - expectedRelationships := []artifact.Relationship{ - { - From: awssdkcore, - To: testCommon, - Type: artifact.DependencyOfRelationship, - }, - { - From: msftDependencyInjection, - To: msftExtensionsLogging, - Type: artifact.DependencyOfRelationship, - }, - { - From: msftDependencyInjection, - To: rootPkg, - Type: artifact.DependencyOfRelationship, - }, - { - From: msftDependencyInjectionAbstractions, - To: msftDependencyInjection, - Type: artifact.DependencyOfRelationship, - }, - { - From: msftDependencyInjectionAbstractions, - To: msftExtensionsLogging, - Type: artifact.DependencyOfRelationship, - }, - { - From: msftDependencyInjectionAbstractions, - To: msftExtensionsOptions, - Type: artifact.DependencyOfRelationship, - }, - { - From: msftExtensionsLogging, - To: rootPkg, - Type: artifact.DependencyOfRelationship, - }, - { - From: msftLoggingAbstractions, - To: msftExtensionsLogging, - Type: artifact.DependencyOfRelationship, - }, - { - From: msftExtensionsOptions, - To: msftExtensionsLogging, - Type: artifact.DependencyOfRelationship, - }, - { - From: msftExtensionsPrimitives, - To: msftExtensionsOptions, - Type: artifact.DependencyOfRelationship, - }, - { - From: newtonsoftJson, - To: rootPkg, - Type: artifact.DependencyOfRelationship, - }, - { - From: serilog, - To: serilogSinksConsole, - Type: artifact.DependencyOfRelationship, - }, - { - From: serilog, - To: rootPkg, - Type: artifact.DependencyOfRelationship, - }, - { - From: serilogSinksConsole, - To: rootPkg, - Type: artifact.DependencyOfRelationship, - }, - { - From: systemDiagnosticsDiagnosticsource, - To: msftExtensionsLogging, - Type: artifact.DependencyOfRelationship, - }, - { - From: systemRuntimeCompilerServicesUnsafe, - To: msftDependencyInjection, - Type: artifact.DependencyOfRelationship, - }, - { - From: systemRuntimeCompilerServicesUnsafe, - To: msftExtensionsPrimitives, - Type: artifact.DependencyOfRelationship, - }, - { - From: systemRuntimeCompilerServicesUnsafe, - To: systemDiagnosticsDiagnosticsource, - Type: artifact.DependencyOfRelationship, - }, - { - From: testCommon, - To: rootPkg, - Type: artifact.DependencyOfRelationship, - }, - } - - pkgtest.TestFileParser(t, fixture, parseDotnetDeps, expectedPkgs, expectedRelationships) -} diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_portable_executable.go b/syft/pkg/cataloger/dotnet/parse_dotnet_portable_executable.go deleted file mode 100644 index fbf5e860e..000000000 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_portable_executable.go +++ /dev/null @@ -1,248 +0,0 @@ -package dotnet - -import ( - "context" - "fmt" - "io" - "regexp" - "strings" - - "github.com/saferwall/pe" - - version "github.com/anchore/go-version" - "github.com/anchore/packageurl-go" - "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/file" - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/generic" -) - -type dotnetPortableExecutableParser struct { - cfg CatalogerConfig -} - -func (p dotnetPortableExecutableParser) parseDotnetPortableExecutable(_ context.Context, _ file.Resolver, _ *generic.Environment, f file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { - by, err := io.ReadAll(f) - if err != nil { - return nil, nil, fmt.Errorf("unable to read file: %w", err) - } - - peFile, err := pe.NewBytes(by, &pe.Options{DisableCertValidation: !p.cfg.EnableCertificateValidation}) - if err != nil { - log.Tracef("unable to create PE instance for file '%s': %v", f.RealPath, err) - return nil, nil, err - } - - err = peFile.Parse() - if err != nil { - log.Tracef("unable to parse PE file '%s': %v", f.RealPath, err) - return nil, nil, err - } - - versionResources, err := peFile.ParseVersionResources() - if err != nil { - log.Tracef("unable to parse version resources in PE file: %s: %v", f.RealPath, err) - return nil, nil, fmt.Errorf("unable to parse version resources in PE file: %w", err) - } - - dotNetPkg, err := buildDotNetPackage(versionResources, f) - if err != nil { - log.Tracef("unable to build dotnet package for: %v %v", f.RealPath, err) - return nil, nil, err - } - - return []pkg.Package{dotNetPkg}, nil, nil -} - -func buildDotNetPackage(versionResources map[string]string, f file.LocationReadCloser) (dnpkg pkg.Package, err error) { - name := findName(versionResources) - if name == "" { - return dnpkg, fmt.Errorf("unable to find PE name in file") - } - - version := findVersion(versionResources) - if version == "" { - return dnpkg, fmt.Errorf("unable to find PE version in file") - } - - metadata := pkg.DotnetPortableExecutableEntry{ - AssemblyVersion: versionResources["Assembly Version"], - LegalCopyright: versionResources["LegalCopyright"], - Comments: versionResources["Comments"], - InternalName: versionResources["InternalName"], - CompanyName: versionResources["CompanyName"], - ProductName: versionResources["ProductName"], - ProductVersion: versionResources["ProductVersion"], - } - - dnpkg = pkg.Package{ - Name: name, - Version: version, - Locations: file.NewLocationSet(f.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), - Type: pkg.DotnetPkg, - Language: pkg.Dotnet, - PURL: portableExecutablePackageURL(name, version), - Metadata: metadata, - } - - dnpkg.SetID() - - return dnpkg, nil -} - -func portableExecutablePackageURL(name, version string) string { - return packageurl.NewPackageURL( - packageurl.TypeNuget, // See explanation in syft/pkg/cataloger/dotnet/package.go as to why this was chosen. - "", - name, - version, - nil, - "", - ).ToString() -} - -func extractVersion(version string) string { - version = strings.TrimSpace(version) - - out := "" - - // some example versions are: "1, 0, 0, 0", "Release 73" or "4.7.4076.0 built by: NET472REL1LAST_B" - // so try to split it and take the first parts that look numeric - for i, f := range strings.Fields(version) { - // if the output already has a number but the current segment does not have a number, - // return what we found for the version - if containsNumber(out) && !containsNumber(f) { - return out - } - - if i == 0 { - out = f - } else { - out += " " + f - } - } - - return out -} - -func keepGreaterSemanticVersion(productVersion string, fileVersion string) string { - semanticProductVersion, err := version.NewVersion(productVersion) - - if err != nil || semanticProductVersion == nil { - log.Tracef("Unable to create semantic version from portable executable product version %s", productVersion) - return "" - } - - semanticFileVersion, err := version.NewVersion(fileVersion) - - if err != nil || semanticFileVersion == nil { - log.Tracef("Unable to create semantic version from portable executable file version %s", fileVersion) - return productVersion - } - - // Make no choice when they are semantically equal so that it falls - // through to the other comparison cases - if semanticProductVersion.Equal(semanticFileVersion) { - return "" - } - - if semanticFileVersion.GreaterThan(semanticProductVersion) { - return fileVersion - } - - return productVersion -} - -func findVersion(versionResources map[string]string) string { - productVersion := extractVersion(versionResources["ProductVersion"]) - fileVersion := extractVersion(versionResources["FileVersion"]) - - semanticVersionCompareResult := keepGreaterSemanticVersion(productVersion, fileVersion) - - if semanticVersionCompareResult != "" { - return semanticVersionCompareResult - } - - productVersionDetail := punctuationCount(productVersion) - fileVersionDetail := punctuationCount(fileVersion) - - if containsNumber(productVersion) && productVersionDetail >= fileVersionDetail { - return productVersion - } - - if containsNumber(fileVersion) && fileVersionDetail > 0 { - return fileVersion - } - - if containsNumber(productVersion) { - return productVersion - } - - if containsNumber(fileVersion) { - return fileVersion - } - - return productVersion -} - -func containsNumber(s string) bool { - return numberRegex.MatchString(s) -} - -func punctuationCount(s string) int { - return len(versionPunctuationRegex.FindAllString(s, -1)) -} - -var ( - // spaceRegex includes nbsp (#160) considered to be a space character - spaceRegex = regexp.MustCompile(`[\s\xa0]+`) - numberRegex = regexp.MustCompile(`\d`) - versionPunctuationRegex = regexp.MustCompile(`[.,]+`) -) - -func findName(versionResources map[string]string) string { - // PE files found in the wild _not_ authored by Microsoft seem to use ProductName as a clear - // identifier of the software - nameFields := []string{"ProductName", "FileDescription", "InternalName", "OriginalFilename"} - - if isMicrosoft(versionResources) { - // Microsoft seems to be consistent using the FileDescription, with a few that are blank and have - // fallbacks to ProductName last, as this is often something very broad like "Microsoft Windows" - nameFields = []string{"FileDescription", "InternalName", "OriginalFilename", "ProductName"} - } - - for _, field := range nameFields { - value := spaceNormalize(versionResources[field]) - if value == "" { - continue - } - return value - } - - return "" -} - -// normalizes a string to a trimmed version with all contigous whitespace collapsed to a single space character -func spaceNormalize(value string) string { - value = strings.TrimSpace(value) - if value == "" { - return "" - } - // ensure valid utf8 text - value = strings.ToValidUTF8(value, "") - // consolidate all space characters - value = spaceRegex.ReplaceAllString(value, " ") - // remove other non-space, non-printable characters - value = regexp.MustCompile(`[\x00-\x1f]`).ReplaceAllString(value, "") - // consolidate all space characters again in case other non-printables were in-between - value = spaceRegex.ReplaceAllString(value, " ") - // finally, remove any remaining surrounding whitespace - value = strings.TrimSpace(value) - return value -} - -func isMicrosoft(versionResources map[string]string) bool { - return strings.Contains(strings.ToLower(versionResources["CompanyName"]), "microsoft") || - strings.Contains(strings.ToLower(versionResources["ProductName"]), "microsoft") -} diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_portable_executable_test.go b/syft/pkg/cataloger/dotnet/parse_dotnet_portable_executable_test.go deleted file mode 100644 index b92664e9b..000000000 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_portable_executable_test.go +++ /dev/null @@ -1,358 +0,0 @@ -package dotnet - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/anchore/syft/syft/file" - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" -) - -func TestParseDotnetPortableExecutable(t *testing.T) { - tests := []struct { - name string - versionResources map[string]string - expectedPackage pkg.Package - }{ - { - name: "dotnet package with extra version info", - versionResources: map[string]string{ - "InternalName": "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll", - "FileVersion": "3.14.40721.0918 xxxfffdddjjjj", - "FileDescription": "Active Directory Authentication Library", - "ProductName": "Active Directory Authentication Library", - "Comments": "", - "CompanyName": "Microsoft Corporation", - "LegalTrademarks": "", - "LegalCopyright": "Copyright (c) Microsoft Corporation. All rights reserved.", - "OriginalFilename": "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll", - "ProductVersion": "c61f043686a544863efc014114c42e844f905336", - "Assembly Version": "3.14.2.11", - }, - expectedPackage: pkg.Package{ - Name: "Active Directory Authentication Library", - Version: "3.14.40721.0918", - Metadata: pkg.DotnetPortableExecutableEntry{ - AssemblyVersion: "3.14.2.11", - LegalCopyright: "Copyright (c) Microsoft Corporation. All rights reserved.", - InternalName: "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll", - CompanyName: "Microsoft Corporation", - ProductName: "Active Directory Authentication Library", - ProductVersion: "c61f043686a544863efc014114c42e844f905336", - }, - }, - }, - { - name: "dotnet package with malformed field and extended version", - versionResources: map[string]string{ - "CompanyName": "Microsoft Corporation", - "FileDescription": "äbFile\xa0\xa1Versi on", - "FileVersion": "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db", - "InternalName": "äbFileVersion", - "LegalCopyright": "© Microsoft Corporation. All rights reserved.", - "OriginalFilename": "TProductName", - "ProductName": "Microsoft® .NET Framework", - "ProductVersion": "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db", - }, - expectedPackage: pkg.Package{ - Name: "äbFileVersi on", - Version: "4.6.25512.01", - PURL: "pkg:nuget/%C3%A4bFileVersi%20on@4.6.25512.01", - Metadata: pkg.DotnetPortableExecutableEntry{ - LegalCopyright: "© Microsoft Corporation. All rights reserved.", - InternalName: "äb\x01FileVersion", - CompanyName: "Microsoft Corporation", - ProductName: "Microsoft® .NET Framework", - ProductVersion: "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db", - }, - }, - }, - { - name: "System.Data.Linq.dll", - versionResources: map[string]string{ - "CompanyName": "Microsoft Corporation", - "FileDescription": "System.Data.Linq.dll", - "FileVersion": "4.7.3190.0 built by: NET472REL1LAST_C", - "InternalName": "System.Data.Linq.dll", - "LegalCopyright": "© Microsoft Corporation. All rights reserved.", - "OriginalFilename": "System.Data.Linq.dll", - "ProductName": "Microsoft® .NET Framework", - "ProductVersion": "4.7.3190.0", - }, - expectedPackage: pkg.Package{ - Name: "System.Data.Linq.dll", - Version: "4.7.3190.0", - }, - }, - { - name: "curl", - versionResources: map[string]string{ - "CompanyName": "curl, https://curl.se/", - "FileDescription": "The curl executable", - "FileVersion": "8.4.0", - "InternalName": "curl", - "LegalCopyright": "© Daniel Stenberg, .", - "OriginalFilename": "curl.exe", - "ProductName": "The curl executable", - "ProductVersion": "8.4.0", - }, - expectedPackage: pkg.Package{ - Name: "The curl executable", - Version: "8.4.0", - }, - }, - { - name: "Prometheus", - versionResources: map[string]string{ - "AssemblyVersion": "8.0.0.0", - "CompanyName": "", - "FileDescription": "", - "FileVersion": "8.0.1", - "InternalName": "Prometheus.AspNetCore.dll", - "OriginalFilename": "Prometheus.AspNetCore.dll", - "ProductName": "", - "ProductVersion": "8.0.1", - }, - expectedPackage: pkg.Package{ - Name: "Prometheus.AspNetCore.dll", - Version: "8.0.1", - }, - }, - { - name: "Hidden Input", - versionResources: map[string]string{ - "FileDescription": "Reads from stdin without leaking info to the terminal and outputs back to stdout", - "FileVersion": "1, 0, 0, 0", - "InternalName": "hiddeninput", - "LegalCopyright": "Jordi Boggiano - 2012", - "OriginalFilename": "hiddeninput.exe", - "ProductName": "Hidden Input", - "ProductVersion": "1, 0, 0, 0", - }, - expectedPackage: pkg.Package{ - Name: "Hidden Input", - Version: "1, 0, 0, 0", - }, - }, - { - name: "SQLite3", - versionResources: map[string]string{ - "CompanyName": "SQLite Development Team", - "FileDescription": "SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine.", - "FileVersion": "3.23.2", - "InternalName": "sqlite3", - "LegalCopyright": "http://www.sqlite.org/copyright.html", - "ProductName": "SQLite", - "ProductVersion": "3.23.2", - }, - expectedPackage: pkg.Package{ - Name: "SQLite", - Version: "3.23.2", - }, - }, - { - name: "Brave Browser", - versionResources: map[string]string{ - "CompanyName": "Brave Software, Inc.", - "FileDescription": "Brave Browser", - "FileVersion": "80.1.7.92", - "InternalName": "chrome_exe", - "LegalCopyright": "Copyright 2016 The Brave Authors. All rights reserved.", - "OriginalFilename": "chrome.exe", - "ProductName": "Brave Browser", - "ProductVersion": "80.1.7.92", - }, - expectedPackage: pkg.Package{ - Name: "Brave Browser", - Version: "80.1.7.92", - }, - }, - { - name: "Better product version", - versionResources: map[string]string{ - "FileDescription": "Better version", - "FileVersion": "80.1.7", - "ProductVersion": "80.1.7.92", - }, - expectedPackage: pkg.Package{ - Name: "Better version", - Version: "80.1.7.92", - }, - }, - { - name: "Better file version", - versionResources: map[string]string{ - "FileDescription": "Better version", - "FileVersion": "80.1.7.92", - "ProductVersion": "80.1.7", - }, - expectedPackage: pkg.Package{ - Name: "Better version", - Version: "80.1.7.92", - }, - }, - { - name: "Higher semantic version Product Version", - versionResources: map[string]string{ - "FileDescription": "Higher semantic version Product Version", - "FileVersion": "3.0.0.0", - "ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - }, - expectedPackage: pkg.Package{ - Name: "Higher semantic version Product Version", - Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - }, - }, - { - name: "Higher semantic version File Version", - versionResources: map[string]string{ - "FileDescription": "Higher semantic version File Version", - "FileVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - "ProductVersion": "3.0.0", - }, - expectedPackage: pkg.Package{ - Name: "Higher semantic version File Version", - Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - }, - }, - { - name: "Invalid semantic version File Version", - versionResources: map[string]string{ - "FileDescription": "Invalid semantic version File Version", - "FileVersion": "A", - "ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - }, - expectedPackage: pkg.Package{ - Name: "Invalid semantic version File Version", - Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - }, - }, - { - name: "Invalid semantic version File Version", - versionResources: map[string]string{ - "FileDescription": "Invalid semantic version File Version", - "FileVersion": "A", - "ProductVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - }, - expectedPackage: pkg.Package{ - Name: "Invalid semantic version File Version", - Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - }, - }, - { - name: "Invalid semantic version Product Version", - versionResources: map[string]string{ - "FileDescription": "Invalid semantic version Product Version", - "FileVersion": "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - "ProductVersion": "A", - }, - expectedPackage: pkg.Package{ - Name: "Invalid semantic version Product Version", - Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207", - }, - }, - { - name: "Semantically equal falls through, chooses File Version with more components", - versionResources: map[string]string{ - "FileDescription": "Semantically equal falls through, chooses File Version with more components", - "FileVersion": "3.0.0.0", - "ProductVersion": "3.0.0", - }, - expectedPackage: pkg.Package{ - Name: "Semantically equal falls through, chooses File Version with more components", - Version: "3.0.0.0", - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - location := file.NewLocation("") - f := file.LocationReadCloser{ - Location: location, - } - got, err := buildDotNetPackage(tc.versionResources, f) - assert.NoErrorf(t, err, "failed to build package from version resources: %+v", tc.versionResources) - - // ignore certain metadata - if tc.expectedPackage.Metadata == nil { - got.Metadata = nil - } - // set known defaults - if tc.expectedPackage.Type == "" { - tc.expectedPackage.Type = pkg.DotnetPkg - } - if tc.expectedPackage.Language == "" { - tc.expectedPackage.Language = pkg.Dotnet - } - if tc.expectedPackage.PURL == "" { - tc.expectedPackage.PURL = portableExecutablePackageURL(tc.expectedPackage.Name, tc.expectedPackage.Version) - } - tc.expectedPackage.Locations = file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) - - pkgtest.AssertPackagesEqual(t, tc.expectedPackage, got) - }) - } -} - -func Test_corruptDotnetPE(t *testing.T) { - p := dotnetPortableExecutableParser{ - cfg: DefaultCatalogerConfig(), - } - pkgtest.NewCatalogTester(). - FromFile(t, "test-fixtures/glob-paths/src/something.exe"). - WithError(). - TestParser(t, p.parseDotnetPortableExecutable) -} - -func Test_extractVersion(t *testing.T) { - tests := []struct { - input string - expected string - }{ - { - input: "1, 0, 0, 0", - expected: "1, 0, 0, 0", - }, - { - input: "Release 73", - expected: "Release 73", - }, - { - input: "4.7.4076.0 built by: NET472REL1LAST_B", - expected: "4.7.4076.0", - }, - } - - for _, test := range tests { - t.Run(test.input, func(t *testing.T) { - got := extractVersion(test.input) - assert.Equal(t, test.expected, got) - }) - } -} - -func Test_spaceNormalize(t *testing.T) { - tests := []struct { - input string - expected string - }{ - { - expected: "some spaces apart", - input: " some spaces\n\t\t \n\rapart\n", - }, - { - expected: "söme ¡nvalid characters", - input: "\rsöme \u0001¡nvalid\t characters\n", - }, - } - - for _, test := range tests { - t.Run(test.expected, func(t *testing.T) { - got := spaceNormalize(test.input) - assert.Equal(t, test.expected, got) - }) - } -} diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_packages_lock.go b/syft/pkg/cataloger/dotnet/parse_packages_lock.go similarity index 100% rename from syft/pkg/cataloger/dotnet/parse_dotnet_packages_lock.go rename to syft/pkg/cataloger/dotnet/parse_packages_lock.go diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_packages_lock_test.go b/syft/pkg/cataloger/dotnet/parse_packages_lock_test.go similarity index 99% rename from syft/pkg/cataloger/dotnet/parse_dotnet_packages_lock_test.go rename to syft/pkg/cataloger/dotnet/parse_packages_lock_test.go index 17ed88806..6059e28d8 100644 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_packages_lock_test.go +++ b/syft/pkg/cataloger/dotnet/parse_packages_lock_test.go @@ -13,7 +13,7 @@ func Test_corruptDotnetPackagesLock(t *testing.T) { pkgtest.NewCatalogTester(). FromFile(t, "test-fixtures/glob-paths/src/packages.lock.json"). WithError(). - TestParser(t, parseDotnetDeps) + TestParser(t, parseDotnetPackagesLock) } func TestParseDotnetPackagesLock(t *testing.T) { diff --git a/syft/pkg/cataloger/dotnet/pe.go b/syft/pkg/cataloger/dotnet/pe.go new file mode 100644 index 000000000..dd58eca49 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/pe.go @@ -0,0 +1,740 @@ +package dotnet + +import ( + "bytes" + "debug/pe" + "encoding/binary" + "errors" + "fmt" + "io" + "unicode/utf16" + + "github.com/scylladb/go-set/strset" + "github.com/scylladb/go-set/u32set" + + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/internal/unionreader" +) + +const peMaxAllowedDirectoryEntries = 0x1000 + +var imageDirectoryEntryIndexes = []int{ + pe.IMAGE_DIRECTORY_ENTRY_RESOURCE, // where version resources are stored + pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, // where info about the CLR is stored +} + +// logicalPE does not directly represent a binary shape to be parsed, instead it represents the +// information of interest extracted from a PE file. +type logicalPE struct { + // Location is where the PE file was found + Location file.Location + + // TargetPath is the path is the deps.json target entry. This is not present in the PE file + // but instead is used in downstream processing to track associations between the PE file and the deps.json file. + TargetPath string + + // CLR is the information about the CLR (common language runtime) version found in the PE file which helps + // understand if this executable is even a .NET application. + CLR *clrEvidence + + // VersionResources is a map of version resource keys to their values found in the VERSIONINFO resource directory. + VersionResources map[string]string +} + +// clrEvidence is basic info about the CLR (common language runtime) version from the COM descriptor. +// This is not a complete representation of the CLR version, but rather a subset of the information that is +// useful to us. +type clrEvidence struct { + // HasClrResourceNames is true if there are CLR resource names found in the PE file (e.g. "CLRDEBUGINFO"). + HasClrResourceNames bool + + // MajorVersion is the minimum supported major version of the CLR. + MajorVersion uint16 + + // MinorVersion is the minimum supported minor version of the CLR. + MinorVersion uint16 +} + +// hasEvidenceOfCLR returns true if the PE file has evidence of a CLR (common language runtime) version. +func (c *clrEvidence) hasEvidenceOfCLR() bool { + return c != nil && (c.MajorVersion != 0 && c.MinorVersion != 0 || c.HasClrResourceNames) +} + +type peDosHeader struct { + Magic [2]byte // "MZ" + Unused [58]byte + AddressOfNewEXEHeader uint32 // offset to PE header +} + +// peImageCore20 represents the .NET Core 2.0 header structure. +// Source: https://github.com/dotnet/msbuild/blob/9fa9d800dabce3bfcf8365f651f3a713e01f8a85/src/Tasks/NativeMethods.cs#L761-L775 +type peImageCore20 struct { + Cb uint32 + MajorRuntimeVersion uint16 + MinorRuntimeVersion uint16 +} + +// peImageResourceDirectory represents the resource directory structure. +type peImageResourceDirectory struct { + Characteristics uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + NumberOfNamedEntries uint16 + NumberOfIDEntries uint16 +} + +// peImageResourceDirectoryEntry represents an entry in the resource directory entries. +type peImageResourceDirectoryEntry struct { + Name uint32 + OffsetToData uint32 +} + +// peImageResourceDataEntry is the unit of raw data in the Resource Data area. +type peImageResourceDataEntry struct { + OffsetToData uint32 + Size uint32 + CodePage uint32 + Reserved uint32 +} + +// peVsFixedFileInfo represents the fixed file information structure. +type peVsFixedFileInfo struct { + Signature uint32 + StructVersion uint32 + FileVersionMS uint32 + FileVersionLS uint32 + ProductVersionMS uint32 + ProductVersionLS uint32 + FileFlagsMask uint32 + FileFlags uint32 + FileOS uint32 + FileType uint32 + FileSubtype uint32 + FileDateMS uint32 + FileDateLS uint32 +} + +type peVsVersionInfo peLenValLenType + +type peStringFileInfo peLenValLenType + +type peStringTable peLenValLenType + +type peString peLenValLenType + +type peLenValLenType struct { + Length uint16 + ValueLength uint16 + Type uint16 +} + +type extractedSection struct { + RVA uint32 + BaseRVA uint32 + Size uint32 + Reader *bytes.Reader +} + +func (s extractedSection) exists() bool { + return s.RVA != 0 && s.Size != 0 +} + +func directoryName(i int) string { + switch i { + case pe.IMAGE_DIRECTORY_ENTRY_RESOURCE: + return "Resource" + case pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR: + return "COM Descriptor" + } + return fmt.Sprintf("Unknown (%d)", i) +} + +func getLogicalDotnetPE(f file.LocationReadCloser) (*logicalPE, error) { + r, err := unionreader.GetUnionReader(f) + if err != nil { + return nil, err + } + + sections, _, err := parsePEFile(r) + if err != nil { + return nil, fmt.Errorf("unable to parse PE sections: %w", err) + } + + dirs := u32set.New() // keep track of the RVAs we have already parsed (prevent infinite recursion edge cases) + versionResources := make(map[string]string) // map of version resource keys to their values + resourceNames := strset.New() // set of resource names found in the PE file + err = parseResourceDirectory(sections[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE], dirs, versionResources, resourceNames) + if err != nil { + return nil, err + } + + c, err := parseCLR(sections[pe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR], resourceNames) + if err != nil { + return nil, fmt.Errorf("unable to parse PE CLR directory: %w", err) + } + + return &logicalPE{ + Location: f.Location, + CLR: c, + VersionResources: versionResources, + }, nil +} + +// parsePEFile creates readers for targeted sections of the binary used by downstream processing. +func parsePEFile(file unionreader.UnionReader) (map[int]*extractedSection, []pe.SectionHeader32, error) { + fileHeader, magic, err := parsePEHeader(file) + if err != nil { + return nil, nil, fmt.Errorf("error parsing PE header: %w", err) + } + + soi, headers, err := parseSectionHeaders(file, magic, fileHeader.NumberOfSections) + if err != nil { + return nil, nil, fmt.Errorf("error parsing section headers: %w", err) + } + + for i, sec := range soi { + if !sec.exists() { + continue + } + data, err := readDataFromRVA(file, sec.RVA, sec.Size, headers) + if err != nil { + return nil, nil, fmt.Errorf("error reading %q section data: %w", directoryName(i), err) + } + sec.Reader = data + } + + return soi, headers, nil +} + +// parsePEHeader reads the beginning of a PE formatted file, returning the file header and "magic" indicator +// for downstream logic to determine 32/64 bit parsing. +func parsePEHeader(file unionreader.UnionReader) (*pe.FileHeader, uint16, error) { + var dosHeader peDosHeader + if err := binary.Read(file, binary.LittleEndian, &dosHeader); err != nil { + return nil, 0, fmt.Errorf("error reading DOS header: %w", err) + } + if string(dosHeader.Magic[:]) != "MZ" { + return nil, 0, fmt.Errorf("invalid DOS header magic") + } + + peOffset := int64(dosHeader.AddressOfNewEXEHeader) + if _, err := file.Seek(peOffset, io.SeekStart); err != nil { + return nil, 0, fmt.Errorf("error seeking to PE header: %w", err) + } + + var signature [4]byte + if err := binary.Read(file, binary.LittleEndian, &signature); err != nil { + return nil, 0, fmt.Errorf("error reading PE signature: %w", err) + } + if !bytes.Equal(signature[:], []byte("PE\x00\x00")) { + return nil, 0, fmt.Errorf("invalid PE signature") + } + + var fileHeader pe.FileHeader + if err := binary.Read(file, binary.LittleEndian, &fileHeader); err != nil { + return nil, 0, fmt.Errorf("error reading file header: %w", err) + } + + var magic uint16 + if err := binary.Read(file, binary.LittleEndian, &magic); err != nil { + return nil, 0, fmt.Errorf("error reading optional header magic: %w", err) + } + + // seek back to before reading magic (since that value is in the header) + if _, err := file.Seek(-2, io.SeekCurrent); err != nil { + return nil, 0, fmt.Errorf("error seeking back to before reading magic: %w", err) + } + + return &fileHeader, magic, nil +} + +// parseSectionHeaders reads the section headers from the PE file and extracts the virtual addresses + section size +// information for the sections of interest. Additionally, all section headers are returned to aid in downstream processing. +func parseSectionHeaders(file unionreader.UnionReader, magic uint16, numberOfSections uint16) (map[int]*extractedSection, []pe.SectionHeader32, error) { + soi := make(map[int]*extractedSection) + switch magic { + case 0x10B: // PE32 + var optHeader pe.OptionalHeader32 + if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil { + return nil, nil, fmt.Errorf("error reading optional header (PE32): %w", err) + } + + for _, i := range imageDirectoryEntryIndexes { + sectionHeader := optHeader.DataDirectory[i] + if sectionHeader.Size == 0 { + continue + } + soi[i] = &extractedSection{ + RVA: sectionHeader.VirtualAddress, + Size: sectionHeader.Size, + } + } + case 0x20B: // PE32+ (64 bit) + var optHeader pe.OptionalHeader64 + if err := binary.Read(file, binary.LittleEndian, &optHeader); err != nil { + return nil, nil, fmt.Errorf("error reading optional header (PE32+): %w", err) + } + + for _, i := range imageDirectoryEntryIndexes { + sectionHeader := optHeader.DataDirectory[i] + if sectionHeader.Size == 0 { + continue + } + soi[i] = &extractedSection{ + RVA: sectionHeader.VirtualAddress, + Size: sectionHeader.Size, + } + } + default: + return nil, nil, fmt.Errorf("unknown optional header magic: 0x%x", magic) + } + + // read section headers + headers := make([]pe.SectionHeader32, numberOfSections) + for i := 0; i < int(numberOfSections); i++ { + if err := binary.Read(file, binary.LittleEndian, &headers[i]); err != nil { + return nil, nil, fmt.Errorf("error reading section header: %w", err) + } + } + + return soi, headers, nil +} + +// parseCLR extracts the CLR (common language runtime) version information from the COM descriptor and makes +// present/not-present determination based on the presence of CLR resource names. +func parseCLR(sec *extractedSection, resourceNames *strset.Set) (*clrEvidence, error) { + hasCLRDebugResourceNames := resourceNames.HasAny("CLRDEBUGINFO") + if sec == nil || sec.Reader == nil { + return &clrEvidence{ + HasClrResourceNames: hasCLRDebugResourceNames, + }, nil + } + + reader := sec.Reader + var c peImageCore20 + if err := binary.Read(reader, binary.LittleEndian, &c); err != nil { + return nil, fmt.Errorf("error reading CLR header: %w", err) + } + + return &clrEvidence{ + HasClrResourceNames: hasCLRDebugResourceNames, + MajorVersion: c.MajorRuntimeVersion, + MinorVersion: c.MinorRuntimeVersion, + }, nil +} + +// rvaToFileOffset is a helper function to convert RVA to file offset using section headers +func rvaToFileOffset(rva uint32, sections []pe.SectionHeader32) (uint32, error) { + for _, section := range sections { + if rva >= section.VirtualAddress && rva < section.VirtualAddress+section.VirtualSize { + return section.PointerToRawData + (rva - section.VirtualAddress), nil + } + } + return 0, fmt.Errorf("RVA 0x%x not found in any section", rva) +} + +// readDataFromRVA will read data from a specific RVA in the PE file +func readDataFromRVA(file io.ReadSeeker, rva, size uint32, sections []pe.SectionHeader32) (*bytes.Reader, error) { + if size == 0 { + return nil, fmt.Errorf("zero size specified") + } + + offset, err := rvaToFileOffset(rva, sections) + if err != nil { + return nil, err + } + + if _, err := file.Seek(int64(offset), io.SeekStart); err != nil { + return nil, fmt.Errorf("error seeking to data: %w", err) + } + + data := make([]byte, size) + if _, err := io.ReadFull(file, data); err != nil { + return nil, fmt.Errorf("error reading data: %w", err) + } + + return bytes.NewReader(data), nil +} + +// parseResourceDirectory recursively parses a PE resource directory. This takes a relative virtual address (offset of +// a piece of data or code relative to the base address), the size of the resource directory, the set of RVAs already +// parsed, and the map to populate discovered version resource values. +// +// .rsrc Section +// +------------------------------+ +// | Resource Directory Table | +// +------------------------------+ +// | Resource Directory Entries | +// | +------------------------+ | +// | | Subdirectory or Data | | +// | +------------------------+ | +// +------------------------------+ +// | Resource Data Entries | +// | +------------------------+ | +// | | Resource Data | | +// | +------------------------+ | +// +------------------------------+ +// | Actual Resource Data | +// +------------------------------+ +// +// sources: +// - https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-rsrc-section +// - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)#pe-file-resources +func parseResourceDirectory(sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error { + if sec == nil || sec.Size <= 0 { + return nil + } + + if sec.Reader == nil { + return errors.New("resource section not found") + } + + baseRVA := sec.BaseRVA + if baseRVA == 0 { + baseRVA = sec.RVA + } + + offset := int64(sec.RVA - baseRVA) + if _, err := sec.Reader.Seek(offset, io.SeekStart); err != nil { + return fmt.Errorf("error seeking to directory offset: %w", err) + } + + var directoryHeader peImageResourceDirectory + if err := readIntoStruct(sec.Reader, &directoryHeader); err != nil { + return fmt.Errorf("error reading directory header: %w", err) + } + + numEntries := int(directoryHeader.NumberOfNamedEntries + directoryHeader.NumberOfIDEntries) + switch { + case numEntries > peMaxAllowedDirectoryEntries: + return fmt.Errorf("too many entries in resource directory: %d", numEntries) + case numEntries == 0: + return fmt.Errorf("no entries in resource directory") + case numEntries < 0: + return fmt.Errorf("invalid number of entries in resource directory: %d", numEntries) + } + + for i := 0; i < numEntries; i++ { + var entry peImageResourceDirectoryEntry + + entryOffset := offset + int64(binary.Size(directoryHeader)) + int64(i*binary.Size(entry)) + if _, err := sec.Reader.Seek(entryOffset, io.SeekStart); err != nil { + log.Tracef("error seeking to PE entry offset: %v", err) + continue + } + + if err := readIntoStruct(sec.Reader, &entry); err != nil { + continue + } + + if err := processResourceEntry(entry, baseRVA, sec, dirs, fields, names); err != nil { + log.Tracef("error processing resource entry: %v", err) + continue + } + } + + return nil +} + +func processResourceEntry(entry peImageResourceDirectoryEntry, baseRVA uint32, sec *extractedSection, dirs *u32set.Set, fields map[string]string, names *strset.Set) error { + // if the high bit is set, this is a directory entry, otherwise it is a data entry + isDirectory := entry.OffsetToData&0x80000000 != 0 + + // note: the offset is relative to the beginning of the resource section, not an RVA + entryOffsetToData := entry.OffsetToData & 0x7FFFFFFF + + nameIsString := entry.Name&0x80000000 != 0 + nameOffset := entry.Name & 0x7FFFFFFF + + // read the string name of the resource directory + if nameIsString { + currentPos, err := sec.Reader.Seek(0, io.SeekCurrent) + if err != nil { + return fmt.Errorf("error getting current reader position: %w", err) + } + + if _, err := sec.Reader.Seek(int64(nameOffset), io.SeekStart); err != nil { + return fmt.Errorf("error restoring reader position: %w", err) + } + + name, err := readUTF16WithLength(sec.Reader) + if err == nil { + names.Add(name) + } + + if _, err := sec.Reader.Seek(currentPos, io.SeekStart); err != nil { + return fmt.Errorf("error restoring reader position: %w", err) + } + } + + if isDirectory { + subRVA := baseRVA + entryOffsetToData + if dirs.Has(subRVA) { + // some malware uses recursive PE references to evade analysis + return fmt.Errorf("recursive PE reference detected; skipping directory at baseRVA=0x%x subRVA=0x%x", baseRVA, subRVA) + } + + dirs.Add(subRVA) + err := parseResourceDirectory( + &extractedSection{ + RVA: subRVA, + BaseRVA: baseRVA, + Size: sec.Size - (sec.RVA - baseRVA), + Reader: sec.Reader, + }, + dirs, fields, names) + if err != nil { + return err + } + return nil + } + return parseResourceDataEntry(sec.Reader, baseRVA, baseRVA+entryOffsetToData, sec.Size, fields) +} + +func parseResourceDataEntry(reader *bytes.Reader, baseRVA, rva, remainingSize uint32, fields map[string]string) error { + var dataEntry peImageResourceDataEntry + offset := int64(rva - baseRVA) + + if _, err := reader.Seek(offset, io.SeekStart); err != nil { + return fmt.Errorf("error seeking to data entry offset: %w", err) + } + + if err := readIntoStruct(reader, &dataEntry); err != nil { + return fmt.Errorf("error reading resource data entry: %w", err) + } + + if remainingSize < dataEntry.Size { + return fmt.Errorf("resource data entry size exceeds remaining size") + } + + data := make([]byte, dataEntry.Size) + if _, err := reader.Seek(int64(dataEntry.OffsetToData-baseRVA), io.SeekStart); err != nil { + return fmt.Errorf("error seeking to resource data: %w", err) + } + + if _, err := reader.Read(data); err != nil { + return fmt.Errorf("error reading resource data: %w", err) + } + + return parseVersionResourceSection(bytes.NewReader(data), fields) +} + +// parseVersionResourceSection parses a PE version resource section from within a resource directory. +// +// "The main structure in a version resource is the VS_FIXEDFILEINFO structure. Additional structures include the +// VarFileInfo structure to store language information data, and StringFileInfo for user-defined string information. +// All strings in a version resource are in Unicode format. Each block of information is aligned on a DWORD boundary." +// +// "VS_VERSIONINFO" (utf16) +// +---------------------------------------------------+ +// | wLength (2 bytes) | +// | wValueLength (2 bytes) | +// | wType (2 bytes) | +// | szKey ("VS_VERSION_INFO") (utf16) | +// | Padding (to DWORD) | +// +---------------------------------------------------+ +// | VS_FIXEDFILEINFO (52 bytes) | +// +---------------------------------------------------+ +// | "StringFileInfo" (utf16) | +// +---------------------------------------------------+ +// | wLength (2 bytes) | +// | wValueLength (2 bytes) | +// | wType (2 bytes) | +// | szKey ("StringFileInfo") (utf16) | +// | Padding (to DWORD) | +// | StringTable | +// | +--------------------------------------------+ | +// | | wLength (2 bytes) | | +// | | wValueLength (2 bytes) | | +// | | wType (2 bytes) | | +// | | szKey ("040904b0") | | +// | | Padding (to DWORD) | | +// | | String | | +// | | +--------------------------------------+ | | +// | | | wLength (2 bytes) | | | +// | | | wValueLength (2 bytes) | | | +// | | | wType (2 bytes) | | | +// | | | szKey ("FileVersion") | | | +// | | | Padding (to DWORD) | | | +// | | | szValue ("15.00.0913.015") | | | +// | | | Padding (to DWORD) | | | +// | +--------------------------------------------+ | +// +---------------------------------------------------+ +// | VarFileInfo (utf16) | +// +---------------------------------------------------+ +// | (skip!) | +// +---------------------------------------------------+ +// +// sources: +// - https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats +// - https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo +// - https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo +// - https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo +// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo +// - https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable +func parseVersionResourceSection(reader *bytes.Reader, fields map[string]string) error { + offset := 0 + + var info peVsVersionInfo + if szKey, err := readIntoStructAndSzKey(reader, &info, &offset); err != nil { + return fmt.Errorf("error reading PE version info: %v", err) + } else if szKey != "VS_VERSION_INFO" { + // this is a resource section, but not the version resources + return nil + } + + if err := alignAndSeek(reader, &offset); err != nil { + return fmt.Errorf("error aligning past PE version info: %w", err) + } + + var fixedFileInfo peVsFixedFileInfo + if err := readIntoStruct(reader, &fixedFileInfo, &offset); err != nil { + return fmt.Errorf("error reading PE FixedFileInfo: %v", err) + } + + for reader.Len() > 0 { + if err := alignAndSeek(reader, &offset); err != nil { + return fmt.Errorf("error seeking to PE StringFileInfo: %w", err) + } + + var sfiHeader peStringFileInfo + if szKey, err := readIntoStructAndSzKey(reader, &sfiHeader, &offset); err != nil { + return fmt.Errorf("error reading PE string file info header: %v", err) + } else if szKey != "StringFileInfo" { + // we only care about extracting strings from any string tables, skip this + offset += int(sfiHeader.ValueLength) + continue + } + + var stOffset int + + // note: the szKey for the prStringTable is the language + var stHeader peStringTable + if _, err := readIntoStructAndSzKey(reader, &stHeader, &offset, &stOffset); err != nil { + return fmt.Errorf("error reading PE string table header: %v", err) + } + + for stOffset < int(stHeader.Length) { + var stringHeader peString + if err := readIntoStruct(reader, &stringHeader, &offset, &stOffset); err != nil { + break + } + + key := readUTF16(reader, &offset, &stOffset) + + if err := alignAndSeek(reader, &offset, &stOffset); err != nil { + return fmt.Errorf("error aligning to next PE string table value: %w", err) + } + + var value string + if stringHeader.ValueLength > 0 { + value = readUTF16(reader, &offset, &stOffset) + } + + fields[key] = value + + if err := alignAndSeek(reader, &offset, &stOffset); err != nil { + return fmt.Errorf("error aligning to next PE string table key: %w", err) + } + } + } + + if fields["FileVersion"] == "" { + // we can derive the file version from the fixed file info if it is not already specified as a string entry... neat! + fields["FileVersion"] = fmt.Sprintf("%d.%d.%d.%d", + fixedFileInfo.FileVersionMS>>16, fixedFileInfo.FileVersionMS&0xFFFF, + fixedFileInfo.FileVersionLS>>16, fixedFileInfo.FileVersionLS&0xFFFF) + } + + return nil +} + +// readIntoStructAndSzKey reads a struct from the reader and updates the offsets if provided, returning the szKey value. +// This is only useful in the context of the resource directory parsing in narrow cases (this is invalid to use outside of that context). +func readIntoStructAndSzKey[T any](reader *bytes.Reader, data *T, offsets ...*int) (string, error) { + if err := readIntoStruct(reader, data, offsets...); err != nil { + return "", err + } + return readUTF16(reader, offsets...), nil +} + +// readIntoStruct reads a struct from the reader and updates the offsets if provided. +func readIntoStruct[T any](reader io.Reader, data *T, offsets ...*int) error { + if err := binary.Read(reader, binary.LittleEndian, data); err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return err + } + + for i := range offsets { + *offsets[i] += binary.Size(*data) + } + return nil +} + +// alignAndSeek aligns the reader to the next DWORD boundary and seeks to the new offset (updating any provided trackOffsets). +func alignAndSeek(reader io.Seeker, offset *int, trackOffsets ...*int) error { + ogOffset := *offset + *offset = alignToDWORD(*offset) + diff := *offset - ogOffset + for i := range trackOffsets { + *trackOffsets[i] += diff + } + _, err := reader.Seek(int64(*offset), io.SeekStart) + return err +} + +// alignToDWORD aligns the offset to the next DWORD boundary (4 byte boundary) +func alignToDWORD(offset int) int { + return (offset + 3) & ^3 +} + +// readUTF16 is a helper function to read a null-terminated UTF16 string +func readUTF16(reader *bytes.Reader, offsets ...*int) string { + startPos, err := reader.Seek(0, io.SeekCurrent) + if err != nil { + return "" + } + + var result []rune + for { + var char uint16 + err := binary.Read(reader, binary.LittleEndian, &char) + if err != nil || char == 0 { + break + } + result = append(result, rune(char)) + } + + // calculate how many bytes we've actually read (including null terminator) + endPos, _ := reader.Seek(0, io.SeekCurrent) + bytesRead := int(endPos - startPos) + + for i := range offsets { + *offsets[i] += bytesRead + } + + return string(result) +} + +// readUTF16WithLength reads a length-prefixed UTF-16 string from reader. +// The first 2 bytes represent the number of UTF-16 code units. +func readUTF16WithLength(reader *bytes.Reader) (string, error) { + var length uint16 + if err := binary.Read(reader, binary.LittleEndian, &length); err != nil { + return "", err + } + if length == 0 { + return "", nil + } + + // read length UTF-16 code units. + codes := make([]uint16, length) + if err := binary.Read(reader, binary.LittleEndian, &codes); err != nil { + return "", err + } + return string(utf16.Decode(codes)), nil +} diff --git a/syft/pkg/cataloger/dotnet/pe_test.go b/syft/pkg/cataloger/dotnet/pe_test.go new file mode 100644 index 000000000..98a56d160 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/pe_test.go @@ -0,0 +1,163 @@ +package dotnet + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/source/stereoscopesource" +) + +func Test_getLogicalDotnetPE(t *testing.T) { + tests := []struct { + name string + fixture string + path string + wantVR map[string]string + wantCLR bool + wantErr require.ErrorAssertionFunc + }{ + { + name: "newtonsoft", + path: "/app/Newtonsoft.Json.dll", + fixture: "image-net8-app", + wantCLR: true, + wantVR: map[string]string{ + // the numbers are the field parse order, which helped for debugging and understanding corrupted fields + "Comments": "Json.NET is a popular high-performance JSON framework for .NET", // 1 + "CompanyName": "Newtonsoft", // 2 + "FileDescription": "Json.NET .NET 6.0", // 3 + "FileVersion": "13.0.3.27908", // 4 + "InternalName": "Newtonsoft.Json.dll", // 5 + "LegalCopyright": "Copyright © James Newton-King 2008", // 6 + "LegalTrademarks": "", // 7 (empty value actually exists in the string table) + "OriginalFilename": "Newtonsoft.Json.dll", // 8 + "ProductName": "Json.NET", // 9 + "ProductVersion": "13.0.3+0a2e291c0d9c0c7675d445703e51750363a549ef", // 10 + "Assembly Version": "13.0.0.0", // 11 + }, + }, + { + name: "humanizer", + path: "/app/Humanizer.dll", + fixture: "image-net8-app", + wantCLR: true, + wantVR: map[string]string{ + "Comments": "A micro-framework that turns your normal strings, type names, enum fields, date fields ETC into a human friendly format", + "CompanyName": "Mehdi Khalili, Claire Novotny", + "FileDescription": "Humanizer", + "FileVersion": "2.14.1.48190", + "InternalName": "Humanizer.dll", + "LegalCopyright": "Copyright © .NET Foundation and Contributors", + "OriginalFilename": "Humanizer.dll", + "ProductName": "Humanizer (net6.0)", + "ProductVersion": "2.14.1+3ebc38de58", + "Assembly Version": "2.14.0.0", + }, + wantErr: require.NoError, + }, + { + name: "dotnetapp", + path: "/app/dotnetapp.dll", + fixture: "image-net8-app", + wantCLR: true, + wantVR: map[string]string{ + "CompanyName": "dotnetapp", + "FileDescription": "dotnetapp", + "FileVersion": "1.0.0.0", + "InternalName": "dotnetapp.dll", + "LegalCopyright": " ", + "OriginalFilename": "dotnetapp.dll", + "ProductName": "dotnetapp", + "ProductVersion": "1.0.0", + "Assembly Version": "1.0.0.0", + }, + wantErr: require.NoError, + }, + { + name: "jruby", + path: "/app/jruby_windows_9_3_15_0.exe", + fixture: "image-net8-app", + wantCLR: false, // important! + wantVR: map[string]string{ + "CompanyName": "JRuby Dev Team", + "FileDescription": "JRuby", + "FileVersion": "9.3.15.0", + "InternalName": "jruby", + "LegalCopyright": "JRuby Dev Team", + "OriginalFilename": "jruby_windows-x32_9_3_15_0.exe", + "ProductName": "JRuby", + "ProductVersion": "9.3.15.0", + }, + wantErr: require.NoError, + }, + { + name: "single file deployment", + path: "/app/dotnetapp.exe", + fixture: "image-net8-app-single-file", + // single file deployment does not have CLR metadata embedded in the COM descriptor. Instead we need + // to look for evidence of the CLR in other resources directory names, specifically for "CLRDEBUGINFO". + wantCLR: true, + wantVR: map[string]string{ + "CompanyName": "dotnetapp", + "FileDescription": "dotnetapp", + "FileVersion": "1.0.0.0", + "InternalName": "dotnetapp.dll", + "LegalCopyright": " ", + "OriginalFilename": "dotnetapp.dll", + "ProductName": "dotnetapp", + "ProductVersion": "1.0.0", + "Assembly Version": "1.0.0.0", + }, + wantErr: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + + reader := fixtureFile(t, tt.fixture, tt.path) + + got, err := getLogicalDotnetPE(reader) + tt.wantErr(t, err) + if err != nil { + return + } + + if d := cmp.Diff(tt.wantVR, got.VersionResources); d != "" { + t.Errorf("unexpected version resources (-want +got): %s", d) + } + + assert.Equal(t, tt.wantCLR, got.CLR.hasEvidenceOfCLR()) + }) + } +} + +func fixtureFile(t *testing.T, fixture, path string) file.LocationReadCloser { + img := imagetest.GetFixtureImage(t, "docker-archive", fixture) + + s := stereoscopesource.New(img, stereoscopesource.ImageConfig{ + Reference: fixture, + }) + + r, err := s.FileResolver(source.SquashedScope) + require.NoError(t, err) + + locs, err := r.FilesByPath(path) + require.NoError(t, err) + + require.Len(t, locs, 1) + loc := locs[0] + + reader, err := r.FileContentsByLocation(loc) + require.NoError(t, err) + return file.NewLocationReadCloser(loc, reader) +} diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/.gitignore index 027ad4668..9f2519397 100644 --- a/syft/pkg/cataloger/dotnet/test-fixtures/.gitignore +++ b/syft/pkg/cataloger/dotnet/test-fixtures/.gitignore @@ -1,2 +1,3 @@ !*.dll -!*.exe \ No newline at end of file +!*.exe +/cache \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/Makefile b/syft/pkg/cataloger/dotnet/test-fixtures/Makefile new file mode 100644 index 000000000..af4751c73 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/Makefile @@ -0,0 +1,21 @@ +FINGERPRINT_FILE=cache.fingerprint + + +.DEFAULT_GOAL := fixtures + +# requirement 1: 'fixtures' goal to generate any and all test fixtures +fixtures: + @echo "nothing to do" + +# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted +fingerprint: $(FINGERPRINT_FILE) + +# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint +.PHONY: $(FINGERPRINT_FILE) +$(FINGERPRINT_FILE): + @find Makefile **/Dockerfile **/src/** -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE) + @#cat $(FINGERPRINT_FILE) | sha256sum | awk '{print $$1}' + +# requirement 4: 'clean' goal to remove all generated test fixtures +clean: + rm -f $(FINGERPRINT_FILE) diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/TestLibrary.deps.json b/syft/pkg/cataloger/dotnet/test-fixtures/dir-example-1/TestLibrary.deps.json similarity index 100% rename from syft/pkg/cataloger/dotnet/test-fixtures/TestLibrary.deps.json rename to syft/pkg/cataloger/dotnet/test-fixtures/dir-example-1/TestLibrary.deps.json diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/.gitignore new file mode 100644 index 000000000..6750dba93 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/.gitignore @@ -0,0 +1 @@ +/app diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/Dockerfile b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/Dockerfile new file mode 100644 index 000000000..562a13ef9 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/Dockerfile @@ -0,0 +1,22 @@ +# This is the same as the net8-app image, but without the .deps.json file. +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build +ARG RUNTIME=win-x64 +WORKDIR /src + +# copy csproj and restore as distinct layers +COPY src/*.csproj . +COPY src/packages.lock.json . +RUN dotnet restore -r $RUNTIME --verbosity normal --locked-mode + +# copy and publish app and libraries +COPY src/ . +RUN dotnet publish -r $RUNTIME --no-restore -o /app + +# important! +RUN rm /app/*.deps.json + +FROM busybox +WORKDIR /app +COPY --from=build /app . +# just a nice to have for later... +#COPY --from=build /src/packages.lock.json . \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/src/Program.cs b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/src/Program.cs new file mode 100644 index 000000000..af4200bf1 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/src/Program.cs @@ -0,0 +1,31 @@ +using System; +using System.Net; +using System.Runtime.InteropServices; +using static System.Console; + +WriteLine("Runtime and Environment Information"); + +// OS and .NET information +WriteLine($"{nameof(RuntimeInformation.OSArchitecture)}: {RuntimeInformation.OSArchitecture}"); +WriteLine($"{nameof(RuntimeInformation.OSDescription)}: {RuntimeInformation.OSDescription}"); +WriteLine($"{nameof(RuntimeInformation.FrameworkDescription)}: {RuntimeInformation.FrameworkDescription}"); +WriteLine(); + +// Environment information +WriteLine($"{nameof(Environment.UserName)}: {Environment.UserName}"); +WriteLine($"HostName: {Dns.GetHostName()}"); +WriteLine($"{nameof(Environment.ProcessorCount)}: {Environment.ProcessorCount}"); +WriteLine(); + +// Memory information +WriteLine($"Available Memory (GC): {GetInBestUnit(GC.GetGCMemoryInfo().TotalAvailableMemoryBytes)}"); + +string GetInBestUnit(long size) +{ + const double Mebi = 1024 * 1024; + const double Gibi = Mebi * 1024; + + if (size < Mebi) return $"{size} bytes"; + if (size < Gibi) return $"{size / Mebi:F} MiB"; + return $"{size / Gibi:F} GiB"; +} diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/src/dotnetapp.csproj b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/src/dotnetapp.csproj new file mode 100644 index 000000000..de8b12f87 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/src/dotnetapp.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + true + + + + + + + + diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/src/packages.lock.json b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/src/packages.lock.json new file mode 100644 index 000000000..a0f55dc2c --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-no-depjson/src/packages.lock.json @@ -0,0 +1,459 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "Humanizer": { + "type": "Direct", + "requested": "[2.14.1, )", + "resolved": "2.14.1", + "contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==", + "dependencies": { + "Humanizer.Core.af": "2.14.1", + "Humanizer.Core.ar": "2.14.1", + "Humanizer.Core.az": "2.14.1", + "Humanizer.Core.bg": "2.14.1", + "Humanizer.Core.bn-BD": "2.14.1", + "Humanizer.Core.cs": "2.14.1", + "Humanizer.Core.da": "2.14.1", + "Humanizer.Core.de": "2.14.1", + "Humanizer.Core.el": "2.14.1", + "Humanizer.Core.es": "2.14.1", + "Humanizer.Core.fa": "2.14.1", + "Humanizer.Core.fi-FI": "2.14.1", + "Humanizer.Core.fr": "2.14.1", + "Humanizer.Core.fr-BE": "2.14.1", + "Humanizer.Core.he": "2.14.1", + "Humanizer.Core.hr": "2.14.1", + "Humanizer.Core.hu": "2.14.1", + "Humanizer.Core.hy": "2.14.1", + "Humanizer.Core.id": "2.14.1", + "Humanizer.Core.is": "2.14.1", + "Humanizer.Core.it": "2.14.1", + "Humanizer.Core.ja": "2.14.1", + "Humanizer.Core.ko-KR": "2.14.1", + "Humanizer.Core.ku": "2.14.1", + "Humanizer.Core.lv": "2.14.1", + "Humanizer.Core.ms-MY": "2.14.1", + "Humanizer.Core.mt": "2.14.1", + "Humanizer.Core.nb": "2.14.1", + "Humanizer.Core.nb-NO": "2.14.1", + "Humanizer.Core.nl": "2.14.1", + "Humanizer.Core.pl": "2.14.1", + "Humanizer.Core.pt": "2.14.1", + "Humanizer.Core.ro": "2.14.1", + "Humanizer.Core.ru": "2.14.1", + "Humanizer.Core.sk": "2.14.1", + "Humanizer.Core.sl": "2.14.1", + "Humanizer.Core.sr": "2.14.1", + "Humanizer.Core.sr-Latn": "2.14.1", + "Humanizer.Core.sv": "2.14.1", + "Humanizer.Core.th-TH": "2.14.1", + "Humanizer.Core.tr": "2.14.1", + "Humanizer.Core.uk": "2.14.1", + "Humanizer.Core.uz-Cyrl-UZ": "2.14.1", + "Humanizer.Core.uz-Latn-UZ": "2.14.1", + "Humanizer.Core.vi": "2.14.1", + "Humanizer.Core.zh-CN": "2.14.1", + "Humanizer.Core.zh-Hans": "2.14.1", + "Humanizer.Core.zh-Hant": "2.14.1" + } + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "Humanizer.Core": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" + }, + "Humanizer.Core.af": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ar": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.az": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bg": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bn-BD": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.cs": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.da": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.de": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.el": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.es": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fa": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fi-FI": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr-BE": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.he": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hu": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hy": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.id": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.is": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.it": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ja": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ko-KR": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ku": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.lv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ms-MY": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.mt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb-NO": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ro": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ru": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr-Latn": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.th-TH": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.tr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Cyrl-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Latn-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.vi": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-CN": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hans": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hant": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + } + }, + "net8.0/win-x64": {} + } +} \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/.gitignore new file mode 100644 index 000000000..b0b8376dc --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/.gitignore @@ -0,0 +1,2 @@ +/app +/extract.sh \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/Dockerfile b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/Dockerfile new file mode 100644 index 000000000..ee65bb938 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/Dockerfile @@ -0,0 +1,21 @@ +# This is the same as the net8-app image, however, the .NET runtime is copied to the target directory tree. There +# is no bundling to include the runtime within a single archive/binary. +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build +ARG RUNTIME=win-x64 +WORKDIR /src + +# copy csproj and restore as distinct layers +COPY src/*.csproj . +COPY src/packages.lock.json . +RUN dotnet restore -r $RUNTIME --verbosity normal --locked-mode + +# copy and publish app and libraries (self-contained!) +COPY src/ . +RUN dotnet publish -r $RUNTIME --self-contained --no-restore -o /app + + +FROM busybox +WORKDIR /app +COPY --from=build /app . +# just a nice to have for later... +#COPY --from=build /src/packages.lock.json . \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/src/Program.cs b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/src/Program.cs new file mode 100644 index 000000000..af4200bf1 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/src/Program.cs @@ -0,0 +1,31 @@ +using System; +using System.Net; +using System.Runtime.InteropServices; +using static System.Console; + +WriteLine("Runtime and Environment Information"); + +// OS and .NET information +WriteLine($"{nameof(RuntimeInformation.OSArchitecture)}: {RuntimeInformation.OSArchitecture}"); +WriteLine($"{nameof(RuntimeInformation.OSDescription)}: {RuntimeInformation.OSDescription}"); +WriteLine($"{nameof(RuntimeInformation.FrameworkDescription)}: {RuntimeInformation.FrameworkDescription}"); +WriteLine(); + +// Environment information +WriteLine($"{nameof(Environment.UserName)}: {Environment.UserName}"); +WriteLine($"HostName: {Dns.GetHostName()}"); +WriteLine($"{nameof(Environment.ProcessorCount)}: {Environment.ProcessorCount}"); +WriteLine(); + +// Memory information +WriteLine($"Available Memory (GC): {GetInBestUnit(GC.GetGCMemoryInfo().TotalAvailableMemoryBytes)}"); + +string GetInBestUnit(long size) +{ + const double Mebi = 1024 * 1024; + const double Gibi = Mebi * 1024; + + if (size < Mebi) return $"{size} bytes"; + if (size < Gibi) return $"{size / Mebi:F} MiB"; + return $"{size / Gibi:F} GiB"; +} diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/src/dotnetapp.csproj b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/src/dotnetapp.csproj new file mode 100644 index 000000000..de8b12f87 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/src/dotnetapp.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + true + + + + + + + + diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/src/packages.lock.json b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/src/packages.lock.json new file mode 100644 index 000000000..a0f55dc2c --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-self-contained/src/packages.lock.json @@ -0,0 +1,459 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "Humanizer": { + "type": "Direct", + "requested": "[2.14.1, )", + "resolved": "2.14.1", + "contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==", + "dependencies": { + "Humanizer.Core.af": "2.14.1", + "Humanizer.Core.ar": "2.14.1", + "Humanizer.Core.az": "2.14.1", + "Humanizer.Core.bg": "2.14.1", + "Humanizer.Core.bn-BD": "2.14.1", + "Humanizer.Core.cs": "2.14.1", + "Humanizer.Core.da": "2.14.1", + "Humanizer.Core.de": "2.14.1", + "Humanizer.Core.el": "2.14.1", + "Humanizer.Core.es": "2.14.1", + "Humanizer.Core.fa": "2.14.1", + "Humanizer.Core.fi-FI": "2.14.1", + "Humanizer.Core.fr": "2.14.1", + "Humanizer.Core.fr-BE": "2.14.1", + "Humanizer.Core.he": "2.14.1", + "Humanizer.Core.hr": "2.14.1", + "Humanizer.Core.hu": "2.14.1", + "Humanizer.Core.hy": "2.14.1", + "Humanizer.Core.id": "2.14.1", + "Humanizer.Core.is": "2.14.1", + "Humanizer.Core.it": "2.14.1", + "Humanizer.Core.ja": "2.14.1", + "Humanizer.Core.ko-KR": "2.14.1", + "Humanizer.Core.ku": "2.14.1", + "Humanizer.Core.lv": "2.14.1", + "Humanizer.Core.ms-MY": "2.14.1", + "Humanizer.Core.mt": "2.14.1", + "Humanizer.Core.nb": "2.14.1", + "Humanizer.Core.nb-NO": "2.14.1", + "Humanizer.Core.nl": "2.14.1", + "Humanizer.Core.pl": "2.14.1", + "Humanizer.Core.pt": "2.14.1", + "Humanizer.Core.ro": "2.14.1", + "Humanizer.Core.ru": "2.14.1", + "Humanizer.Core.sk": "2.14.1", + "Humanizer.Core.sl": "2.14.1", + "Humanizer.Core.sr": "2.14.1", + "Humanizer.Core.sr-Latn": "2.14.1", + "Humanizer.Core.sv": "2.14.1", + "Humanizer.Core.th-TH": "2.14.1", + "Humanizer.Core.tr": "2.14.1", + "Humanizer.Core.uk": "2.14.1", + "Humanizer.Core.uz-Cyrl-UZ": "2.14.1", + "Humanizer.Core.uz-Latn-UZ": "2.14.1", + "Humanizer.Core.vi": "2.14.1", + "Humanizer.Core.zh-CN": "2.14.1", + "Humanizer.Core.zh-Hans": "2.14.1", + "Humanizer.Core.zh-Hant": "2.14.1" + } + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "Humanizer.Core": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" + }, + "Humanizer.Core.af": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ar": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.az": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bg": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bn-BD": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.cs": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.da": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.de": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.el": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.es": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fa": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fi-FI": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr-BE": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.he": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hu": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hy": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.id": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.is": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.it": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ja": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ko-KR": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ku": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.lv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ms-MY": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.mt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb-NO": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ro": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ru": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr-Latn": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.th-TH": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.tr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Cyrl-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Latn-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.vi": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-CN": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hans": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hant": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + } + }, + "net8.0/win-x64": {} + } +} \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/.gitignore new file mode 100644 index 000000000..b0b8376dc --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/.gitignore @@ -0,0 +1,2 @@ +/app +/extract.sh \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/Dockerfile b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/Dockerfile new file mode 100644 index 000000000..e5c8dcf22 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/Dockerfile @@ -0,0 +1,20 @@ +# This is the same as the net8-app image, however, the entire .NET runtime is compiled into a single binary, residing with the application. +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build +ARG RUNTIME=win-x64 +WORKDIR /src + +# copy csproj and restore as distinct layers +COPY src/*.csproj . +COPY src/packages.lock.json . +RUN dotnet restore -r $RUNTIME --verbosity normal --locked-mode + +# copy and publish app and libraries (single file) +COPY src/ . +RUN dotnet publish -r $RUNTIME -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:PublishTrimmed=true --no-restore -o /app + + +FROM busybox +WORKDIR /app +COPY --from=build /app . +# just a nice to have for later... +#COPY --from=build /src/packages.lock.json . \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/src/Program.cs b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/src/Program.cs new file mode 100644 index 000000000..d1856dbc2 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/src/Program.cs @@ -0,0 +1,48 @@ +using System; +using Humanizer; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + + +namespace IndirectDependencyExample +{ + class Program + { + static void Main(string[] args) + { + string runtimeInfo = "hello world!\n"; + Console.WriteLine(runtimeInfo); + + + Console.WriteLine($"\"this_is_a_test\" to title case: {"this_is_a_test".Humanize(LetterCasing.Title)}"); + + const string jsonString = @" + { + ""message"": ""Hello from JSON!"", + ""details"": { + ""timestamp"": ""2025-03-26T12:00:00Z"", + ""version"": ""1.0.0"", + ""metadata"": { + ""author"": ""Claude"", + ""environment"": ""Development"" + } + }, + ""items"": [ + { + ""id"": 1, + ""name"": ""Item One"" + }, + { + ""id"": 2, + ""name"": ""Item Two"" + } + ] + }"; + + JObject jsonObject = JObject.Parse(jsonString); + + string message = (string)jsonObject["message"]; + Console.WriteLine($"Message: {message}"); + } + } +} \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/src/dotnetapp.csproj b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/src/dotnetapp.csproj new file mode 100644 index 000000000..de8b12f87 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/src/dotnetapp.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + true + + + + + + + + diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/src/packages.lock.json b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/src/packages.lock.json new file mode 100644 index 000000000..a0f55dc2c --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app-single-file/src/packages.lock.json @@ -0,0 +1,459 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "Humanizer": { + "type": "Direct", + "requested": "[2.14.1, )", + "resolved": "2.14.1", + "contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==", + "dependencies": { + "Humanizer.Core.af": "2.14.1", + "Humanizer.Core.ar": "2.14.1", + "Humanizer.Core.az": "2.14.1", + "Humanizer.Core.bg": "2.14.1", + "Humanizer.Core.bn-BD": "2.14.1", + "Humanizer.Core.cs": "2.14.1", + "Humanizer.Core.da": "2.14.1", + "Humanizer.Core.de": "2.14.1", + "Humanizer.Core.el": "2.14.1", + "Humanizer.Core.es": "2.14.1", + "Humanizer.Core.fa": "2.14.1", + "Humanizer.Core.fi-FI": "2.14.1", + "Humanizer.Core.fr": "2.14.1", + "Humanizer.Core.fr-BE": "2.14.1", + "Humanizer.Core.he": "2.14.1", + "Humanizer.Core.hr": "2.14.1", + "Humanizer.Core.hu": "2.14.1", + "Humanizer.Core.hy": "2.14.1", + "Humanizer.Core.id": "2.14.1", + "Humanizer.Core.is": "2.14.1", + "Humanizer.Core.it": "2.14.1", + "Humanizer.Core.ja": "2.14.1", + "Humanizer.Core.ko-KR": "2.14.1", + "Humanizer.Core.ku": "2.14.1", + "Humanizer.Core.lv": "2.14.1", + "Humanizer.Core.ms-MY": "2.14.1", + "Humanizer.Core.mt": "2.14.1", + "Humanizer.Core.nb": "2.14.1", + "Humanizer.Core.nb-NO": "2.14.1", + "Humanizer.Core.nl": "2.14.1", + "Humanizer.Core.pl": "2.14.1", + "Humanizer.Core.pt": "2.14.1", + "Humanizer.Core.ro": "2.14.1", + "Humanizer.Core.ru": "2.14.1", + "Humanizer.Core.sk": "2.14.1", + "Humanizer.Core.sl": "2.14.1", + "Humanizer.Core.sr": "2.14.1", + "Humanizer.Core.sr-Latn": "2.14.1", + "Humanizer.Core.sv": "2.14.1", + "Humanizer.Core.th-TH": "2.14.1", + "Humanizer.Core.tr": "2.14.1", + "Humanizer.Core.uk": "2.14.1", + "Humanizer.Core.uz-Cyrl-UZ": "2.14.1", + "Humanizer.Core.uz-Latn-UZ": "2.14.1", + "Humanizer.Core.vi": "2.14.1", + "Humanizer.Core.zh-CN": "2.14.1", + "Humanizer.Core.zh-Hans": "2.14.1", + "Humanizer.Core.zh-Hant": "2.14.1" + } + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "Humanizer.Core": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" + }, + "Humanizer.Core.af": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ar": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.az": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bg": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bn-BD": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.cs": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.da": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.de": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.el": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.es": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fa": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fi-FI": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr-BE": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.he": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hu": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hy": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.id": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.is": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.it": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ja": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ko-KR": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ku": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.lv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ms-MY": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.mt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb-NO": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ro": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ru": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr-Latn": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.th-TH": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.tr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Cyrl-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Latn-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.vi": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-CN": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hans": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hant": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + } + }, + "net8.0/win-x64": {} + } +} \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/.gitignore new file mode 100644 index 000000000..b0b8376dc --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/.gitignore @@ -0,0 +1,2 @@ +/app +/extract.sh \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/Dockerfile b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/Dockerfile new file mode 100644 index 000000000..c3ce13e39 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/Dockerfile @@ -0,0 +1,82 @@ +# This represents a basic .NET project build where the project dependencies are downloaded and the project is built. +# The output is a directory tree of DLLs, a project.lock.json (not used in these tests), a .deps.json file, and +# a .runtimeconfig.json file (not used in these tests). With this deployment strategy there is no bundled runtime. +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build +ARG RUNTIME=win-x64 +WORKDIR /src + +# copy csproj and restore as distinct layers +COPY src/*.csproj . +COPY src/packages.lock.json . +RUN dotnet restore -r $RUNTIME --verbosity normal --locked-mode + +# copy and publish app and libraries +COPY src/ . +RUN dotnet publish -r $RUNTIME --no-restore -o /app + +# $ dotnet list package --include-transitive +# Project 'dotnetapp' has the following package references +# [net8.0]: +# Top-level Package Requested Resolved +# > Humanizer 2.14.1 2.14.1 +# > Newtonsoft.Json 13.0.3 13.0.3 +# +# Transitive Package Resolved +# > Humanizer.Core 2.14.1 +# > Humanizer.Core.af 2.14.1 +# > Humanizer.Core.ar 2.14.1 +# > Humanizer.Core.az 2.14.1 +# > Humanizer.Core.bg 2.14.1 +# > Humanizer.Core.bn-BD 2.14.1 +# > Humanizer.Core.cs 2.14.1 +# > Humanizer.Core.da 2.14.1 +# > Humanizer.Core.de 2.14.1 +# > Humanizer.Core.el 2.14.1 +# > Humanizer.Core.es 2.14.1 +# > Humanizer.Core.fa 2.14.1 +# > Humanizer.Core.fi-FI 2.14.1 +# > Humanizer.Core.fr 2.14.1 +# > Humanizer.Core.fr-BE 2.14.1 +# > Humanizer.Core.he 2.14.1 +# > Humanizer.Core.hr 2.14.1 +# > Humanizer.Core.hu 2.14.1 +# > Humanizer.Core.hy 2.14.1 +# > Humanizer.Core.id 2.14.1 +# > Humanizer.Core.is 2.14.1 +# > Humanizer.Core.it 2.14.1 +# > Humanizer.Core.ja 2.14.1 +# > Humanizer.Core.ko-KR 2.14.1 +# > Humanizer.Core.ku 2.14.1 +# > Humanizer.Core.lv 2.14.1 +# > Humanizer.Core.ms-MY 2.14.1 +# > Humanizer.Core.mt 2.14.1 +# > Humanizer.Core.nb 2.14.1 +# > Humanizer.Core.nb-NO 2.14.1 +# > Humanizer.Core.nl 2.14.1 +# > Humanizer.Core.pl 2.14.1 +# > Humanizer.Core.pt 2.14.1 +# > Humanizer.Core.ro 2.14.1 +# > Humanizer.Core.ru 2.14.1 +# > Humanizer.Core.sk 2.14.1 +# > Humanizer.Core.sl 2.14.1 +# > Humanizer.Core.sr 2.14.1 +# > Humanizer.Core.sr-Latn 2.14.1 +# > Humanizer.Core.sv 2.14.1 +# > Humanizer.Core.th-TH 2.14.1 +# > Humanizer.Core.tr 2.14.1 +# > Humanizer.Core.uk 2.14.1 +# > Humanizer.Core.uz-Cyrl-UZ 2.14.1 +# > Humanizer.Core.uz-Latn-UZ 2.14.1 +# > Humanizer.Core.vi 2.14.1 +# > Humanizer.Core.zh-CN 2.14.1 +# > Humanizer.Core.zh-Hans 2.14.1 +# > Humanizer.Core.zh-Hant 2.14.1 + +# lets pull in a file that is not related at all and in fact is not a .NET binary either (this should be ignored) +RUN wget -O /app/jruby_windows_9_3_15_0.exe https://s3.amazonaws.com/jruby.org/downloads/9.3.15.0/jruby_windows_9_3_15_0.exe + +FROM busybox +WORKDIR /app +COPY --from=build /app . +# just a nice to have for later... +#COPY --from=build /src/packages.lock.json . diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/src/Program.cs b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/src/Program.cs new file mode 100644 index 000000000..af4200bf1 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/src/Program.cs @@ -0,0 +1,31 @@ +using System; +using System.Net; +using System.Runtime.InteropServices; +using static System.Console; + +WriteLine("Runtime and Environment Information"); + +// OS and .NET information +WriteLine($"{nameof(RuntimeInformation.OSArchitecture)}: {RuntimeInformation.OSArchitecture}"); +WriteLine($"{nameof(RuntimeInformation.OSDescription)}: {RuntimeInformation.OSDescription}"); +WriteLine($"{nameof(RuntimeInformation.FrameworkDescription)}: {RuntimeInformation.FrameworkDescription}"); +WriteLine(); + +// Environment information +WriteLine($"{nameof(Environment.UserName)}: {Environment.UserName}"); +WriteLine($"HostName: {Dns.GetHostName()}"); +WriteLine($"{nameof(Environment.ProcessorCount)}: {Environment.ProcessorCount}"); +WriteLine(); + +// Memory information +WriteLine($"Available Memory (GC): {GetInBestUnit(GC.GetGCMemoryInfo().TotalAvailableMemoryBytes)}"); + +string GetInBestUnit(long size) +{ + const double Mebi = 1024 * 1024; + const double Gibi = Mebi * 1024; + + if (size < Mebi) return $"{size} bytes"; + if (size < Gibi) return $"{size / Mebi:F} MiB"; + return $"{size / Gibi:F} GiB"; +} diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/src/dotnetapp.csproj b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/src/dotnetapp.csproj new file mode 100644 index 000000000..de8b12f87 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/src/dotnetapp.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + true + + + + + + + + diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/src/packages.lock.json b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/src/packages.lock.json new file mode 100644 index 000000000..a0f55dc2c --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-app/src/packages.lock.json @@ -0,0 +1,459 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "Humanizer": { + "type": "Direct", + "requested": "[2.14.1, )", + "resolved": "2.14.1", + "contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==", + "dependencies": { + "Humanizer.Core.af": "2.14.1", + "Humanizer.Core.ar": "2.14.1", + "Humanizer.Core.az": "2.14.1", + "Humanizer.Core.bg": "2.14.1", + "Humanizer.Core.bn-BD": "2.14.1", + "Humanizer.Core.cs": "2.14.1", + "Humanizer.Core.da": "2.14.1", + "Humanizer.Core.de": "2.14.1", + "Humanizer.Core.el": "2.14.1", + "Humanizer.Core.es": "2.14.1", + "Humanizer.Core.fa": "2.14.1", + "Humanizer.Core.fi-FI": "2.14.1", + "Humanizer.Core.fr": "2.14.1", + "Humanizer.Core.fr-BE": "2.14.1", + "Humanizer.Core.he": "2.14.1", + "Humanizer.Core.hr": "2.14.1", + "Humanizer.Core.hu": "2.14.1", + "Humanizer.Core.hy": "2.14.1", + "Humanizer.Core.id": "2.14.1", + "Humanizer.Core.is": "2.14.1", + "Humanizer.Core.it": "2.14.1", + "Humanizer.Core.ja": "2.14.1", + "Humanizer.Core.ko-KR": "2.14.1", + "Humanizer.Core.ku": "2.14.1", + "Humanizer.Core.lv": "2.14.1", + "Humanizer.Core.ms-MY": "2.14.1", + "Humanizer.Core.mt": "2.14.1", + "Humanizer.Core.nb": "2.14.1", + "Humanizer.Core.nb-NO": "2.14.1", + "Humanizer.Core.nl": "2.14.1", + "Humanizer.Core.pl": "2.14.1", + "Humanizer.Core.pt": "2.14.1", + "Humanizer.Core.ro": "2.14.1", + "Humanizer.Core.ru": "2.14.1", + "Humanizer.Core.sk": "2.14.1", + "Humanizer.Core.sl": "2.14.1", + "Humanizer.Core.sr": "2.14.1", + "Humanizer.Core.sr-Latn": "2.14.1", + "Humanizer.Core.sv": "2.14.1", + "Humanizer.Core.th-TH": "2.14.1", + "Humanizer.Core.tr": "2.14.1", + "Humanizer.Core.uk": "2.14.1", + "Humanizer.Core.uz-Cyrl-UZ": "2.14.1", + "Humanizer.Core.uz-Latn-UZ": "2.14.1", + "Humanizer.Core.vi": "2.14.1", + "Humanizer.Core.zh-CN": "2.14.1", + "Humanizer.Core.zh-Hans": "2.14.1", + "Humanizer.Core.zh-Hant": "2.14.1" + } + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "Humanizer.Core": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" + }, + "Humanizer.Core.af": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ar": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.az": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bg": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bn-BD": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.cs": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.da": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.de": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.el": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.es": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fa": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fi-FI": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr-BE": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.he": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hu": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hy": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.id": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.is": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.it": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ja": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ko-KR": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ku": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.lv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ms-MY": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.mt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb-NO": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ro": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ru": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr-Latn": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.th-TH": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.tr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Cyrl-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Latn-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.vi": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-CN": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hans": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hant": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + } + }, + "net8.0/win-x64": {} + } +} \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/.gitignore new file mode 100644 index 000000000..b0b8376dc --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/.gitignore @@ -0,0 +1,2 @@ +/app +/extract.sh \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/Dockerfile b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/Dockerfile new file mode 100644 index 000000000..67389ccd9 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/Dockerfile @@ -0,0 +1,18 @@ +# This is the same as the net8-app image, but without the .deps.json file. +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build +ARG RUNTIME=win-x64 +WORKDIR /src + +# copy csproj and restore as distinct layers +COPY src/*.csproj . +RUN dotnet restore -r $RUNTIME --verbosity normal + +# copy and publish app and libraries +COPY src/ . +RUN dotnet publish -r $RUNTIME --no-restore -o /app + +FROM busybox +WORKDIR /app +COPY --from=build /app . +# just a nice to have for later... +#COPY --from=build /src/packages.lock.json . \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/.gitignore new file mode 100644 index 000000000..ee4c73ebf --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/.gitignore @@ -0,0 +1,3 @@ +/bin +/obj +/app \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/Program.cs b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/Program.cs new file mode 100644 index 000000000..d1856dbc2 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/Program.cs @@ -0,0 +1,48 @@ +using System; +using Humanizer; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + + +namespace IndirectDependencyExample +{ + class Program + { + static void Main(string[] args) + { + string runtimeInfo = "hello world!\n"; + Console.WriteLine(runtimeInfo); + + + Console.WriteLine($"\"this_is_a_test\" to title case: {"this_is_a_test".Humanize(LetterCasing.Title)}"); + + const string jsonString = @" + { + ""message"": ""Hello from JSON!"", + ""details"": { + ""timestamp"": ""2025-03-26T12:00:00Z"", + ""version"": ""1.0.0"", + ""metadata"": { + ""author"": ""Claude"", + ""environment"": ""Development"" + } + }, + ""items"": [ + { + ""id"": 1, + ""name"": ""Item One"" + }, + { + ""id"": 2, + ""name"": ""Item Two"" + } + ] + }"; + + JObject jsonObject = JObject.Parse(jsonString); + + string message = (string)jsonObject["message"]; + Console.WriteLine($"Message: {message}"); + } + } +} \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/dotnetapp.csproj b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/dotnetapp.csproj new file mode 100644 index 000000000..495d80894 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-ilrepack/src/dotnetapp.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + Debug;Release + + + + + + + + + \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/.gitignore new file mode 100644 index 000000000..b0b8376dc --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/.gitignore @@ -0,0 +1,2 @@ +/app +/extract.sh \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/Dockerfile b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/Dockerfile new file mode 100644 index 000000000..67389ccd9 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/Dockerfile @@ -0,0 +1,18 @@ +# This is the same as the net8-app image, but without the .deps.json file. +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:7d3a75ca5c8ac4679908ef7a2591b9bc257c62bd530167de32bba105148bb7be AS build +ARG RUNTIME=win-x64 +WORKDIR /src + +# copy csproj and restore as distinct layers +COPY src/*.csproj . +RUN dotnet restore -r $RUNTIME --verbosity normal + +# copy and publish app and libraries +COPY src/ . +RUN dotnet publish -r $RUNTIME --no-restore -o /app + +FROM busybox +WORKDIR /app +COPY --from=build /app . +# just a nice to have for later... +#COPY --from=build /src/packages.lock.json . \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/.gitignore new file mode 100644 index 000000000..ee4c73ebf --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/.gitignore @@ -0,0 +1,3 @@ +/bin +/obj +/app \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/Program.cs b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/Program.cs new file mode 100644 index 000000000..d1856dbc2 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/Program.cs @@ -0,0 +1,48 @@ +using System; +using Humanizer; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + + +namespace IndirectDependencyExample +{ + class Program + { + static void Main(string[] args) + { + string runtimeInfo = "hello world!\n"; + Console.WriteLine(runtimeInfo); + + + Console.WriteLine($"\"this_is_a_test\" to title case: {"this_is_a_test".Humanize(LetterCasing.Title)}"); + + const string jsonString = @" + { + ""message"": ""Hello from JSON!"", + ""details"": { + ""timestamp"": ""2025-03-26T12:00:00Z"", + ""version"": ""1.0.0"", + ""metadata"": { + ""author"": ""Claude"", + ""environment"": ""Development"" + } + }, + ""items"": [ + { + ""id"": 1, + ""name"": ""Item One"" + }, + { + ""id"": 2, + ""name"": ""Item Two"" + } + ] + }"; + + JObject jsonObject = JObject.Parse(jsonString); + + string message = (string)jsonObject["message"]; + Console.WriteLine($"Message: {message}"); + } + } +} \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/dotnetapp.csproj b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/dotnetapp.csproj new file mode 100644 index 000000000..803897d2b --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-privateassets/src/dotnetapp.csproj @@ -0,0 +1,13 @@ + + + + Exe + net8.0 + + + + + + + + \ No newline at end of file diff --git a/syft/pkg/dotnet.go b/syft/pkg/dotnet.go index cf432e8ab..47aca15d6 100644 --- a/syft/pkg/dotnet.go +++ b/syft/pkg/dotnet.go @@ -7,6 +7,8 @@ type DotnetDepsEntry struct { Path string `mapstructure:"path" json:"path"` Sha512 string `mapstructure:"sha512" json:"sha512"` HashPath string `mapstructure:"hashPath" json:"hashPath"` + + Executables map[string]DotnetPortableExecutableEntry `json:"executables,omitempty"` } // DotnetPackagesLockEntry is a struct that represents a single entry found in the "dependencies" section in a .NET packages.lock.json file. diff --git a/test/cli/scan_cmd_test.go b/test/cli/scan_cmd_test.go index d76121504..6b6c197c0 100644 --- a/test/cli/scan_cmd_test.go +++ b/test/cli/scan_cmd_test.go @@ -9,7 +9,7 @@ import ( const ( // this is the number of packages that should be found in the image-pkg-coverage fixture image // when analyzed with the squashed scope. - coverageImageSquashedPackageCount = 30 + coverageImageSquashedPackageCount = 42 ) func TestPackagesCmdFlags(t *testing.T) {