Dependency Security

.NET NuGet Package Security

Securing your .NET supply chain with NuGet package signing, lock files, and vulnerability scanning.

Alex
Security Consultant
4 min read

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:

  1. Author signing. The package author signs the package with their certificate. This proves the package came from the expected author.
  2. 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:

  1. 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>
  1. 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.

  2. Disable NuGet.org for internal packages. Configure your nuget.config so 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.