837 words
4 minutes
NuGet Audit

Refs:
https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages

NuGet Audit#

Supply chain attacks are a growing concern in software development, and one way to mitigate this risk is leveraging tools like NuGet Audit.

By enabling this feature in your solution/project, you can be warned about, or even fail builds with known vulnerable dependencies.

Creating a new project with a vulnerable dependency#

To demonstrate how NuGet Audit works, let’s create a new .NET project and add a vulnerable dependency.

We start by creating a new console application:

dotnet new console -n test-project
cd test-project

Let’s do a dotnet restore to see that everything looks fine:

dotnet restore result initial

Now that we have a project, let’s add a vulnerable package:

dotnet add package Newtonsoft.Json --version 12.0.3

If we run dotnet restore again, we can see that we get a warning about the vulnerable package:

dotnet restore result with vulnerable package

Understanding NuGet Audit#

Right, so we got a warning about the vulnerable Newtonsoft.Json package, so what’s the deal with NuGet Audit anyway?

With .NET 8.0.100 (NuGet version 6.8), NuGet Audit was introduced and enabled by default.
For instance, if we change the project SDK to 7.0.x and project to target .NET 7.0:

dotnet new globaljson --sdk-version 7.0.410
<!-- test-project.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <RootNamespace>test_project</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
  </ItemGroup>

</Project>

Running dotnet restore now will not give us any warnings anymore:

dotnet restore result for .net7

So, let’s delete the global.json file and set the target framework back to .NET 10.0, but additionally add <NuGetAudit>false</NuGetAudit> to the project file to disable NuGet Audit:

<!-- test-project.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <NuGetAudit>false</NuGetAudit>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <RootNamespace>test_project</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
  </ItemGroup>

</Project>

Running dotnet restore will not give us any warnings anymore even with the vulnerable package:

dotnet restore result for .net10 with suppressed warnings

Configuring NuGet Audit Level#

Now that we’ve seen that NuGet Audit is something we can turn on and off, let’s have a look at what else we can configure.
We can start by changing the severity level by adding the NuGetAuditLevel property to the project file and setting this to Critical:

<!-- test-project.csproj -->
  <PropertyGroup>
    <NuGetAudit>true</NuGetAudit>
    <NuGetAuditLevel>critical</NuGetAuditLevel>

As we saw earlier, the Newtonsoft.Json package has a High severity vulnerability, so setting the level to Critical will suppress the warning:

dotnet restore result for .net10 with suppressed warnings

The options for NuGetAuditLevel are: low, moderate, high, and critical.

Let’s set it to low to ensure we get all warnings:

<!-- test-project.csproj -->
  <PropertyGroup>
    <NuGetAudit>true</NuGetAudit>
    <NuGetAuditLevel>low</NuGetAuditLevel>

Configuring NuGet Audit Mode#

Another configuration option is to change the <NuGetAuditMode>. This will allow us to define how deep we want to scan (direct or including transitive dependencies).
The options are direct and all. Let’s set the mode to direct:

<!-- test-project.csproj -->
  <PropertyGroup>
    <NuGetAudit>true</NuGetAudit>
    <NuGetAuditLevel>low</NuGetAuditLevel>
    <NuGetAuditMode>direct</NuGetAuditMode>

Let’s remove the Newtonsoft.Json package and add a package with a vulnerable transitive dependency:

dotnet remove package Newtonsoft.Json
dotnet add package Swashbuckle.AspNetCore.Swagger --version 1.0.0

Running dotnet restore and all is looking well and good:

dotnet restore result for .net10 with suppressed warnings

But if we change the mode to all:

<!-- test-project.csproj -->
  <PropertyGroup>
    <NuGetAudit>true</NuGetAudit>
    <NuGetAuditLevel>low</NuGetAuditLevel>
    <NuGetAuditMode>all</NuGetAuditMode>

We will now get a lot of warnings:

dotnet restore result with transative vulnerabilities

Adding visibility to the build pipeline#

A nice way to get visibility into the vulnerable dependencies in your project or solution, is to output the audit results as part of the build process.

For Azure DevOps pipelines, you can add the following task to your pipeline:

- script: dotnet restore ./YourSolutionFile.sln
  displayName: 'dotnet restore'

Failing the build when vulnerabilities are found#

To ensure your pipeline does not publish vulnerable code, you can configure the build to fail when vulnerabilities are found.
We can do that by treating WarningsAsErrors, but to ensure this is done for all the projects in a solution, you can add a Directory.Build.props file to the solution folder with the following content:

<Project>
  <PropertyGroup>
    <!-- Enable NuGet package auditing -->
    <NuGetAudit>true</NuGetAudit>
    <!-- Audit direct and transitive packages -->
    <NuGetAuditMode>all</NuGetAuditMode>
    <!-- Report low, moderate, high and critical advisories -->
    <NuGetAuditLevel>low</NuGetAuditLevel>
    <!-- Fails the build on CI or on release when a vulnerability is detected -->
    <WarningsAsErrors Condition="$(ContinuousIntegrationBuild) == 'true' OR '$(Configuration)' == 'Release'">
      $(WarningsAsErrors);NU1900;NU1901;NU1902;NU1903;NU1904
    </WarningsAsErrors>
  </PropertyGroup>
</Project>

The warning codes shown above are as follows (+ NU1905 to cover them all):

Warning CodeReason
NU1900Error communicating with package source, while getting vulnerability information
NU1901Package with low severity detected
NU1902Package with moderate severity detected
NU1903Package with high severity detected
NU1904Package with critical severity detected
NU1905An audit source does not provide a vulnerability database.

Finding the root cause of a transitive vulnerability#

As we saw earlier, when calling dotnet restore, we get a list of vulnerable packages, but it does not tell us which package is bringing in the vulnerable dependency.

For instance we can see that we have the vulnerable Microsoft.AspNetCore.Http package in our project:

Restore succeeded with 15 warning(s) in 0.4s
    C:\git\test-project\test-project.csproj : warning NU1903: Package 'Microsoft.AspNetCore.Http' 1.0.0 has a known high severity vulnerability, https://github.com/advisories/GHSA-hxrm-9w7p-39cc

To find out which package is bringing in the vulnerable dependency, we can use the dotnet nuget why command.

Let’s run it and specify the relevant project and package name dotnet nuget why .\test-project.csproj Microsoft.AspNetCore.Http

dotnet nuget why result

At the top of the tree we can see that Swashbuckle.AspNetCore.Swagger is bringing in Microsoft.AspNetCore.Http as a dependency via Microsoft.AspNetCore.Mvc.Core (twice), and thus is the root cause of the vulnerability.

NuGet Audit
https://cognitiveoverload.blog/posts/dotnet/nuget-audit/
Author
cognitive;overload
Published at
2026-02-04
License
CC BY