Software Composition Analysis (SCA)

Rule ND3120 Security

Raises an issue per consumed NuGet package version with known security advisories listed on GitHub: https://github.com/advisories?query=+ecosystem%3Anuget The issue is reported on the first type (alphabetically) using a third-party assembly from the affected package.

Flagged Code
<!-- MyApp.csproj: Newtonsoft.Json 12.0.1 has the advisory GHSA-5crp-9r3c-p9vr -->
<ItemGroup>
   <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
</ItemGroup>
Fixed Code
<!-- 13.0.1 is the patched version that resolves all known advisories of this package -->
<ItemGroup>
   <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

How to fix

Update the affected package to at least the patched version recommended, which resolves all known advisories for the package. It is not always the latest version (e.g. an issue in 4.0.2 might be fixed in 4.1.0 while 7.0.8 exists).

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Software Composition Analysis (SCA)</Name>
// <Id>ND3120:SoftwareCompositionAnalysis</Id>

warnif count > 0

// Use this hashset to avoid to have several SAC issues on a type
let hashset = new HashSet<IType>()

// Gather third-party assemblies with one or several GitHub Security Advisories
let thirdPartyAsmWithAdvisories = from asm in ThirdParty.Assemblies

  // Assemblies from  Microsoft.NetCore.App.Runtime pkg reference version 
  // are always X.0.0.0 (like 8.0.0.0 or 9.0.0.0)
  // Thus this rule cannot detect if patched version is referenced.
  where !asm.Name.StartsWithAny("System.","Microsoft.") && 

        // If the asm is not resolved at analysis time, the file-version 
        // remains unknow and this can lead to false positive
        !asm.IsThirdPartyInferredFromRef

  let advisories = asm.GetSecurityAdvisories()
  where advisories.Count > 0
  select new { 
    asm, 
    advisories,
    PackageName = (string)advisories.First().PackageName
  } 

// Merge third-party assemblies within the same NuGet Package
from x in thirdPartyAsmWithAdvisories.ToLookup(a1 => a1.PackageName)

// Gather data useful to describe the advisories and how to fix
let HighestSeverity = x.SelectMany(r => r.advisories).Max(ad => ad.Severity)
let AdvisoryWithHighestSeverity = x.SelectMany(r => r.advisories).First(ad => ad.Severity == HighestSeverity)
let ThirdPartyAssembliesImpacted = x.Select(x1 => x1.asm).ToArray()
// Application AND test assemblies: a vulnerable package referenced only by tests is still a vulnerability
let AppAssembliesUser = ApplicationAndTestCode.Assemblies.UsingAny(ThirdPartyAssembliesImpacted)
// Discard when consummers are only .NET Fx v4.x, this provoke some false positive with .NET Core version range matching
where AppAssembliesUser.Any(a1 => !a1.TargetFrameworkName.StartsWith("net4"))

// The issue appears on the first type using one culprit third-party assembly 
let typeUsers = ApplicationAndTestCode.Types.UsingAny(ThirdPartyAssembliesImpacted)
    .Where(t => !t.IsGeneratedByCompiler || t.Name.StartsWith("Program+")).ToArray()
let FirstAppTypeUser = (typeUsers.Length == 0 ? ApplicationAndTestCode.Types : typeUsers ) // Make sure at least one type else we get an exception!
    .Aggregate((minElement, nextElement) => 
        string.Compare(nextElement.Name, minElement.Name, StringComparison.Ordinal) < 0 && !hashset.Contains(nextElement)
        ? nextElement 
        : minElement)
let fillHashset = hashset.Add(FirstAppTypeUser)

select new { 
   FirstAppTypeUser,
   PackageName = x.Key,
   ThirdPartyAssembliesImpacted,
   AppAssembliesUser,
   PackageVersion = x.First().asm.Version,
   PatchAllAdvisoriesVersion = x.First().advisories.Max(ad => ad.PatchedVersion),
   HighestSeverity,
   AdvisoryWithHighestSeverity = AdvisoryWithHighestSeverity.Url,
   AdvisorySummary = AdvisoryWithHighestSeverity.Summary,
   NbAdvisory = x.First().advisories.Count,
   AdvisoriesUrl = x.First().advisories.Select(ad=> ad.Url).Except(AdvisoryWithHighestSeverity.Url).Aggregate(" ; "),
   Debt = 20.ToMinutes().ToDebt()
}

//<Expl>
// The application uses the NuGet Package **{1} version {4}** which is affected by {9} Github security advisor{9?y:ies}.
//
// **Recommended Action:** Update the package to version *{5}* or higher that has no known security advisory.
//
// Advisory with highest severity *{6}* is {7} : **{8}**. {9?:Other advisory url: }{10}
//
// The *{0}* is the first type (alphabetically) in the application that uses this package.
//</Expl>

//<Desc>
// Raises an issue per consumed NuGet package version with known security
// advisories listed on GitHub:
// https://github.com/advisories?query=+ecosystem%3Anuget
// The issue is reported on the first type (alphabetically) using a
// third-party assembly from the affected package.
//
// **Flagged Code**
// ```xml
// <!-- MyApp.csproj: Newtonsoft.Json 12.0.1 has the advisory GHSA-5crp-9r3c-p9vr -->
// <ItemGroup>
//    <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
// </ItemGroup>
// ```
//
// **Fixed Code**
// ```xml
// <!-- 13.0.1 is the patched version that resolves all known advisories of this package -->
// <ItemGroup>
//    <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
// </ItemGroup>
// ```
//</Desc>

//<HowToFix>
// Update the affected package to at least the patched version recommended,
// which resolves all known advisories for the package. It is not always the
// latest version (e.g. an issue in 4.0.2 might be fixed in 4.1.0 while 7.0.8
// exists).
//</HowToFix>

Security: all 39 items