Skip to content

Commit

Permalink
Transitive Dependency Checking
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalcoyote committed Jun 30, 2020
1 parent 926f181 commit e3dd4cf
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 30 deletions.
1 change: 1 addition & 0 deletions Src/NuGetDefense/Configuration/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace NuGetDefense.Configuration
public class Settings
{
public bool WarnOnly { get; set; } = false;
public bool CheckTransitiveDependencies { get; set; } = true;

public BuildErrorSettings ErrorSettings { get; set; } = new BuildErrorSettings();

Expand Down
2 changes: 1 addition & 1 deletion Src/NuGetDefense/NuGetDefense.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand Down
2 changes: 1 addition & 1 deletion Src/NuGetDefense/NuGetDefense.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<metadata>
<id>NuGetDefense</id>
<title>NuGetDefense</title>
<version>1.0.7.2</version>
<version>1.0.8.0-beta</version>
<authors>Curtis Carter</authors>
<owners>Curtis Carter</owners>
<projectUrl>https://github.com/DigitalCoyote/NuGetDefense</projectUrl>
Expand Down
134 changes: 106 additions & 28 deletions Src/NuGetDefense/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Xml;
using System.Xml.Linq;
using NuGet.Versioning;
Expand All @@ -14,7 +16,7 @@ namespace NuGetDefense
internal class Program
{
private static string _nuGetFile;
private const string UserAgentString = @"NuGetDefense/1.0.7.2 (https://github.com/digitalcoyote/NuGetDefense/blob/master/README.md)";
private const string UserAgentString = @"NuGetDefense/1.0.8.0-beta (https://github.com/digitalcoyote/NuGetDefense/blob/master/README.md)";
private static NuGetPackage[] _pkgs;
private static Settings _settings;

Expand All @@ -25,10 +27,7 @@ internal class Program
private static void Main(string[] args)
{
_settings = Settings.LoadSettings(Path.GetDirectoryName(args[0]));
var pkgConfig = Path.Combine(Path.GetDirectoryName(args[0]), "packages.config");
_nuGetFile = File.Exists(pkgConfig) ? pkgConfig : args[0];

_pkgs = LoadPackages(_nuGetFile);
_pkgs = LoadPackages(args[0], _settings.CheckTransitiveDependencies).Values.ToArray();
if (_settings.ErrorSettings.BlackListedPackages.Length > 0) CheckForBlacklistedPackages();
if (_settings.ErrorSettings.WhiteListedPackages.Length > 0)
foreach (var pkg in _pkgs.Where(p => !_settings.ErrorSettings.WhiteListedPackages.Any(b =>
Expand Down Expand Up @@ -64,6 +63,10 @@ private static void CheckForBlacklistedPackages()
}
}

/// <summary>
/// Removes CVE's from the vulnerablity reports if they ar ein the ignoredCVEs list
/// </summary>
/// <param name="vulnDict"></param>
private static void IgnoreCVEs(Dictionary<string, Dictionary<string, Vulnerability>> vulnDict)
{
foreach (var vuln in vulnDict.Values)
Expand All @@ -76,42 +79,117 @@ private static void IgnoreCVEs(Dictionary<string, Dictionary<string, Vulnerabili
/// </summary>
/// <param name="nuGetPackages"> Array of Packages used as the source</param>
/// <returns>Filtered list of packages</returns>
private static NuGetPackage[] IgnorePackages(IEnumerable<NuGetPackage> nuGetPackages)
private static Dictionary<string, NuGetPackage> IgnorePackages(Dictionary<string, NuGetPackage> nuGetPackages)
{
return nuGetPackages.Where(nuget => !_settings.ErrorSettings.IgnoredPackages
.Where(ignoredNupkg => ignoredNupkg.Id == nuget.Id)
return (Dictionary<string, NuGetPackage>) nuGetPackages.Where(nuget => !_settings.ErrorSettings.IgnoredPackages
.Where(ignoredNupkg => ignoredNupkg.Id == nuget.Value.Id)
.Any(ignoredNupkg => !VersionRange.TryParse(ignoredNupkg.Version, out var versionRange) ||
versionRange.Satisfies(new NuGetVersion(nuget.Version)))).ToArray();
versionRange.Satisfies(new NuGetVersion(nuget.Value.Version))))
.ToDictionary(kv => kv.Key, kv => kv.Value);
}

/// <summary>
/// Loads NuGet packages in use form packages.config or PackageReferences in the project file
/// </summary>
/// <returns></returns>
public static NuGetPackage[] LoadPackages(string packageSource)
public static Dictionary<string, NuGetPackage> LoadPackages(string projectFile, bool checkTransitiveDependencies = true)
{
IEnumerable<NuGetPackage> pkgs;
if (Path.GetFileName(packageSource) == "packages.config")
pkgs = XElement.Load(packageSource, LoadOptions.SetLineInfo).DescendantsAndSelf("package")
.Where(x => RemoveInvalidVersions(x))
.Select(x => new NuGetPackage
{
Id = (x.AttributeIgnoreCase("id")).Value, Version = x.AttributeIgnoreCase("version").Value,
LineNumber = ((IXmlLineInfo) x).LineNumber, LinePosition = ((IXmlLineInfo) x).LinePosition
});
else
pkgs = XElement.Load(packageSource, LoadOptions.SetLineInfo).DescendantsAndSelf("PackageReference")
.Where(x => RemoveInvalidVersions(x))
.Select(
x => new NuGetPackage
var pkgConfig = Path.Combine(Path.GetDirectoryName(projectFile), "packages.config");
var legacy = File.Exists(pkgConfig);
_nuGetFile = legacy ? pkgConfig : projectFile;
Dictionary<string, NuGetPackage> pkgs = new Dictionary<string, NuGetPackage>();

if (Path.GetFileName(projectFile) == "packages.config")
pkgs = XElement.Load(projectFile, LoadOptions.SetLineInfo).DescendantsAndSelf("package")
.Where(x => RemoveInvalidVersions(x))
.Select(x => new NuGetPackage
{
Id = x.AttributeIgnoreCase("Include").Value, Version = x.AttributeIgnoreCase("Version").Value,
Id = (x.AttributeIgnoreCase("id")).Value, Version = x.AttributeIgnoreCase("version").Value,
LineNumber = ((IXmlLineInfo) x).LineNumber, LinePosition = ((IXmlLineInfo) x).LinePosition
});
}).ToDictionary(p => p.Id);
else
pkgs = XElement.Load(projectFile, LoadOptions.SetLineInfo).DescendantsAndSelf("PackageReference")
.Where(x => RemoveInvalidVersions(x))
.Select(
x => new NuGetPackage
{
Id = x.AttributeIgnoreCase("Include").Value,
Version = x.AttributeIgnoreCase("Version").Value,
LineNumber = ((IXmlLineInfo) x).LineNumber,
LinePosition = ((IXmlLineInfo) x).LinePosition
}).ToDictionary(p => p.Id);;
if(!legacy)
{
var resolvedPackages = dotnetListPackages(projectFile);

if (checkTransitiveDependencies)
{
foreach (var (key, value) in pkgs.Where(package => resolvedPackages.ContainsKey(package.Key)))
{
resolvedPackages[key].LineNumber = value.LineNumber;
resolvedPackages[key].LinePosition = value.LinePosition;
}

pkgs = resolvedPackages;
}
else
{
foreach (var (key, _) in pkgs)
{
pkgs[key].Version = resolvedPackages[key].Version;
}
}
}
else if (checkTransitiveDependencies)
{
Console.WriteLine(
$"{_nuGetFile} : Warning : Transitive depency checking skipped. 'dotnet list package --include-transitive' only supports SDK style NuGet Package References");
}

if (_settings.ErrorSettings.IgnoredPackages.Length > 0) pkgs = IgnorePackages(pkgs);

return pkgs;
}

/// <summary>
/// Uses 'dotnet list' to get a list of resolved versions and dependencies
/// </summary>
/// <param name="projectFile"></param>
/// <returns></returns>
private static Dictionary<string, NuGetPackage> dotnetListPackages(string projectFile)
{
Dictionary<string, NuGetPackage> pkgs;
var startInfo = new ProcessStartInfo("dotnet")
{
Arguments = $"list {projectFile} package --include-transitive",
CreateNoWindow = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
var dotnet = new Process() {StartInfo = startInfo};
dotnet.Start();
dotnet.WaitForExit();
var output = dotnet.StandardOutput.ReadToEnd();

var lines = output.Split(Environment.NewLine);
var topLevelPackageResolvedIndex = lines[2].IndexOf("Resolved") - 8;
var transitiveHeaderIndex = Array.FindIndex(lines, l => l.Contains("Transitive Package"));
pkgs = lines.Skip(3).Take(transitiveHeaderIndex - 4).Select(l => new NuGetPackage
{
Id = l.Substring(l.IndexOf(">") + 2, topLevelPackageResolvedIndex - l.IndexOf(">") + 3).Trim(),
Version = l.Substring(topLevelPackageResolvedIndex).Trim()
}).ToDictionary(p => p.Id);;

var transitiveResolvedColumnStart = output.Split(Environment.NewLine)[transitiveHeaderIndex].IndexOf("Resolved") - 8;

if (_settings.ErrorSettings.IgnoredPackages.Length > 0) pkgs = IgnorePackages(pkgs);
pkgs.Concat(lines.Skip(transitiveHeaderIndex + 1).SkipLast(2).Where(s => !string.IsNullOrWhiteSpace(s))
.Select(l => new NuGetPackage
{
Id = l.Substring(l.IndexOf(">") + 2, transitiveResolvedColumnStart - l.IndexOf(">") + 3).Trim(),
Version = l.Substring(transitiveResolvedColumnStart).Trim()
}).ToDictionary(p => p.Id));

return pkgs as NuGetPackage[] ?? pkgs.ToArray();
return pkgs;
}

private static bool RemoveInvalidVersions(XElement x)
Expand Down

0 comments on commit e3dd4cf

Please sign in to comment.