diff --git a/README.md b/README.md index 46d8efa8..3f3c993c 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,15 @@ View the full documentation for NuGetDefense [here](https://digitalcoyote.github ## Features * Uses Multiple Sources to check for known vulnerabilities in third-party libraries (NuGet packages) - * [OSS Index](https://ossindex.sonatype.org/) + * [OSS Index](https://ossindex.sonatype.org/) (Caching Coming Soon!) * [National Vulnerability Database](https://nvd.nist.gov/) (Optionally Self-Updating) + * [Google's Open Source Vulnerabilities Database](https://osv.dev/) ([Coming Soon!](https://github.com/digitalcoyote/NuGetDefense/discussions/53)) * Simple installation/configuration: the [NuGet Package](https://www.nuget.org/packages/NuGetDefense/) is all you need. * Transitive Dependency Checking * SDK style projects only (older project format is not supported by the dotnet cli) * Uses the versions resolved by the dotnet cli at build +* Project Reference Scanning + * Scan all projects in a hierarchy by installing NuGet Defense to the top level package ([pre-release](https://www.nuget.org/packages/NuGetDefense/2.1.0-pre0011)) * Allow breaking the build based on severity of vulnerability. * Ignore specific vulnerabilities/packages. * Sensitive/Internal Packages filtering diff --git a/Src/NuGetDefense.sln.DotSettings b/Src/NuGetDefense.sln.DotSettings new file mode 100644 index 00000000..05363485 --- /dev/null +++ b/Src/NuGetDefense.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Src/NuGetDefense/Configuration/Settings.cs b/Src/NuGetDefense/Configuration/Settings.cs index 1c679ab5..64e3f9f4 100644 --- a/Src/NuGetDefense/Configuration/Settings.cs +++ b/Src/NuGetDefense/Configuration/Settings.cs @@ -10,7 +10,7 @@ namespace NuGetDefense.Configuration { public class Settings { - public bool WarnOnly { get; set; } = false; + public bool WarnOnly { get; set; } public FileLogSettings Log { @@ -18,17 +18,18 @@ public FileLogSettings Log set { Logs = new[] {value}; } } - public VulnerabilityReportsSettings VulnerabilityReports { get; set; } = new VulnerabilityReportsSettings(); + public VulnerabilityReportsSettings VulnerabilityReports { get; set; } = new(); public FileLogSettings[] Logs { get; set; } public bool CheckTransitiveDependencies { get; set; } = true; + public bool CheckReferencedProjects{ get; set; } - public BuildErrorSettings ErrorSettings { get; set; } = new BuildErrorSettings(); + public BuildErrorSettings ErrorSettings { get; set; } = new(); - public RemoteVulnerabilitySourceConfiguration OssIndex { get; set; } = new RemoteVulnerabilitySourceConfiguration(); + public RemoteVulnerabilitySourceConfiguration OssIndex { get; set; } = new(); public OfflineVulnerabilitySourceConfiguration NVD { get; set; } = - new OfflineVulnerabilitySourceConfiguration(); + new(); public string[] SensitivePackages { get; set; } = new string[0]; diff --git a/Src/NuGetDefense/NuGetDefense.csproj b/Src/NuGetDefense/NuGetDefense.csproj index 4100aa21..f9c282b5 100644 --- a/Src/NuGetDefense/NuGetDefense.csproj +++ b/Src/NuGetDefense/NuGetDefense.csproj @@ -8,7 +8,7 @@ NuGetDefense ~ Check for Known Vulnerabilities at Build NuGetDefense was inspired by [OWASP SafeNuGet](https://nuget.org/packages/SafeNuGet/) but aims to check with multiple sources for known vulnerabilities. Curtis Carter 2020 - 8 + 9 Debug;Release;DotNetTool AnyCPU https://digitalcoyote.github.io/NuGetDefense/ @@ -30,15 +30,19 @@ nugetdefense 1.0.15 + + + - - - - + + + + + - + diff --git a/Src/NuGetDefense/NuGetDefense.nuspec b/Src/NuGetDefense/NuGetDefense.nuspec index 30b4c6f1..8a6f1ed3 100644 --- a/Src/NuGetDefense/NuGetDefense.nuspec +++ b/Src/NuGetDefense/NuGetDefense.nuspec @@ -3,7 +3,7 @@ NuGetDefense NuGetDefense - 1.0.15.1 + 1.0.16 Curtis Carter Curtis Carter https://digitalcoyote.github.io/NuGetDefense/ diff --git a/Src/NuGetDefense/Program.cs b/Src/NuGetDefense/Program.cs index bd630bb4..351d27c7 100644 --- a/Src/NuGetDefense/Program.cs +++ b/Src/NuGetDefense/Program.cs @@ -2,21 +2,25 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Serialization; +using ByteDev.DotNet.Project; +using ByteDev.DotNet.Solution; using NuGet.Versioning; using NuGetDefense.Configuration; using NuGetDefense.Core; -using NuGetDefense.OSSIndex; +using NuGetDefense.NVD; using Serilog; using static NuGetDefense.UtilityMethods; +using Scanner = NuGetDefense.OSSIndex.Scanner; namespace NuGetDefense { - internal class Program + internal static class Program { private static readonly string UserAgentString = @$"NuGetDefense/{Version}"; @@ -24,8 +28,9 @@ internal class Program private static string _nuGetFile; private static string _projectFileName; - private static NuGetPackage[] _pkgs; + private static Dictionary _projects; private static Settings _settings; + public static int NumberOfVulnerabilities; /// /// args[0] is expected to be the path to the project file. @@ -38,8 +43,10 @@ private static int Main(string[] args) { Console.WriteLine($"NuGetDefense v{Version}"); Console.WriteLine("-------------"); - Console.WriteLine("\nUsage:"); - Console.WriteLine(" nugetdefense projectFile.proj TargetFrameworkMoniker"); + Console.WriteLine($"{Environment.NewLine}Usage:"); + Console.WriteLine($"{Environment.NewLine} nugetdefense projectFile.proj TargetFrameworkMoniker"); + Console.WriteLine($"{Environment.NewLine} nugetdefense SolutionFile.sln Release"); + Console.WriteLine($"{Environment.NewLine} nugetdefense SolutionFile.sln Debug|Any CPU"); return 0; } #endif @@ -52,20 +59,70 @@ private static int Main(string[] args) Log.Logger.Verbose("Logging Configured"); Log.Logger.Verbose("Started NuGetDefense with arguments: {args}", args); - var nugetFile = new NuGetFile(args[0]); - _nuGetFile = nugetFile.Path; - Log.Logger.Verbose("NuGetFile Path: {nugetFilePath}", _nuGetFile); + var targetFramework = args.Length == 2 ? args[1] : ""; + if (args[0].EndsWith(".sln", StringComparison.OrdinalIgnoreCase)) + { + var projects = DotNetSolution.Load(args[0]).Projects.Select(p => p.Path).ToArray(); + var specificFramework = !string.IsNullOrWhiteSpace(targetFramework); + if (specificFramework) + { + Log.Logger.Information("Target Framework: {framework}", targetFramework); + } - var targetFramework = args.Length > 1 ? args[1] : ""; - Log.Logger.Information("Target Framework: {framework}", string.IsNullOrWhiteSpace(targetFramework) ? "Undefined" : targetFramework); - Log.Logger.Verbose("Loading Packages"); - Log.Logger.Verbose("Transitive Dependencies Included: {CheckTransitiveDependencies}", _settings.CheckTransitiveDependencies); - _pkgs = nugetFile.LoadPackages(targetFramework, _settings.CheckTransitiveDependencies).Values.ToArray(); - var nonSensitivePackages = GetNonSensitivePackages(_pkgs); + _projects = LoadMultipleProjects(args[0], projects, specificFramework, targetFramework, true); + } + else if (_settings.CheckReferencedProjects) + { + var projects = new List{args[0]}; + GetProjectsReferenced(in args[0], in projects); + var specificFramework = !string.IsNullOrWhiteSpace(targetFramework); + if (specificFramework) + { + Log.Logger.Information("Target Framework: {framework}", targetFramework); + } + + _projects = LoadMultipleProjects(args[0], projects.ToArray(), specificFramework, targetFramework, false); + } + else + { + var nugetFile = new NuGetFile(args[0]); + _nuGetFile = nugetFile.Path; + + Log.Logger.Verbose("NuGetFile Path: {nugetFilePath}", _nuGetFile); + + Log.Logger.Information("Target Framework: {framework}", string.IsNullOrWhiteSpace(targetFramework) ? "Undefined" : targetFramework); + Log.Logger.Verbose("Loading Packages"); + Log.Logger.Verbose("Transitive Dependencies Included: {CheckTransitiveDependencies}", _settings.CheckTransitiveDependencies); + + if (_settings.CheckTransitiveDependencies && nugetFile.PackagesConfig) + { + var projects = DotNetProject.Load(args[0]).ProjectReferences.Select(p => p.FilePath).ToArray(); + var specificFramework = !string.IsNullOrWhiteSpace(targetFramework); + if (specificFramework) + { + Log.Logger.Information("Target Framework: {framework}", targetFramework); + } + + _projects = LoadMultipleProjects(args[0], projects, specificFramework, targetFramework); + } + else + { + _projects = new Dictionary(); + _projects.Add(nugetFile.Path, nugetFile.LoadPackages(targetFramework, _settings.CheckTransitiveDependencies).Values.ToArray()); + } + } + + GetNonSensitivePackages( out var nonSensitivePackages); if (_settings.ErrorSettings.IgnoredPackages.Length > 0) - IgnorePackages(_pkgs, _settings.ErrorSettings.IgnoredPackages, out _pkgs); - Log.Logger.Information("Loaded {packageCount} packages", _pkgs.Length); + { + foreach (var (project, packages) in _projects) + { + IgnorePackages(in packages, _settings.ErrorSettings.IgnoredPackages, out var projPackages); + _projects[project] = projPackages; + } + } + Log.Logger.Information("Loaded {packageCount} packages", _projects.Sum(p => p.Value.Length)); if (_settings.ErrorSettings.BlockedPackages.Length > 0) CheckBlockedPackages(); if (_settings.ErrorSettings.AllowedPackages.Length > 0) CheckAllowedPackages(); @@ -75,18 +132,21 @@ private static int Main(string[] args) Log.Logger.Verbose("Checking with OSSIndex for Vulnerabilities"); vulnDict = new Scanner(_nuGetFile, _settings.OssIndex.BreakIfCannotRun, UserAgentString, _settings.OssIndex.Username, _settings.OssIndex.ApiToken) - .GetVulnerabilitiesForPackages(nonSensitivePackages); + .GetVulnerabilitiesForPackages(nonSensitivePackages.SelectMany(p => p.Value).ToArray()); } if (_settings.NVD.Enabled) { Log.Logger.Verbose("Checking the embedded NVD source for Vulnerabilities"); - vulnDict = - new NVD.Scanner(_nuGetFile, TimeSpan.FromSeconds(_settings.NVD.TimeoutInSeconds), - _settings.NVD.BreakIfCannotRun, _settings.NVD.SelfUpdate) - .GetVulnerabilitiesForPackages(_pkgs, - vulnDict); + foreach (var (proj, pkgs) in _projects) + { + vulnDict = + new NVD.Scanner(_nuGetFile, TimeSpan.FromSeconds(_settings.NVD.TimeoutInSeconds), + _settings.NVD.BreakIfCannotRun, _settings.NVD.SelfUpdate) + .GetVulnerabilitiesForPackages(pkgs, + vulnDict); + } } Log.Logger.Information("ignoring {ignoredCVECount} Vulnerabilities", _settings.ErrorSettings.IgnoredCvEs.Length); @@ -94,7 +154,7 @@ private static int Main(string[] args) VulnerabilityData.IgnoreCVEs(vulnDict, _settings.ErrorSettings.IgnoredCvEs); ReportVulnerabilities(vulnDict); - if (vulnDict?.Count == 0) return 0; + return _settings.WarnOnly ? 0 : NumberOfVulnerabilities; } catch (Exception e) { @@ -104,8 +164,76 @@ private static int Main(string[] args) Log.Logger.Fatal(msBuildMessage); return -1; } + } + + private static void GetProjectsReferenced(in string proj, in List projectList) + { + var dir = Path.GetDirectoryName(proj); + foreach (var referencedProj in DotNetProject.Load(proj).ProjectReferences + .Select(p => Path.Combine(dir!, p.FilePath.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar)))) + { + if(!projectList.Contains(referencedProj)) + projectList.Add(referencedProj); + GetProjectsReferenced(in referencedProj, in projectList); + } + } + + private static Dictionary LoadMultipleProjects(string TopLevelProject, string[] projects, bool specificFramework, string targetFramework, bool solutionFile = false) + { + var projectPackages = new Dictionary(); + for (var i = 0; i < projects.Length; i++) + { + var pkgs = new List(); + + var project = projects[i]; + var path = Path.Combine(Path.GetDirectoryName(TopLevelProject)!, project + .Replace('\\', Path.DirectorySeparatorChar) + .Replace('/', Path.DirectorySeparatorChar)); + + var proj = DotNetProject.Load(path); + if (!specificFramework && proj.Format == ProjectFormat.New) + { + var monikersListBuilder = new StringBuilder(); + var monikers = proj.ProjectTargets.Select(t => t.Moniker).ToArray(); + monikersListBuilder.Append(monikers[0]); + for (var index = 1; index < monikers.Length; index++) + { + monikersListBuilder.Append($", {monikers[index]}"); + } + + Log.Logger.Information("Target Frameworks for {project}: {frameworks}", project, monikersListBuilder.ToString()); + var nugetFile = new NuGetFile(path); + pkgs.AddDistinctPackages(monikers.SelectMany(m => nugetFile.LoadPackages(m, _settings.CheckTransitiveDependencies).Values)); + } + else + { + pkgs.AddDistinctPackages(new NuGetFile(path).LoadPackages(targetFramework, _settings.CheckTransitiveDependencies).Values); + } + projectPackages.Add(path, pkgs.ToArray()); + } + + var nuGetFile = new NuGetFile(TopLevelProject); + _nuGetFile = nuGetFile.Path; + + if (!solutionFile && !projectPackages.ContainsKey(TopLevelProject)) projectPackages.Add(TopLevelProject, nuGetFile.LoadPackages(targetFramework, _settings.CheckTransitiveDependencies).Values.ToArray()); + + return projectPackages; + } + + private static void AddDistinctPackages(this List pkgs, IEnumerable newPkgs) + { + foreach (var pkg in newPkgs) + { + if (pkgs.Any(p => p.Id == pkg.Id && p.Version == pkg.Version)) continue; + + pkgs.Add(pkg); + } + } - return 0; + private static void ParseSolutionForProjects(string s) + { + // TODO: This will parse hte solution file into a list of projects relative to the solution file. + throw new NotImplementedException(); } /// @@ -113,17 +241,23 @@ private static int Main(string[] args) /// /// /// a list of packages that do not match the wild card strings in SensitivePackages - public static NuGetPackage[] GetNonSensitivePackages(NuGetPackage[] nuGetPackages) + public static void GetNonSensitivePackages(out Dictionary nonSensitives) { - var sensitiveRegexSet = _settings.SensitivePackages.Select(sp => Regex.Escape(sp).Replace(@"\*", ".*")); - return nuGetPackages.Where(p => !sensitiveRegexSet.Any(x => Regex.IsMatch(p.Id, x))).ToArray(); + nonSensitives = new Dictionary(); + var sensitiveRegexSet = _settings.SensitivePackages.Select(sp => Regex.Escape(sp).Replace(@"\*", ".*")).ToArray(); + if (!sensitiveRegexSet.Any()) return; + foreach (var (project, packages) in _projects) + { + nonSensitives.Add(project, packages.Where(p => !sensitiveRegexSet.Any(x => Regex.IsMatch(p.Id, x))).ToArray()); + } } private static void CheckAllowedPackages() { Log.Logger.Verbose("Checking Allowed Packages"); - foreach (var pkg in _pkgs.Where(p => !_settings.ErrorSettings.AllowedPackages.Any(b => + foreach (var (project, packages) in _projects) + foreach (var pkg in packages.Where(p => !_settings.ErrorSettings.AllowedPackages.Any(b => b.Id == p.Id && (string.IsNullOrWhiteSpace(b.Version) || VersionRange.Parse(p.Version).Satisfies(new NuGetVersion(b.Version)))))) { @@ -140,26 +274,30 @@ private static void ReportVulnerabilities(Dictionary p.Value.Length)); } else { Log.Logger.Verbose("Building report of Vulnerabilities found in {numberOfPackages} packages", vulnDict.Keys.Count); var vulnReporter = new VulnerabilityReporter(); - vulnReporter.BuildVulnerabilityTextReport(vulnDict, _pkgs, _nuGetFile, _settings.WarnOnly, - _settings.ErrorSettings.Cvss3Threshold); - if (_settings.VulnerabilityReports.OutputTextReport) Log.Logger.Information(vulnReporter.VulnerabilityTextReport); - foreach (var msBuildMessage in vulnReporter.MsBuildMessages) + + foreach (var (project, packages) in _projects) { - Console.WriteLine(msBuildMessage); - Log.Logger.Debug(msBuildMessage); + //TODO: Losing the right file somewhere here + vulnReporter.BuildVulnerabilityTextReport(vulnDict, packages, project, _settings.WarnOnly, + _settings.ErrorSettings.Cvss3Threshold, out NumberOfVulnerabilities); + if (_settings.VulnerabilityReports.OutputTextReport) Log.Logger.Information(vulnReporter.VulnerabilityTextReport); + foreach (var msBuildMessage in vulnReporter.MsBuildMessages) + { + Console.WriteLine(msBuildMessage); + Log.Logger.Debug(msBuildMessage); + } } if (string.IsNullOrWhiteSpace(_settings.VulnerabilityReports.JsonReportPath) && string.IsNullOrWhiteSpace(_settings.VulnerabilityReports.XmlReportPath)) return; - vulnReporter.BuildVulnerabilityReport(vulnDict, _pkgs, _nuGetFile, _settings.WarnOnly, - _settings.ErrorSettings.Cvss3Threshold); + vulnReporter.BuildVulnerabilityReport(vulnDict, _projects, _settings.WarnOnly); if (!string.IsNullOrWhiteSpace(_settings.VulnerabilityReports.JsonReportPath)) { var ops = new JsonSerializerOptions @@ -201,7 +339,8 @@ private static void ConfigureLogging() private static void CheckBlockedPackages() { - foreach (var pkg in _pkgs) + foreach ( var (proj, pkgs) in _projects) + foreach (var pkg in pkgs) { Log.Logger.Verbose("Checking to see if {packageName}:{version} is Blocked", pkg.Id, pkg.Version); @@ -214,7 +353,7 @@ private static void CheckBlockedPackages() continue; } - var msBuildMessage = MsBuild.Log(_nuGetFile, MsBuild.Category.Error, pkg.LineNumber, pkg.LinePosition, + var msBuildMessage = MsBuild.Log(proj, MsBuild.Category.Error, pkg.LineNumber, pkg.LinePosition, $"{pkg.Id}: {(string.IsNullOrEmpty(blockedPackage.CustomErrorMessage) ? "has been blocked and may not be used in this project" : blockedPackage.CustomErrorMessage)}"); Console.WriteLine(msBuildMessage); Log.Logger.Error(msBuildMessage); diff --git a/Src/NuGetDefense/UtilityMethods.cs b/Src/NuGetDefense/UtilityMethods.cs index 973550ad..3ec67528 100644 --- a/Src/NuGetDefense/UtilityMethods.cs +++ b/Src/NuGetDefense/UtilityMethods.cs @@ -3,9 +3,9 @@ namespace NuGetDefense { - public class UtilityMethods + public static class UtilityMethods { - public static void IgnorePackages(NuGetPackage[] pkgs, NuGetPackage[] ignorePackages, out NuGetPackage[] unIgnoredPackages) + public static void IgnorePackages(in NuGetPackage[] pkgs, NuGetPackage[] ignorePackages, out NuGetPackage[] unIgnoredPackages) { unIgnoredPackages = pkgs.Where(p => ignorePackages.All(ip => ip.Id != p.Id || !string.IsNullOrWhiteSpace(ip.Version) && !VersionRange.Parse(ip.Version).Satisfies(new NuGetVersion(p.Version)))).ToArray(); } diff --git a/Src/NuGetDefense/VulnerabilityReport.cs b/Src/NuGetDefense/VulnerabilityReport.cs index 17cf03c9..8cc81b0d 100644 --- a/Src/NuGetDefense/VulnerabilityReport.cs +++ b/Src/NuGetDefense/VulnerabilityReport.cs @@ -12,7 +12,8 @@ public class VulnerabilityReport public class VulnerableNuGetPackage { [XmlAttribute] public string Id { get; set; } - + + public string PackageUrl => $"pkg:nuget/{Id}@{Version}"; [XmlAttribute] public string Version { get; set; } public ReportedVulnerability[] Vulnerabilities { get; set; } diff --git a/Src/NuGetDefense/VulnerabilityReporter.cs b/Src/NuGetDefense/VulnerabilityReporter.cs index 8df9b8ff..6a6aaf75 100644 --- a/Src/NuGetDefense/VulnerabilityReporter.cs +++ b/Src/NuGetDefense/VulnerabilityReporter.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Text; +using Microsoft.VisualBasic.CompilerServices; using NuGetDefense.Core; namespace NuGetDefense @@ -10,12 +12,11 @@ public class VulnerabilityReporter private readonly bool _separateMsBuildMessages; public List MsBuildMessages; public VulnerabilityReport Report; - public string VulnerabilityTextReport; + public string VulnerabilityTextReport = ""; public VulnerabilityReporter(bool separateMsBuildMessages = true) { _separateMsBuildMessages = separateMsBuildMessages; - if (separateMsBuildMessages) MsBuildMessages = new List(); } /// @@ -23,51 +24,57 @@ public VulnerabilityReporter(bool separateMsBuildMessages = true) /// /// /// Parsed Packages - /// Either the project or packages file /// If True, suppresses all errors - /// Threshold CVSS score for error suppresion public void BuildVulnerabilityReport( Dictionary> vulnerabilityDictionary, - IEnumerable pkgs, string nuGetFile, bool warnOnly, double cvss3Threshold) + Dictionary projects, bool warnOnly) { - Report = new VulnerabilityReport(); - Report.VulnerabilitiesCount = vulnerabilityDictionary.Sum(x => x.Value.Count); - var nuGetPackages = pkgs as NuGetPackage[] ?? pkgs.ToArray(); - Report.Packages = nuGetPackages.Where(p => - p.LineNumber != null && vulnerabilityDictionary.ContainsKey(p.Id.ToLower())).Select(p => new VulnerableNuGetPackage {Id = p.Id, Version = p.Version}).ToArray(); - - foreach (var pkg in nuGetPackages.Where(p => - p.LineNumber != null && vulnerabilityDictionary.ContainsKey(p.Id.ToLower()))) + Report = new VulnerabilityReport {VulnerabilitiesCount = vulnerabilityDictionary.Sum(x => x.Value.Count)}; + foreach (var (proj, pkgs) in projects) { - var vulnerabilities = vulnerabilityDictionary[pkg.Id.ToLower()]; - Report.Packages.Where(p => p.Id.ToLower() == pkg.Id.ToLower()).First() - .Vulnerabilities = vulnerabilities.Select(v => new ReportedVulnerability + var nuGetPackages = pkgs as NuGetPackage[] ?? pkgs.ToArray(); + Report.Packages = nuGetPackages.Where(p => + p.LineNumber != null && vulnerabilityDictionary.ContainsKey(p.PackageUrl.ToLower())) + .Select(p => new VulnerableNuGetPackage {Id = p.Id, Version = p.Version}) + .ToArray(); + + foreach (var pkg in nuGetPackages.Where(p => + p.LineNumber != null && vulnerabilityDictionary.ContainsKey(p.PackageUrl.ToLower()))) { - Description = v.Value.Description, - Cve = v.Value.Cve, - Cwe = v.Value.Cwe, - CvssScore = v.Value.CvssScore, - CvssVector = v.Value.Vector.ToString() - }).ToArray(); + var vulnerabilities = vulnerabilityDictionary[pkg.PackageUrl.ToLower()]; + Report.Packages.First(p => string.Equals(p.Id, pkg.Id, StringComparison.CurrentCultureIgnoreCase) && p.Version == pkg.Version) + .Vulnerabilities = vulnerabilities.Select(v => new ReportedVulnerability + { + Description = v.Value.Description, + Cve = v.Value.Cve, + Cwe = v.Value.Cwe, + CvssScore = v.Value.CvssScore, + CvssVector = v.Value.Vector.ToString() + }).ToArray(); + } } } - public void BuildVulnerabilityTextReport( - Dictionary> vulnerabilityDictionary, - IEnumerable pkgs, string nuGetFile, bool warnOnly, double cvss3Threshold) + public void BuildVulnerabilityTextReport(Dictionary> vulnerabilityDictionary, + IEnumerable pkgs, string nuGetFile, bool warnOnly, double cvss3Threshold, out int numberOfVulns) { - var logBuilder = new StringBuilder(); + numberOfVulns = 0; + if (_separateMsBuildMessages) MsBuildMessages = new List(); + + var logBuilder = new StringBuilder(VulnerabilityTextReport); var nuGetPackages = pkgs as NuGetPackage[] ?? pkgs.ToArray(); - logBuilder.AppendLine($"{vulnerabilityDictionary.Sum(ve => ve.Value.Count)} vulnerabilities found in {nuGetPackages.Count()} packages."); - foreach (var pkg in nuGetPackages.Where(p => - p.LineNumber != null && vulnerabilityDictionary.ContainsKey(p.Id.ToLower()))) + logBuilder.AppendLine($"{vulnerabilityDictionary.Sum(ve => ve.Value.Count)} vulnerabilities found in {nuGetPackages.Count()} packages for {nuGetFile}."); + foreach (var pkg in nuGetPackages.Where(p => vulnerabilityDictionary.ContainsKey(p.PackageUrl.ToLower()))) { - var vulnerabilities = vulnerabilityDictionary[pkg.Id.ToLower()]; + var vulnerabilities = vulnerabilityDictionary[pkg.PackageUrl.ToLower()]; logBuilder.AppendLine("*************************************"); warnOnly = warnOnly || !vulnerabilities.Any(v => v.Value.CvssScore >= cvss3Threshold); + + if (!warnOnly) numberOfVulns++; + // TODO: Dependencies will need to be listed by package url when this is used. var dependantVulnerabilities = pkg.Dependencies.Where(dep => vulnerabilityDictionary.ContainsKey(dep)); var vulnTotalMSbuildMessage = MsBuild.Log(nuGetFile, warnOnly ? MsBuild.Category.Warning : MsBuild.Category.Error, pkg.LineNumber, pkg.LinePosition, @@ -92,6 +99,7 @@ public void BuildVulnerabilityTextReport( foreach (var cve in vulnerabilities.Keys) { warnOnly = warnOnly || vulnerabilities[cve].CvssScore <= cvss3Threshold && vulnerabilities[cve].CvssScore > -1; + if (!warnOnly) numberOfVulns++; var vulnMsBuildMessage = MsBuild.Log(nuGetFile, warnOnly ? MsBuild.Category.Warning : MsBuild.Category.Error, cve, pkg.LineNumber, pkg.LinePosition, $"{vulnerabilities[cve].Description}"); @@ -120,6 +128,7 @@ public void BuildVulnerabilityTextReport( { warnOnly = warnOnly || vulnerabilities[cve].CvssScore <= cvss3Threshold; + if (!warnOnly) numberOfVulns++; var vulnMsBuildMessage = MsBuild.Log(nuGetFile, warnOnly ? MsBuild.Category.Warning : MsBuild.Category.Error, cve, pkg.LineNumber, pkg.LinePosition, $"{dependancy}: {vulnerabilities[cve].Description}"); diff --git a/Src/NuGetDefenseTests/NuGetDefenseTests.csproj b/Src/NuGetDefenseTests/NuGetDefenseTests.csproj index 632e49e8..2dfff2ed 100644 --- a/Src/NuGetDefenseTests/NuGetDefenseTests.csproj +++ b/Src/NuGetDefenseTests/NuGetDefenseTests.csproj @@ -11,13 +11,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Src/NuGetDefenseTests/VulnerabilityReportsTest.cs b/Src/NuGetDefenseTests/VulnerabilityReportsTest.cs index 178a2634..bfff335d 100644 --- a/Src/NuGetDefenseTests/VulnerabilityReportsTest.cs +++ b/Src/NuGetDefenseTests/VulnerabilityReportsTest.cs @@ -35,7 +35,8 @@ public void ReportVulnerabilityWithNullReferences() var pkgs = new[] {new NuGetPackage {LineNumber = 1, Id = "TestPkg", Version = "1.0.1"}}; var reporter = new VulnerabilityReporter(); - reporter.BuildVulnerabilityTextReport(vulnDict, pkgs, "NuGetDefense.dll", false, 0D); + reporter.BuildVulnerabilityTextReport(vulnDict, pkgs, "NuGetDefense.dll", false, 0D, out var vulnNumber); + Assert.Equal(0, vulnNumber); //TODO: Assert MSBuildMessages and VulnerabilityReport } @@ -58,7 +59,7 @@ public void IgnoreVulnerabilitiesForPackage() new NuGetPackage {LineNumber = 4, Id = "TestPkg4", Version = null} }; - IgnorePackages(pkgs, ignorePkgs, out pkgs); + IgnorePackages(in pkgs, ignorePkgs, out pkgs); Assert.True(pkgs.Length == 1); Assert.True(pkgs[0].Id == "TestPkg"); Assert.True(pkgs[0].Version == "1.0.1"); diff --git a/build/_build.csproj b/build/_build.csproj index 9166225c..8e772ab4 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.1 + net5.0 CS0649;CS0169 .. @@ -17,7 +17,7 @@ - +