Identifying Vulnerable Nuget Packages - A Mystery (Solved)
I'm working on updating packages on multiple projects to eliminate vulnerable nuget packages. We have been doing this on a recurring basis for some time, but I recently got a script built to do better searches across transitive dependencies in all projects under a given path. And... it's exposed quite a few more packages that need to be updated. The basic command I'm using to identify vulnerable packages is dotnet list package --vulnerable --include-transitive
.
As part of remediation, it's often useful to know where the transitive package dependency came from - David Jarman had a post a while back where he talked about dotnet nuget why
, which is a pretty slick tool to see the dependency chain for a given package. It does occasionally have bugs (that I was unable to consistently reproduce 😕), but it's been very helpful so far in tracking down the source of vulnerable package versions.
Problem
However.
If you're doing this, you may run into the following situation:
- Project A depends on Project B.
- Project B reports no vulnerable packages or vulnerable transitive packages.
- Project A reports vulnerable transitive packages (in my case,
System.Formats.Asn1 v5.0.0
). - When you run
dotnet nuget why
on Project A, you find that the transitive dependency runs through Project B which just said that it didn't have any vulnerable transitive packages.
There are some hints online about why this might be the case, but I haven't found anything concrete. Google's AI summary seems pretty confident that it depends on the SDK, but it's slightly incoherent, and when I check the cited sources, they don't mention the SDK type (that I can see). So I'm uninclined to belive that it's fully correct.
In my case, Project A is a <Project Sdk="Microsoft.NET.Sdk.Web">
project, while Project B is <Project Sdk="Microsoft.NET.Sdk">
, but that's really the only difference. I played with swapping those values around - no difference in the vulnerable package lists.
So, for now - this remains a mystery as to 'Why'. But it's real darn frustrating.
Solved!
Ok, I figured this thing out.
What was happening is that Project B has a transitive dependency on System.Formats.Asn1
, but allows a higher version (ie, does not require v5.0.0 exactly). However, additional packages that Project A depends on have a dependncy on the explicit version of the vulnerable package - v5.0.0. So, when you run the package vulnerability checker on Project B, there is no vulnerable package - it does not depend on a vulnerable verison, it's presumably resolving a higher version.
But when you run against Project A, the resolved package version is v5.0.0, which is vulnerable. This then reports a vulnerable package, chained under Project B and version 5. It is important to note that initially I did not see the other vulnerability chains under Project A, I focused on the path through Project B.
The fix is to find the package references from Project A that have a transitive dependency on Systsem.Formats.Asn1
, and update those - this allows us to resolve the vulnerable dependency.
I found this pattern repeated several times for different vulnerable packages with some variation during this process, including problems when the target framework changed from Any CPU
locally to win-x64
in the CI build. It does make sense, but it's a very frustrating thing to deal with. I now appreciate more NPM's use of a package-lock.json
file to fix the resolved version - it makes tracking down problems like this somewhat easier.
Note:
This was initially written on 4/29, but didn't have time to finish it up, so it's getting published 5/4/25.