Software Supply Chain Security

NuGet Package Tampering Detection: Securing the .NET Supply Chain

NuGet packages can be tampered with at multiple points in the supply chain. Here is how to detect and prevent package tampering in your .NET projects.

James
Security Architect
5 min read

The .NET ecosystem relies on NuGet for package management, serving billions of package downloads annually. While NuGet has made progress on security features like package signing and reserved prefixes, the reality is that most .NET projects do not take full advantage of these protections. Package tampering remains a real threat.

Tampering can happen at the registry level, during transit, in local caches, or through compromised build pipelines. A tampered package looks identical to the legitimate one from the outside but contains modified code -- anything from a subtle backdoor to full credential-harvesting malware.

Where Tampering Can Occur

At the source. If an attacker compromises a package author's NuGet account, they can push a new version with malicious code. NuGet.org supports two-factor authentication, but adoption is not universal. The 2022 compromise of several npm maintainer accounts showed that this attack is practical at scale.

During transit. If NuGet packages are fetched over unencrypted connections, a man-in-the-middle attacker can substitute packages. While NuGet.org uses HTTPS, internal NuGet feeds may not.

In the cache. NuGet caches packages in the global packages folder (%userprofile%\.nuget\packages on Windows). If an attacker has access to a developer's machine or a shared build agent, they can modify cached packages. Subsequent builds will use the tampered cached version without re-downloading.

Through feed manipulation. Organizations using private NuGet feeds may have weaker access controls. An attacker with write access to a private feed can replace packages or publish new versions with malicious content.

Via dependency confusion. If an internal package name matches a public NuGet.org package, NuGet may resolve the public package depending on feed configuration. This is the classic dependency confusion attack adapted for the .NET ecosystem.

NuGet Package Signing

NuGet supports two types of signatures: author signatures and repository signatures.

Author signatures are applied by the package author using a code-signing certificate. They prove that the package came from a specific author and has not been modified since signing. Author signatures persist across repositories.

Repository signatures are applied by the repository (NuGet.org) when a package is published. They prove that the package was served by a specific repository. NuGet.org signs all packages with a repository signature.

To require signed packages, add this to your nuget.config:

<config>
  <add key="signatureValidationMode" value="require" />
</config>

The practical challenge is that many legitimate packages are not author-signed. Setting signatureValidationMode to require will break builds that depend on unsigned packages. The default mode is accept, which validates signatures when present but does not require them.

Lock Files in NuGet

NuGet supports lock files (packages.lock.json) starting with SDK-style projects. When enabled, the lock file records the exact version and content hash of every resolved package. Subsequent restores verify that the resolved packages match the lock file.

Enable lock files by adding this to your project file:

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

In CI/CD, use the --locked-mode flag to fail the build if the lock file does not match:

dotnet restore --locked-mode

This prevents silent package substitution in CI/CD environments.

Reserved Package Prefixes

NuGet.org allows package authors to reserve ID prefixes. For example, Microsoft has reserved the Microsoft.* and System.* prefixes. Packages with reserved prefixes show a checkmark icon on NuGet.org, and only authorized owners can publish new packages under reserved prefixes.

This prevents attackers from publishing packages like Microsoft.Security.Utils that could be confused with official Microsoft packages. However, prefix reservation is voluntary and only applies to NuGet.org.

Detecting Tampering

Compare package hashes. Every NuGet package has a SHA-512 hash recorded in the lock file and in the NuGet repository. Compare the hash of your local package against the repository hash. Any mismatch indicates tampering.

Monitor package metadata. Track changes in package ownership, author information, repository URLs, and license types. Sudden changes in metadata for established packages are suspicious.

Audit the global packages folder. In shared build environments, periodically verify that cached packages match their expected hashes. Automated tooling can do this as part of build pipeline health checks.

Verify certificate chains. For signed packages, verify that the signing certificate chain is valid and traces to a trusted root. Expired or revoked certificates indicate a problem.

Check for unauthorized package sources. Review your nuget.config files across all projects to ensure only authorized package sources are configured. A malicious source added to a single project can affect builds.

Organizational Controls

Centralize NuGet feed management. Use Azure Artifacts, ProGet, or Artifactory as a central NuGet feed that proxies NuGet.org. This gives you control over which packages are available and creates an audit trail for all package downloads.

Implement package approval workflows. Require security review before new packages or major version updates are approved for use. This is more practical than reviewing every package version.

Enable package vulnerability scanning. NuGet.org reports known vulnerabilities in packages. The dotnet list package --vulnerable command shows vulnerable packages in your projects. Integrate this into your CI/CD pipeline.

Enforce lock files across all projects. Make lock files mandatory for all projects in your organization. Require --locked-mode in CI/CD to catch any discrepancies.

How Safeguard.sh Helps

Safeguard.sh provides continuous monitoring of your NuGet dependency chain, detecting package tampering through hash verification, signature validation, and metadata tracking. The platform flags unexpected package source changes, monitors for dependency confusion attacks targeting your internal packages, and generates comprehensive SBOMs that include verification status for every .NET component in your builds.

Never miss an update

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