Safeguard
Security Guides

NuGet Supply Chain Security: Protecting Your .NET Dependencies

How NuGet supply chain attacks work, from dependency confusion to typosquatting, and the concrete controls, lock files, source mapping, and signing, that lock down your .NET build.

Marcus Chen
AppSec Engineer
5 min read

Your .NET application's attack surface is not just the code you write — it's every NuGet package you pull, every package those packages pull, and the feeds and build machines in between. The SolarWinds SUNBURST attack of 2020 was the wake-up call for the .NET ecosystem: attackers didn't exploit a CVE, they compromised the build pipeline of a trusted vendor and shipped signed malware to thousands of organizations. NuGet supply-chain security is about controlling what enters your build and proving it's what you expected. This post covers the specific attack classes and the specific configuration that defends against each.

How do NuGet supply chain attacks actually happen?

They happen at four points: the package, the name, the feed, and the build. Understanding the mechanism tells you which control to reach for.

  • Malicious or backdoored packages — a package that does something harmful, either from the start (typosquatting) or after a maintainer takeover or a compromised publish.
  • Dependency confusion — your build resolves an internal package name from the public nuget.org feed because an attacker registered that name there with a higher version number. Alex Birsan demonstrated this class of attack in 2021 across multiple ecosystems, and package managers that check multiple feeds without name pinning are exposed.
  • Typosquatting — a package named Newtonsoft.Jsoon or Microsoft.AspNetcore that a developer installs by mistake.
  • Build-time execution — MSBuild targets and package install scripts can run code during restore/build, so a malicious dependency doesn't need to wait until runtime.

Defense 1: Pin what you resolve with package source mapping

Dependency confusion is defeated by telling NuGet exactly which feed each package pattern comes from. Package source mapping, available since NuGet 6.0 (shipped with .NET 6), does this in nuget.config:

<packageSourceMapping>
  <packageSource key="nuget.org">
    <package pattern="*" />
  </packageSource>
  <packageSource key="internal-feed">
    <package pattern="Contoso.*" />
  </packageSource>
</packageSourceMapping>

Now Contoso.Billing can only ever come from your internal feed, no matter what a same-named package on nuget.org claims. This one file is the single highest-value control against dependency confusion.

Defense 2: Make restores reproducible with lock files

A lock file records the exact resolved version and content hash of every package, direct and transitive, so a compromised mirror or a floating version can't silently swap something in. Enable it per project:

<PropertyGroup>
  <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>

Commit the generated packages.lock.json, then enforce it in CI so any drift fails the build:

dotnet restore --locked-mode

With --locked-mode, restore refuses to run if the lock file and the project's declared dependencies disagree — turning a silent substitution into a loud failure.

Defense 3: Audit and verify what you already depend on

Since .NET 8, dotnet restore runs NuGet Audit against the GitHub Advisory Database and warns on known-vulnerable packages; .NET 9 extended this to transitive dependencies by default. Elevate the warnings and run an explicit listing in CI:

dotnet list package --vulnerable --include-transitive
dotnet list package --deprecated

Require package signature verification so an unsigned or tampered artifact is rejected, and prefer feeds that enforce author or repository signing. Continuous software composition analysis for your NuGet tree fills the gap between advisory databases — which lag — and the packages you actually ship, and gives you reachability context so you know which advisories touch your code.

Defense 4: Know exactly what you shipped with an SBOM

You cannot respond to "are we affected by the new X vulnerability?" without an accurate inventory. Generate a CycloneDX or SPDX software bill of materials on every build and store it as a build artifact. When the next widely-exploited package issue lands, an SBOM turns an all-hands repo grep into a database query. SBOM generation and management tooling automates producing, storing, and diffing these across releases so you can also see when a transitive dependency changed and why.

A NuGet hardening checklist

ControlMechanismDefends against
Source mappingpackageSourceMapping in nuget.configDependency confusion
Lock filesRestorePackagesWithLockFile + --locked-modeSilent substitution, floating versions
Signature verificationSigned-package enforcement on the feedTampered/unsigned artifacts
NuGet auditNuGetAudit + warnings-as-errorsKnown-vulnerable packages
Private feed hygiene2FA on publishing accounts, scoped API keysAccount/publish compromise
SBOM per buildCycloneDX/SPDX generationSlow incident response
  • packageSourceMapping pins internal patterns to the private feed
  • Lock files committed; CI runs --locked-mode
  • dotnet list package --vulnerable gate in the pipeline
  • Signature verification and scoped, rotated feed keys
  • SBOM produced and retained on every build

How Safeguard Helps

Safeguard watches the NuGet layer continuously so the controls above stay enforced instead of documented. It ingests your dependency graph, monitors it against fresh advisory feeds, and uses reachability analysis to separate the flagged CVEs that touch executed code from the ones buried in a package method you never call. When a dependency-confusion risk or a newly disclosed malicious package appears, you're alerted with the affected projects already identified. You can run it from the Safeguard CLI directly in CI to fail builds on policy violations, and generate or ingest SBOMs automatically so incident response is a lookup, not a scramble. Teams consolidating supply-chain tooling frequently compare it on the Safeguard vs Snyk and Safeguard vs Black Duck pages.

Create a free project at app.safeguard.sh/register and read the NuGet integration guide at docs.safeguard.sh.

Never miss an update

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