Skip to content

Commit

Permalink
Merge pull request #46 from tom-englert/Mitigation
Browse files Browse the repository at this point in the history
Mitigation
  • Loading branch information
sboulema authored Oct 21, 2024
2 parents 234a5cf + f694cdd commit a830f9f
Show file tree
Hide file tree
Showing 52 changed files with 99 additions and 35 deletions.
26 changes: 17 additions & 9 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,28 @@
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Community.VisualStudio.Toolkit.17" Version="17.0.522" />
<PackageVersion Include="Community.VisualStudio.Toolkit.17" Version="17.0.527" />
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="DataGridExtensions" Version="2.6.0" />
<PackageVersion Include="Fody" Version="6.8.2" />
<PackageVersion Include="Microsoft.Build" Version="[17.4.0]" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="[17.4.0]" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageVersion Include="Microsoft.VSSDK.BuildTools" Version="17.11.435" />
<PackageVersion Include="NuGet.Protocol" Version="6.11.0" />
<PackageVersion Include="Nullable.Extended.Analyzer" Version="1.15.6169" />
<PackageVersion Include="Microsoft.Build" Version="[17.4.0]" Justification="VSIX limitation"/>
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="[17.4.0]" Justification="VSIX limitation"/>
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageVersion Include="Microsoft.VSSDK.BuildTools" Version="17.12.2069" />
<PackageVersion Include="NuGet.Protocol" Version="[6.5.1]" Justification="VSIX limitation (Newtonsoft.Json 13.0.1)" />
<PackageVersion Include="PropertyChanged.Fody" Version="4.1.0" />
<PackageVersion Include="Throttle.Fody" Version="1.7.0" />
<PackageVersion Include="TomsToolbox.Essentials" Version="2.18.1" />
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.18.1" />
<PackageVersion Include="TomsToolbox.Essentials" Version="2.20.0" />
<PackageVersion Include="TomsToolbox.Wpf.Styles" Version="2.20.0" />
<PackageVersion Include="VSIX-SdkProjectAdapter" Version="3.0.0" />
</ItemGroup>

<ItemGroup Label="Transitive fixes">
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>

<ItemGroup Label="Mitigations">
<PackageMitigation Include="MessagePack" Version="2.2.85" Justification="VSIX limitation" />
<PackageMitigation Include="Microsoft.IO.Redist" Version="6.0.0" Justification="VSIX limitation" />
</ItemGroup>
</Project>
6 changes: 3 additions & 3 deletions NuGetMonitor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.33808.371
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGetMonitor", "src\NuGetMonitor.csproj", "{C1A48EF4-3E62-4C4C-8A3E-E1BA33FCAF14}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6574B5FA-865B-4150-A0F9-72420A7D5AA4}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Expand All @@ -15,7 +13,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\workflows\workflow.yml = .github\workflows\workflow.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGetMonitor.Model", "NuGetMonitor.Model\NuGetMonitor.Model.csproj", "{D1E7ECAB-56A7-4283-A2BC-847AB3B7F72A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGetMonitor", "src\NuGetMonitor\NuGetMonitor.csproj", "{C1A48EF4-3E62-4C4C-8A3E-E1BA33FCAF14}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGetMonitor.Model", "src\NuGetMonitor.Model\NuGetMonitor.Model.csproj", "{D1E7ECAB-56A7-4283-A2BC-847AB3B7F72A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ for the installed NuGet packages in the current solution.

Dependent on the size of the solution it may take some time until the info bars appear.

---
### Package Manager

The package manager can be opened via the entry in the `Tools` menu.
Expand All @@ -44,16 +45,30 @@ The package manager shows all installed packages of the current solution. Updati

Compared to the original NuGet Package Manager updating packages is very fast, because the package version is instantly updated without validation against other packages - however version conflicts may show up only at the next build and have to be resolved manually.

![ToolWindow](art/ToolWindow.png)

Shared package references, e.g. in the `Directory.Build.props` file, are handled gracefully, and will not be replaced by `Update` entries in every project.

![ToolWindow](art/ToolWindow.png)
CentralPackageManagement (`PackageVersion` entries) are supported as well.

A justification property can be added to `PackageReference` or `PackageVersion` entries, to e.g. document why a reference is pinned and can't be updated
```xml
<PackageReference Include="Newtonsoft.Json" Version="[13.0.1]" Justification="Can't update due to Visual Studio extension limitations">
```

A mitigation element can be added to suppress warnings for transitive dependencies that can't be updated due to project limitations but have been evaluated to not affect the product security.
```xml
<PackageMitigation Include="Newtonsoft.Json" Version="13.0.1" Justification="Can't update due to Visual Studio extension limitations">
```
---
### Dependency Tree

This view allows to investigate how transitive depdencies are introduced into the projects.
This view allows to investigate how transitive dependencies are introduced into the projects.

It lists all transitive dependencies per project, and shows their ancestor tree, where the terminal bold entry is the package reference used in the project.

The context menu for every entry offers to copy ready made XML snippets for `PackageReference`, `PackageVersion` or `PackageMitigation` that can be directly inserted into the project file to fix transitive dependencies.

![DependencyTree](art/DependencyTree.png)

## Thanks
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ public PackageInfo(PackageIdentity packageIdentity, Package package, NuGetSessio

public bool IsOutdated { get; init; }

public string? VulnerabilityMitigation { get; set; }

public string Issues => string.Join(", ", GetIssues().ExceptNullItems());

public bool HasIssues => IsDeprecated || IsVulnerable;
public bool HasIssues => IsDeprecated || (IsVulnerable && VulnerabilityMitigation.IsNullOrEmpty());

public Uri ProjectUrl { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Diagnostics;
using Microsoft.Build.Evaluation;
using NuGet.Frameworks;
using NuGet.Packaging.Core;
using NuGet.Versioning;
using NuGetMonitor.Model.Services;
using TomsToolbox.Essentials;

Expand All @@ -19,13 +21,16 @@ public ProjectInTargetFramework(Project project, NuGetFramework targetFramework)
TargetFramework = targetFramework;
CentralVersionMap = GetCentralVersionMap(project);
IsTransitivePinningEnabled = IsCentralVersionManagementEnabled && project.GetProperty("CentralPackageTransitivePinningEnabled").IsTrue();
PackageMitigations = GetPackageMitigations(project);
}

public Project Project { get; init; }

public NuGetFramework TargetFramework { get; init; }

public ReadOnlyDictionary<string, ProjectItem> CentralVersionMap { get; }
public IReadOnlyDictionary<string, ProjectItem> CentralVersionMap { get; }

public IReadOnlyDictionary<PackageIdentity, string> PackageMitigations { get; }

public bool IsCentralVersionManagementEnabled => CentralVersionMap.Count > 0;

Expand Down Expand Up @@ -57,6 +62,23 @@ private static ReadOnlyDictionary<string, ProjectItem> GetCentralVersionMap(Proj
return new(versionMap);
}

private static ReadOnlyDictionary<PackageIdentity, string> GetPackageMitigations(Project project)
{
var projectItems = project
.GetItems("PackageMitigation");

var mitigations = projectItems
.Select(item => new
{
Identity = new PackageIdentity(item.EvaluatedInclude, NuGetVersion.Parse(item.GetMetadataValue("Version"))),
Justification = item.GetMetadataValue("Justification")
}
)
.ToDictionary(item => item.Identity, item => item.Justification);

return new(mitigations);
}

private ProjectInTargetFramework? GetBestMatch(IEnumerable<ProjectInTargetFramework> projects, string projectPath)
{
var candidates = projects
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net48;net8.0</TargetFrameworks>
<TargetFrameworks>net48</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ public static async Task<TransitiveDependencies> GetTransitiveDependencies(Proje
}
}

package.VulnerabilityMitigation = project.PackageMitigations.GetValueOrDefault(identity);

parentsByChild
.ForceValue(package, _ => new())
.Add(packageReferenceInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ private static IEnumerable<ProjectItemInTargetFramework> GetPackageReferenceItem

return version is null
? null
: new PackageReferenceEntry(id, version, versionSource, projectItemInTargetFramework, projectItem.GetMetadataValue("Justification"), projectItem.GetIsPrivateAsset());
: new PackageReferenceEntry(id, version, versionSource, projectItemInTargetFramework, versionSource.GetMetadataValue("Justification"), projectItem.GetIsPrivateAsset());
}

internal static bool IsTrue(this ProjectProperty? property)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@
<PackageReference Include="VSIX-SdkProjectAdapter" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<Content Include="..\LICENSE">
<Content Include="..\..\LICENSE">
<Link>Resources\LICENSE</Link>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="..\art\Screenshot.png">
<Content Include="..\..\art\Screenshot.png">
<Link>Resources\Screenshot.png</Link>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Content Include="..\art\NugetMonitor_90x90.png">
<Content Include="..\..\art\NugetMonitor_90x90.png">
<Link>Resources\NugetMonitor_90x90.png</Link>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static void ShowTransitivePackageIssues(ICollection<TransitiveDependencie

Log($"{transitivePackages.Length} transitive packages found");

var vulnerablePackages = transitivePackages.Where(item => item.IsVulnerable).ToArray();
var vulnerablePackages = transitivePackages.Where(item => item.IsVulnerable && item.VulnerabilityMitigation.IsNullOrEmpty()).ToArray();

if (vulnerablePackages.Length <= 0)
{
Expand Down Expand Up @@ -142,7 +142,7 @@ private static void PrintDependencyTree(IEnumerable<TransitiveDependencies> depe

var vulnerablePackages = packages
.Select(item => item.Key)
.Where(item => item.IsVulnerable)
.Where(item => item.IsVulnerable && item.VulnerabilityMitigation.IsNullOrEmpty())
.ToArray();

if (vulnerablePackages.Length == 0)
Expand Down Expand Up @@ -211,6 +211,6 @@ private static void PrintDependencyTree(StringBuilder text, PackageInfo package,

yield return topLevelPackages.CountedDescription("update", item => item.IsOutdated);
yield return topLevelPackages.CountedDescription("deprecation", item => item.IsDeprecated);
yield return topLevelPackages.CountedDescription("vulnerability", item => item.IsVulnerable);
yield return topLevelPackages.CountedDescription("vulnerability", item => item.IsVulnerable && item.VulnerabilityMitigation.IsNullOrEmpty());
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,24 @@
<ContextMenu x:Key="PackageContextMenu"
d:DataContext="{d:DesignInstance local:ChildNode}"
Style="{DynamicResource {x:Static styles:ResourceKeys.ContextMenuStyle}}">
<MenuItem Header="Copy package reference"
<MenuItem Header="Copy PackageReference"
Command="{Binding CopyPackageReferenceCommand}">
<MenuItem.Icon>
<imaging:CrispImage Width="16" Height="16" Moniker="{x:Static imageCatalog:KnownMonikers.Copy}" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy package version"
<MenuItem Header="Copy PackageVersion"
Command="{Binding CopyPackageVersionCommand}">
<MenuItem.Icon>
<imaging:CrispImage Width="16" Height="16" Moniker="{x:Static imageCatalog:KnownMonikers.Copy}" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy PackageMitigation"
Command="{Binding CopyPackageMitigationCommand}">
<MenuItem.Icon>
<imaging:CrispImage Width="16" Height="16" Moniker="{x:Static imageCatalog:KnownMonikers.Copy}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
<HierarchicalDataTemplate x:Key="NodeTemplate"
DataType="{x:Type local:ChildNode}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ namespace NuGetMonitor.View.DependencyTree;
internal enum PackageNode
{
PackageReference,
PackageVersion
PackageVersion,
PackageMitigation
}

internal sealed partial class ChildNode : INotifyPropertyChanged
Expand Down Expand Up @@ -57,16 +58,24 @@ public ChildNode(PackageInfo packageInfo, TransitiveDependencies transitiveDepen

public ICommand CopyPackageVersionCommand => new DelegateCommand(() => CopyNode(PackageNode.PackageVersion));

public ICommand CopyPackageMitigationCommand => new DelegateCommand(() => CopyNode(PackageNode.PackageMitigation));

private void CopyNode(PackageNode node)
{
var currentVersion = PackageIdentity.Version;

var latestVersion = _packageInfo.Package.Versions
.Where(v => v.IsPrerelease == currentVersion.IsPrerelease)
.DefaultIfEmpty(currentVersion)
.Max();
var version = node == PackageNode.PackageMitigation
// mitigation is always for the current version
? currentVersion
// for the other nodes we want the latest version of the same kind (pre-release or not)
: _packageInfo.Package.Versions
.Where(v => v.IsPrerelease == currentVersion.IsPrerelease)
.DefaultIfEmpty(currentVersion)
.Max();

var justification = node == PackageNode.PackageMitigation ? "Justification=\"TODO\" " : string.Empty;

Clipboard.SetText($"""<{node} Include="{PackageIdentity.Id}" Version="{latestVersion}" />""");
Clipboard.SetText($"""<{node} Include="{PackageIdentity.Id}" Version="{version}" {justification}/>""");

if (node == PackageNode.PackageReference)
{
Expand All @@ -92,7 +101,7 @@ private IEnumerable<string> GetIssueItems()
yield return "Outdated";

if (_packageInfo.IsVulnerable)
yield return "Vulnerable";
yield return _packageInfo.VulnerabilityMitigation.IsNullOrEmpty() ? "Vulnerable" : $"Vulnerable ({_packageInfo.VulnerabilityMitigation})";
}
}

Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private async void Load()
.SelectMany(item => item.Items)
.Select(item => item.ProjectItemInTargetFramework)
.Where(item => item.Project.IsTransitivePinningEnabled)
.SelectMany(project => project.Project.CentralVersionMap.Values.Select(item => new PackageReferenceEntry(item.EvaluatedInclude, item.GetVersion() ?? VersionRange.None, item, project, string.Empty, false)))
.SelectMany(project => project.Project.CentralVersionMap.Values.Select(item => new PackageReferenceEntry(item.EvaluatedInclude, item.GetVersion() ?? VersionRange.None, item, project, item.GetMetadataValue("Justification"), false)))
.Where(item => !packageIds.Contains(item.Identity.Id))
.GroupBy(item => item.Identity)
.Select(item => new PackageViewModel(item, PackageItemType.PackageVersion, _solutionService))
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit a830f9f

Please sign in to comment.