diff --git a/syft/pkg/cataloger/dotnet/cataloger_test.go b/syft/pkg/cataloger/dotnet/cataloger_test.go
index d4f638bb4..1e135318d 100644
--- a/syft/pkg/cataloger/dotnet/cataloger_test.go
+++ b/syft/pkg/cataloger/dotnet/cataloger_test.go
@@ -1008,6 +1008,18 @@ func TestCataloger(t *testing.T) {
},
assertion: assertAccurateNetRuntimePackage,
},
+ {
+ name: "libman support",
+ fixture: "image-net6-asp-libman",
+ cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()),
+ expectedPkgs: []string{
+ "LibManSample @ 1.0.0 (/app/LibManSample.deps.json)",
+ "jquery @ 3.3.1 (/app/libman.json)",
+ },
+ expectedRels: []string{
+ "jquery @ 3.3.1 (/app/libman.json) [dependency-of] LibManSample @ 1.0.0 (/app/LibManSample.deps.json)",
+ },
+ },
}
for _, tt := range cases {
@@ -1134,6 +1146,20 @@ func TestDotnetDepsCataloger_regressions(t *testing.T) {
},
),
},
+ {
+ name: "libman support",
+ fixture: "image-net6-asp-libman",
+ cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()),
+ assertion: assertPackages(
+ []string{
+ "jquery", // a javascript package, not the nuget package
+ },
+ []string{
+ "vendor", // this is the string reference for a filesystem provider
+ "lodash", // this is from a filesystem provider, which is not supported
+ },
+ ),
+ },
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
diff --git a/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go
index c5964656f..4829ce7cf 100644
--- a/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go
+++ b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go
@@ -78,6 +78,9 @@ func (c depsBinaryCataloger) Catalog(_ context.Context, resolver file.Resolver)
var runtimePkgs []*pkg.Package
for i := range pkgs {
p := &pkgs[i]
+ if p.Type != pkg.DotnetPkg {
+ continue
+ }
if isRuntime(p.Name) {
existingRuntimeVersions.Add(p.Version)
runtimePkgs = append(runtimePkgs, p)
@@ -290,8 +293,22 @@ func packagesFromLogicalDepsJSON(doc logicalDepsJSON, config CatalogerConfig) (*
pkgMap[nameVersion] = *dotnetPkg
}
}
+ rels := relationshipsFromLogicalDepsJSON(doc, pkgMap, skippedDepPkgs)
- return rootPkg, pkgs, relationshipsFromLogicalDepsJSON(doc, pkgMap, skippedDepPkgs)
+ // ensure that any libman packages are associated with the all root packages
+ for _, libmanPkg := range doc.LibmanPackages {
+ pkgs = append(pkgs, libmanPkg)
+ if rootPkg == nil {
+ continue
+ }
+ rels = append(rels, artifact.Relationship{
+ From: libmanPkg,
+ To: *rootPkg,
+ Type: artifact.DependencyOfRelationship,
+ })
+ }
+
+ return rootPkg, pkgs, rels
}
// relationshipsFromLogicalDepsJSON creates relationships from a logicalDepsJSON document for only the given syft packages.
@@ -389,7 +406,13 @@ func findDepsJSON(resolver file.Resolver) ([]logicalDepsJSON, error, error) {
continue
}
- depsJSONs = append(depsJSONs, getLogicalDepsJSON(*dj))
+ libman, err := findLibmanJSON(resolver, loc)
+ if err != nil {
+ unknownErr = unknown.Append(unknownErr, loc, err)
+ libman = nil
+ }
+
+ depsJSONs = append(depsJSONs, getLogicalDepsJSON(*dj, libman))
}
return depsJSONs, unknownErr, nil
diff --git a/syft/pkg/cataloger/dotnet/deps_json.go b/syft/pkg/cataloger/dotnet/deps_json.go
index 8388ddb66..70d8aa1be 100644
--- a/syft/pkg/cataloger/dotnet/deps_json.go
+++ b/syft/pkg/cataloger/dotnet/deps_json.go
@@ -9,6 +9,7 @@ import (
"github.com/scylladb/go-set/strset"
"github.com/anchore/syft/syft/file"
+ "github.com/anchore/syft/syft/pkg"
)
type depsJSON struct {
@@ -147,6 +148,7 @@ type logicalDepsJSON struct {
PackagesByNameVersion map[string]logicalDepsJSONPackage
PackageNameVersions *strset.Set
BundlingDetected bool
+ LibmanPackages []pkg.Package
}
func (l logicalDepsJSON) RootPackage() (logicalDepsJSONPackage, bool) {
@@ -196,7 +198,7 @@ var knownBundlers = strset.New(
"Fody", // IL weaving framework
)
-func getLogicalDepsJSON(deps depsJSON) logicalDepsJSON {
+func getLogicalDepsJSON(deps depsJSON, lm *libmanJSON) logicalDepsJSON {
packageMap := make(map[string]*logicalDepsJSONPackage)
nameVersions := strset.New()
@@ -244,6 +246,7 @@ func getLogicalDepsJSON(deps depsJSON) logicalDepsJSON {
PackagesByNameVersion: packages,
PackageNameVersions: nameVersions,
BundlingDetected: bundlingDetected,
+ LibmanPackages: lm.packages(),
}
}
diff --git a/syft/pkg/cataloger/dotnet/libman_json.go b/syft/pkg/cataloger/dotnet/libman_json.go
new file mode 100644
index 000000000..4483a5cb4
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/libman_json.go
@@ -0,0 +1,109 @@
+package dotnet
+
+import (
+ "encoding/json"
+ "path"
+ "strings"
+
+ "github.com/anchore/packageurl-go"
+ "github.com/anchore/syft/internal"
+ "github.com/anchore/syft/syft/file"
+ "github.com/anchore/syft/syft/pkg"
+)
+
+// libmanJSON represents the libman.json file format in ASP.NET projects for describing javascript assets to be downloaded and bundled
+// see https://github.com/aspnet/LibraryManager/wiki/libman.json-reference
+type libmanJSON struct {
+ Location file.Location `json:"-"`
+ Version string `json:"version"`
+ DefaultProvider string `json:"defaultProvider"`
+ Libraries []struct {
+ Library string `json:"library"`
+ Files []string `json:"files"`
+ Destination string `json:"destination"`
+ Provider string `json:"provider,omitempty"`
+ } `json:"libraries"`
+}
+
+func (l *libmanJSON) packages() []pkg.Package {
+ if l == nil {
+ return nil
+ }
+
+ var pkgs []pkg.Package
+ for _, lib := range l.Libraries {
+ if lib.Provider == "filesystem" {
+ // there is no name and version with filesystem providers
+ continue
+ }
+ fields := strings.Split(lib.Library, "@")
+ if len(fields) != 2 {
+ continue
+ }
+
+ name := fields[0]
+ version := fields[1]
+
+ p := pkg.Package{
+ Name: name,
+ Version: version,
+ Locations: file.NewLocationSet(l.Location),
+ Type: pkg.NpmPkg,
+ PURL: packageurl.NewPackageURL(
+ packageurl.TypeNPM,
+ "",
+ name,
+ version,
+ nil,
+ "",
+ ).ToString(),
+ Language: pkg.JavaScript,
+ }
+
+ p.SetID()
+ pkgs = append(pkgs, p)
+ }
+
+ return pkgs
+}
+
+func newLibmanJSON(reader file.LocationReadCloser) (*libmanJSON, error) {
+ var doc libmanJSON
+ dec := json.NewDecoder(reader)
+ if err := dec.Decode(&doc); err != nil {
+ return nil, err
+ }
+
+ for i := range doc.Libraries {
+ l := &doc.Libraries[i]
+ if l.Provider == "" {
+ l.Provider = doc.DefaultProvider
+ }
+ }
+
+ doc.Location = reader.Location
+
+ return &doc, nil
+}
+
+func findLibmanJSON(resolver file.Resolver, depsJSON file.Location) (*libmanJSON, error) {
+ parent := path.Dir(depsJSON.RealPath)
+ loc := resolver.RelativeFileByPath(depsJSON, path.Join(parent, "libman.json"))
+ if loc == nil {
+ return nil, nil
+ }
+
+ reader, err := resolver.FileContentsByLocation(*loc)
+ defer internal.CloseAndLogError(reader, loc.RealPath)
+ if err != nil {
+ return nil, err
+ }
+ internal.CloseAndLogError(reader, loc.RealPath)
+
+ lj, err := newLibmanJSON(file.NewLocationReadCloser(*loc, reader))
+ if err != nil {
+ return nil, err
+ }
+
+ return lj, nil
+}
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/.dockerignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/.dockerignore
new file mode 100644
index 000000000..585993c76
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/.dockerignore
@@ -0,0 +1,2 @@
+.gitignore
+Dockerfile
\ No newline at end of file
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/.gitignore
new file mode 100644
index 000000000..b0b8376dc
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/.gitignore
@@ -0,0 +1,2 @@
+/app
+/extract.sh
\ No newline at end of file
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/Dockerfile b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/Dockerfile
new file mode 100644
index 000000000..80bc9347f
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/Dockerfile
@@ -0,0 +1,13 @@
+FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
+ARG RUNTIME=win-x64
+
+COPY . .
+WORKDIR /src
+RUN dotnet restore -r $RUNTIME
+RUN dotnet publish -c Release --no-restore -o /app
+
+FROM busybox:latest
+WORKDIR /app
+COPY --from=build /app .
+
+
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/.gitignore
new file mode 100644
index 000000000..fbfccf63c
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/.gitignore
@@ -0,0 +1 @@
+wwwroot/lib/
\ No newline at end of file
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/LibManSample.csproj b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/LibManSample.csproj
new file mode 100644
index 000000000..fccef1d18
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/LibManSample.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/Program.cs b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/Program.cs
new file mode 100644
index 000000000..243205ac6
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/Program.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+
+namespace LibManSample
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStartup();
+ }
+}
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/Startup.cs b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/Startup.cs
new file mode 100644
index 000000000..b7c70b4e7
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/Startup.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace LibManSample
+{
+ public class Startup
+ {
+ // This method gets called by the runtime. Use this method to add services to the container.
+ // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+ public void ConfigureServices(IServiceCollection services)
+ {
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.Run(async (context) =>
+ {
+ await context.Response.WriteAsync("Hello World!");
+ });
+ }
+ }
+}
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/libman.json b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/libman.json
new file mode 100644
index 000000000..2c95941b3
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/libman.json
@@ -0,0 +1,24 @@
+{
+ "version": "1.0",
+ "defaultProvider": "cdnjs",
+ "libraries": [
+ {
+ "library": "jquery@3.3.1",
+ "files": [
+ "jquery.min.js",
+ "jquery.js",
+ "jquery.min.map"
+ ],
+ "destination": "wwwroot/lib/jquery/"
+ },
+ {
+ "provider": "filesystem",
+ "library": "vendor",
+ "files": [
+ "lodash.js",
+ "lodash.min.js"
+ ],
+ "destination": "wwwroot/lib/lodash/"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.js b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.js
new file mode 100644
index 000000000..3352fb89b
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.js
@@ -0,0 +1 @@
+// jk! nothing to see here!
\ No newline at end of file
diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.min.js b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.min.js
new file mode 100644
index 000000000..9ba7bbb31
--- /dev/null
+++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.min.js
@@ -0,0 +1 @@
+// still nothing here...
\ No newline at end of file