Modern .NET applications rarely depend only on code written inside the current repository.
A typical application may pull dozens or hundreds of NuGet packages, including direct and transitive dependencies. That makes package management an important part of the software supply chain.
The basic workflow looks simple:
Developer
|
v
dotnet restore
|
v
NuGet packages
|
v
Build
|
v
Application
The difficult question is what happens if a package is modified, replaced, or published by an unexpected source.
Package signing provides an additional trust signal. Instead of trusting only the package name and version, consumers can verify that the package carries a valid signature and that the package contents have not been altered after signing.
This does not solve every software supply-chain problem, but it can make unauthorized package modification significantly harder to hide.
In this article, we will look at how NuGet package signing works, how to verify signed packages in .NET environments, how to build CI checks around package signatures, and where package signing fits into a broader dependency-security strategy.
Why Package Integrity Matters
Consider a dependency:
Contoso.Logging
Version: 5.2.0
Your project expects:
Contoso.Logging 5.2.0
The package manager can retrieve that package from a configured source.
But package identity and package integrity are different concepts.
You need confidence that:
Package identity
+
Package version
+
Package contents
represent the artifact you intended to consume.
A compromised package repository, publishing credential, build system, or distribution path can create supply-chain risk.
Package signing adds another verification layer:
Package
|
v
Signature
|
v
Signer identity
|
v
Integrity verification
What NuGet Package Signing Does
NuGet supports package signing using certificates.
At a high level:
Package contents
|
v
Cryptographic signing
|
v
Signed package
A consumer can then verify the package signature.
Conceptually:
Signed package
|
+---- Certificate
|
+---- Package data
|
v
Signature verification
If package contents are changed after signing, signature validation can detect the modification.
This provides an important property:
Signed artifact
+
Changed contents
=
Verification failure
Package Signing Is Not the Same as Hash Verification
Hash verification and digital signatures solve related but different problems.
A hash can establish that two files have the same content:
Package
|
v
SHA-256
|
v
Hash
If you already know the expected trusted hash, you can detect changes.
But the question becomes:
Who generated the trusted hash?
A digital signature adds signer identity and cryptographic proof.
Conceptually:
Hash
+
Private key
=
Digital signature
The corresponding public certificate can then be used during verification.
Understand the Trust Model
Package signing does not mean:
Signed = Automatically Safe
That would be an incorrect security assumption.
A malicious package can theoretically be signed if an attacker obtains control of a trusted publisher's signing credentials.
Therefore package signing should be viewed as one layer:
Trusted package source
+
Dependency review
+
Version controls
+
Package signing
+
Vulnerability scanning
+
Reproducible builds
+
CI policy
Each layer addresses a different part of the supply-chain problem.
Create a Signed NuGet Package
A package author can create a package using the standard .NET packaging workflow.
For example:
dotnet pack -c Release
This produces a .nupkg file.
Package signing can then be performed using NuGet tooling and a suitable signing certificate.
A conceptual command looks like:
nuget sign MyLibrary.1.0.0.nupkg \
-CertificatePath signing-cert.pfx
The exact certificate configuration depends on the environment and signing infrastructure.
For production publishing, avoid storing long-lived private signing keys directly in source repositories or CI configuration files.
Protect the Signing Certificate
The signing certificate's private key is highly sensitive.
If an attacker obtains it, they may be able to produce packages that appear to come from the legitimate publisher.
Therefore:
Private signing key
|
v
Protected signing environment
|
v
Controlled package signing
Do not use this pattern:
Git repository
|
+-- signing-cert.pfx
+-- password
That creates an unnecessary high-impact secret exposure.
Instead, use a controlled certificate store, managed key system, hardware-backed protection, or a dedicated signing service where appropriate.
The exact implementation depends on organizational requirements.
Verify Package Signatures
NuGet provides tooling for package verification.
A basic workflow is:
Restore
|
v
Package downloaded
|
v
Signature validation
|
+---- Valid
|
+---- Invalid
For security-sensitive environments, signature verification should become an explicit policy rather than an assumption.
You can also inspect package metadata and signatures during package analysis.
The goal is to answer:
Is this package signed?
Is the signature valid?
Who signed it?
Is the signer trusted?
These are separate questions.
Signed Does Not Automatically Mean Trusted
Suppose a package has a valid signature.
That establishes something about the package's cryptographic authenticity.
It does not automatically mean your organization should use it.
For example:
Package A
Signature: Valid
Publisher: Unknown
versus:
Package B
Signature: Valid
Publisher: Approved
The second package may satisfy your organization's policy while the first does not.
Therefore an enterprise package policy can distinguish:
Unsigned
Signed by unknown publisher
Signed by known publisher
Signed by explicitly trusted publisher
Configure Trusted Signers
NuGet supports trusted signer configuration.
A policy can specify which authors or repositories are trusted for package signatures.
Conceptually:
Package
|
v
Signature valid?
|
+-- No --> Reject
|
+-- Yes
|
v
Trusted signer?
|
+-- No --> Reject / Warn
|
+-- Yes
|
v
Allow
This is much stronger than simply checking whether any signature exists.
A centralized policy can also make package-consumption rules consistent across developer machines and CI agents.
Add Package Verification to CI
Security controls become more effective when they are enforced automatically.
A typical pipeline can look like:
Pull Request
|
v
Restore dependencies
|
v
Validate package policy
|
v
Vulnerability scan
|
v
Build
|
v
Test
If a package violates the organization's signing policy:
Policy violation
|
v
Build fails
This prevents a developer from accidentally introducing an untrusted dependency into production.
Do Not Fail the Build Without a Rollout Strategy
A strict package-signing policy can break existing repositories.
For example:
Repository
|
+-- 120 packages
|
+-- 15 unsigned
Turning on:
Require signed packages
overnight may produce a large number of failures.
A safer rollout is:
Phase 1: Inventory
Determine:
Which packages are signed?
Who signed them?
Which sources provide them?
Which packages are unsigned?
Phase 2: Report
Generate warnings without blocking builds.
Phase 3: Remediation
Replace or approve packages according to policy.
Phase 4: Enforcement
Convert violations into CI failures.
This gives teams time to fix legitimate dependencies before enforcement becomes mandatory.
Build an Allowlist Carefully
Organizations sometimes create an allowlist:
Approved publisher A
Approved publisher B
Approved package source C
This can be useful.
But avoid creating a giant permanent exception list:
Exception 1
Exception 2
Exception 3
...
Exception 157
At that point, the security policy may become difficult to understand and maintain.
Every exception should have:
Reason
Owner
Approval
Expiration or review date
Affected package
Risk assessment
Combine Signing With Locked Dependencies
Package signing answers an integrity and publisher-trust question.
It does not guarantee that your build will always resolve exactly the same dependency graph.
That is why package signing works particularly well with locked dependency graphs.
For example:
Package signing
+
packages.lock.json
+
Controlled package sources
|
v
More predictable dependency supply chain
A lock file can help ensure that dependency resolution does not unexpectedly change between builds.
The signature then provides an additional integrity signal for the resolved packages.
Use Package Source Mapping
Another useful control is package source mapping.
Suppose your project has:
Internal packages
Public packages
You can define which package names are allowed to come from which sources.
Conceptually:
Contoso.* -> Internal feed
ThirdParty.* -> Approved public feed
This reduces the chance that a package is unexpectedly resolved from a different source.
This is particularly relevant to dependency confusion attacks.
The controls complement each other:
Source mapping
+
Trusted signing
+
Dependency lock
+
Vulnerability scanning
Each reduces a different class of risk.
Inspect the Dependency Graph
Before enforcing package signing, understand your actual dependency graph.
Run:
dotnet list package
or the corresponding command supported by your current .NET SDK.
The important question is not only:
What packages did we explicitly install?
It is also:
What transitive packages are entering the application?
For example:
Application
|
+-- Package A
| |
| +-- Package C
|
+-- Package B
|
+-- Package D
A package you never explicitly selected can still become part of your application's supply chain.
Detect Unexpected Package Changes
Historical package analysis is useful here.
Suppose a build previously resolved:
Package A 4.2.1
Package B 7.1.0
Package C 2.5.3
and a later build resolves:
Package A 4.2.1
Package B 7.2.0
Package C 2.5.3
That dependency change should be visible.
Track:
Package ID
Version
Source
Signer
Signature status
Lock-file state
Unexpected changes should receive the same attention as source-code changes.
Add Security Checks to Pull Requests
Dependency changes should receive focused review.
For example:
Developer changes PackageReference
|
v
Pull request
|
+---- Dependency diff
|
+---- Vulnerability scan
|
+---- Signature verification
|
+---- Source validation
|
v
Review
A reviewer can then see not just:
<PackageReference Include="Example.Package"
Version="5.4.0" />
but also the security context surrounding that dependency.
Handle Unsigned Packages
Unsigned does not automatically mean malicious.
There are legitimate packages that may not have signatures.
The policy decision depends on your environment.
Possible policies include:
Policy A:
Unsigned packages allowed
Policy B:
Unsigned packages allowed with warning
Policy C:
Unsigned packages require explicit approval
Policy D:
Unsigned packages prohibited
Highly regulated or security-sensitive environments may choose stricter policies.
Development repositories may use a more gradual approach.
The important point is to define the rule explicitly.
Monitor Certificate Expiration
Certificate lifecycle management is easy to overlook.
A signing certificate may expire.
That means package-signing operations need lifecycle planning.
Track:
Certificate
Expiration date
Issuer
Publisher identity
Rotation owner
Replacement certificate
Certificate rotation should be tested before the existing certificate reaches expiration.
Do not wait until a production release is blocked.
Understand Timestamping
Package signatures can include timestamp information.
Timestamping helps preserve evidence about when a signature was applied, which can be important when certificates later expire.
This matters for long-lived packages.
For example:
Package signed
|
v
Certificate valid
|
v
Package published
|
v
Certificate eventually expires
The signing system and verification policy should be designed with certificate lifecycle considerations in mind.
Common Mistakes
Treating Any Signature as Trusted
A valid signature does not automatically mean the publisher is trusted by your organization.
Storing Private Keys in Git
Never treat signing certificates and private keys as ordinary project files.
Ignoring Transitive Dependencies
A direct dependency is only part of the dependency graph.
Enforcing Policy Without an Inventory
Existing unsigned packages can cause widespread CI failures.
Relying Only on Signing
Signing does not replace vulnerability scanning, dependency review, source controls, or reproducible builds.
Using Permanent Exceptions
Exceptions should be reviewed rather than becoming an alternative security policy.
Forgetting Certificate Rotation
Signing infrastructure has a lifecycle and needs operational ownership.
Troubleshooting Package Signature Failures
When a package fails signature validation, first determine which layer failed.
Package
|
+-- Signature exists?
|
+-- Signature valid?
|
+-- Certificate valid?
|
+-- Signer trusted?
|
+-- Source allowed?
Possible causes include:
Missing signature
Invalid signature
Modified package
Untrusted signer
Certificate problems
Incorrect trusted-signer configuration
Package source policy violation
Inconsistent CI configuration
Do not immediately add an exception.
First determine why the package failed validation.
A Practical Enterprise Policy
A mature .NET organization can define a policy such as:
1. Use approved package sources.
2. Lock production dependency graphs.
3. Verify package signatures.
4. Maintain a trusted publisher list.
5. Scan dependencies for known vulnerabilities.
6. Review direct dependency changes.
7. Monitor transitive dependency changes.
8. Protect signing keys.
9. Rotate certificates safely.
10. Track and review exceptions.
The goal is defense in depth.
No single control should be expected to protect the entire software supply chain.
Frequently Asked Questions
Does package signing prevent malicious packages?
No. It primarily provides integrity and signer-authenticity signals. A malicious package can still be signed if an attacker controls a legitimate signing identity or if your trust policy accepts an untrusted signer.
Should every NuGet package be signed?
There is no universal requirement. Organizations should define their own risk-based policy based on application sensitivity, regulatory requirements, package ecosystem, and operational constraints.
Is package signing enough for dependency security?
No. Combine it with vulnerability scanning, dependency locking, source controls, package source mapping, code review, and secure build infrastructure.
Can a signed package still contain a vulnerability?
Yes. Signing does not mean that the code is vulnerability-free. It establishes information about package integrity and signer identity, not software quality or security.
What happens when a signing certificate expires?
Certificate lifecycle and verification behavior depend on the signing and timestamping configuration. Organizations should plan certificate rotation and validate their verification policy before expiration.
Should unsigned packages automatically fail CI?
That is a policy decision. A strict environment may reject them, while another environment may allow them with explicit approval. The important part is that the behavior is deliberate and documented.
Conclusion
NuGet package signing adds an important layer of trust to the .NET software supply chain. It allows organizations to verify package integrity and make stronger decisions about who is allowed to publish dependencies. However, signing should not be treated as a standalone security solution.
The strongest approach combines trusted package sources, package signing, dependency locking, source mapping, vulnerability scanning, dependency review, protected signing infrastructure, and automated CI enforcement. Start by understanding the packages your organization already consumes, identify where signatures and trust relationships are missing, introduce the policy in reporting mode, and then gradually move toward enforcement. This turns package signing from a theoretical security feature into a practical control that can reduce supply-chain risk without unnecessarily disrupting development.
Join the conversation! Your thoughts help the community grow.