.NET — NuGet
NuGet (packages.lock.json, Directory.Packages.props)
<!-- Directory.Packages.props — Central Package Management -->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
<!-- *.csproj -->
<PackageReference Include="Newtonsoft.Json" />
dotnet restore --locked-mode
Central Package Management (CPM) lets a single Directory.Packages.props pin every transitive across a solution; a <PackageVersion> there coerces a transitive even if no project declares it directly. <RestoreLockedMode>true</RestoreLockedMode> in a .csproj mandates --locked-mode behaviour.
Gotcha: pre-CPM solutions have versions scattered across each .csproj — migrating is a one-time effort but it dramatically simplifies upgrades.
Developer gotchas — written for people who live in the code
packages.lock.jsonis opt-in. New projects don’t get one unless you set<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>. Without a lockfile,dotnet restorere-resolves each build and the CVE you triaged yesterday may regress on a transitive bump tomorrow.bin/Debug/andbin/Release/ship different artefacts. Conditional compilation symbols (#if DEBUG) can compile in/out vulnerable code paths. Reachability of a CVE flagged on a dep depends on which configuration the deployed binary was built from. Checkcsc.exeinvocation flags ordotnet publish -c Release.TargetFrameworkmatters.net6.0vsnet8.0vsnetstandard2.0resolve different versions of the same package. A CVE inSystem.Text.Jsonfornet6.0may not exist in thenet8.0build of the same package.dotnet list package --framework net8.0filters by target.PrivateAssets="all"hides a transitive from downstream. Used on analyzers, source generators, build-time tools. CVE on aPrivateAssets="all"dep doesn’t propagate to your package’s consumers — but it still affects your build.global.jsonpins the SDK, not packages. A CVE in an SDK component (dotnet-tools) needsdotnet --infoto identify, not package scans.- AssemblyLoadContext isolation in plugins. Apps using
AssemblyLoadContext(most plugin systems) can have two versions of the same DLL loaded simultaneously. CVE scanners may flag both; only the actually-invoked one is reachable. - Single-file publish (
dotnet publish -p:PublishSingleFile=true) embeds deps. The resulting binary contains compressed assemblies. Container scanners may not see them as separate packages. Usedotnet list packageagainst the source project for an accurate inventory. <PackageReference>with noVersionattribute relies on CPM. If CPM isn’t set up, the package fails to restore. If CPM is set up but theDirectory.Packages.propsis missing the entry, you get the floor version (often a very old one). CVE flags may be against unexpectedly old versions.
Reachability
dotnet list package --include-transitiveenumerates the graph.- Roslyn analyzers (
Microsoft.CodeAnalysis) can query for method calls;dotnet build /p:RunAnalyzers=true. - Runtime: dotCover or
coverletintegrated withdotnet test.