The .NET ecosystem relies on NuGet for package management, and NuGet.org hosts over 350,000 unique packages. While Microsoft has added significant security features in recent years, many .NET developers are not using them. This guide covers the practical steps to secure your NuGet dependency chain.
NuGet Security Features You Should Be Using
Package Signing
NuGet supports two types of package signing:
- Author signing. The package author signs the package with their certificate. This proves the package came from the expected author.
- Repository signing. NuGet.org signs every package it hosts. This proves the package was downloaded from NuGet.org and was not tampered with in transit.
Enable signature validation in your nuget.config:
<configuration>
<config>
<add key="signatureValidationMode" value="require" />
</config>
<trustedSigners>
<repository name="nuget.org" serviceIndex="https://api.nuget.org/v3/index.json">
<certificate fingerprint="0E5F38F57DC1BCC806D8494F4F90FBCEDD988B46760709CBEEC6F4219AA6157D"
hashAlgorithm="SHA256"
allowUntrustedRoot="false" />
</repository>
</trustedSigners>
</configuration>
NuGet Lock Files
.NET 5+ supports lock files for NuGet packages. Enable them in your project file:
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
Run dotnet restore to generate packages.lock.json. This file records exact versions and content hashes for every package.
In CI, use locked mode to fail if the lock file is out of sync:
dotnet restore --locked-mode
Commit your lock file. It is a security artifact.
Central Package Management
.NET 8 introduced Central Package Management (CPM) through Directory.Packages.props:
<!-- Directory.Packages.props -->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
</ItemGroup>
</Project>
Individual project files then reference packages without specifying versions:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>
CPM ensures consistent versions across all projects in a solution and makes auditing simpler.
Vulnerability Scanning
dotnet list package --vulnerable
The .NET CLI includes built-in vulnerability scanning:
dotnet list package --vulnerable --include-transitive
This checks packages against the GitHub Advisory Database. Add it to your CI pipeline:
dotnet list package --vulnerable --include-transitive --format json | jq '.projects[].frameworks[].topLevelPackages[] | select(.vulnerabilities != null)'
NuGet Audit
Starting with .NET 8, NuGet Audit runs automatically during restore and warns about vulnerable packages:
<PropertyGroup>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>moderate</NuGetAuditLevel>
<NuGetAuditMode>all</NuGetAuditMode>
</PropertyGroup>
Set NuGetAuditMode to all to check transitive dependencies as well, not just direct ones.
Dependency Confusion in .NET
If you use private NuGet feeds alongside NuGet.org, you are vulnerable to dependency confusion. An attacker can publish a package on NuGet.org with the same name as your internal package but with a higher version number.
Defenses:
- Use package source mapping. Available in NuGet 6.0+, this maps packages to specific sources:
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="company-feed">
<package pattern="YourCompany.*" />
</packageSource>
</packageSourceMapping>
-
Use package ID prefix reservation. NuGet.org allows verified organizations to reserve package name prefixes. If you own
YourCompany.*, no one else can publish packages with that prefix. -
Disable NuGet.org for internal packages. Configure your
nuget.configso that internal package patterns only resolve from your private feed.
Controlling Package Installation
NuGet packages can include MSBuild targets and props files that execute during build. While less dangerous than npm postinstall scripts, they can still modify your build process.
Review .targets and .props files in new NuGet packages:
# Extract and inspect a NuGet package
unzip package.1.0.0.nupkg -d package-contents
ls package-contents/build/
Look for PowerShell scripts (.ps1), MSBuild targets that execute custom tools, or references to external URLs.
SBOM Generation
Generate a CycloneDX SBOM for your .NET project:
dotnet tool install --global CycloneDX
dotnet CycloneDX solution.sln -o sbom -j
This produces a JSON SBOM with your complete dependency tree.
Keeping Dependencies Updated
Use Dependabot or Renovate with .NET support. Configure Renovate to handle the Directory.Packages.props file:
{
"nuget": {
"enabled": true,
"fileMatch": ["Directory\\.Packages\\.props$"]
}
}
Also review the .NET End of Life schedule. If you are running on a .NET version that is out of support, you are missing security patches for the runtime itself.
How Safeguard.sh Helps
Safeguard.sh integrates with your .NET CI/CD pipeline to provide continuous NuGet dependency monitoring. It ingests CycloneDX SBOMs from your builds, maps NuGet package vulnerabilities across your entire organization, and alerts you when new CVEs affect your deployed packages. For .NET teams managing dozens of services and solutions, Safeguard.sh provides the consolidated view that dotnet list package --vulnerable cannot, showing you exactly which applications are at risk and helping you prioritize updates based on actual deployment exposure.