Introduction
NuGet API keys have traditionally been a convenient way to authenticate package publishing from local machines and CI/CD pipelines. The problem is that a credential used by an automated pipeline can remain valid long after the pipeline needs it. If that credential is exposed through logs, configuration files, build artifacts, or a compromised runner, the attacker may be able to use it to publish or manage packages within its configured scope.
NuGet already provides scoped API keys with expiration periods, and current NuGet guidance also recommends more secure approaches such as environment variables, credential providers, and Trusted Publishing where available.
The important change for teams is not simply creating another API key. It is treating package-publishing credentials as short-lived deployment credentials and designing the pipeline around rotation.
This article explains how to migrate a .NET CI/CD pipeline from a long-lived NuGet API key to a 30-day credential model, how to avoid common migration failures, and when keyless Trusted Publishing is a better option.
Why Long-Lived NuGet API Keys Are a CI/CD Risk
A NuGet API key should be treated like a password. Microsoft explicitly recommends keeping it secret and deleting or regenerating it if it is exposed. Scoped keys can also be restricted by operation and package pattern.
A long-lived credential creates a larger security window.
For example, consider a pipeline that stores this secret:
NUGET_API_KEY = <long-lived-secret>
If the secret is accidentally exposed today but nobody notices it for several months, the attacker may still be able to use it.
With a 30-day credential, the exposure window is substantially shorter.
The security principle is straightforward:
Long-lived credential
|
v
Long exposure window
|
v
Higher impact if leaked
versus:
Short-lived credential
|
v
Smaller exposure window
|
v
Lower credential lifetime risk
Expiration does not replace least privilege, however. A 30-day key with permission to push every package is still broader than necessary.
What Changes When You Move to 30-Day Credentials
The main operational change is that credential rotation becomes part of the deployment process.
A useful migration model is:
| Area | Long-lived key | 30-day key |
|---|---|---|
| Credential lifetime | Long | Short |
| Rotation | Occasional/manual | Planned/regular |
| Exposure window | Larger | Smaller |
| CI/CD secret update | Infrequent | Recurring |
| Package scope | Should be restricted | Should be restricted |
| Operational dependency | Low | Higher |
| Security posture | Weaker | Stronger |
| Keyless alternative | Usually separate migration | Can be replaced by Trusted Publishing |
NuGet API keys support scopes for operations and package patterns, allowing teams to create separate credentials for different publishing responsibilities.
That means a production publishing key should not automatically be treated as a universal organization-wide credential.
Step 1: Audit Existing NuGet Credentials
Before creating replacement keys, identify where the existing credential is used.
Search your CI/CD configuration for:
NUGET_API_KEY
NUGET_AUTH_TOKEN
NuGetApiKey
dotnet nuget push
nuget push
Also inspect:
CI/CD secret stores
Repository variables
Environment variables
Pipeline YAML files
Release scripts
Build containers
Deployment scripts
Developer documentation
Local
NuGet.Configfiles
The goal is to establish a credential inventory.
For example:
Repository A
-> Package A
-> GitHub Actions
-> NUGET_API_KEY
Repository B
-> Package B
-> Azure pipeline
-> NuGet API key
Repository C
-> Multiple packages
-> Shared publishing credential
A shared credential is an important finding because rotating it can affect multiple pipelines simultaneously.
Step 2: Create a Scoped Replacement Key
Create a new API key rather than modifying the existing production pipeline first.
The key should be scoped to the smallest practical set of permissions.
For example:
Scope:
Push
Package pattern:
Contoso.*
Expiration:
30 days
The exact package pattern should match your publishing model.
If a pipeline publishes only one package, there is little reason to give it access to unrelated packages.
This follows the principle of least privilege:
Required permission
|
v
Required package scope
|
v
Required lifetime
NuGet documentation confirms that API keys can be scoped by operation and package glob pattern and can have an expiration timeframe.
Step 3: Store the Credential Outside Source Control
Never put the API key directly into a repository.
Avoid:
env:
NUGET_API_KEY: "actual-secret-value"
Instead, reference the CI/CD platform's secret store:
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
The exact syntax depends on your CI/CD platform.
Current NuGet tooling supports the NUGET_API_KEY environment variable for package publishing. Microsoft documents this capability for NuGet 7.6 and later.
This approach also avoids placing the API key directly into the command-line arguments.
Step 4: Update the Package Publishing Command
A typical publishing command is:
dotnet nuget push ./artifacts/MyLibrary.1.0.0.nupkg \
--source https://api.nuget.org/v3/index.json
When NUGET_API_KEY is supplied through the environment, the command does not need to contain the secret explicitly.
For example:
export NUGET_API_KEY="$NUGET_API_KEY"
dotnet nuget push ./artifacts/MyLibrary.1.0.0.nupkg \
--source https://api.nuget.org/v3/index.json
The NuGet CLI documentation states that NUGET_API_KEY can provide the API key for push operations, while an explicit --api-key command-line value takes precedence.
This precedence is worth remembering during migration. A pipeline can appear to use the new secret while an older command-line credential is still taking precedence.
Step 5: Test the New Credential Before Revoking the Old One
Do not immediately delete the old key after generating the replacement.
First execute a controlled publishing test.
A practical sequence is:
Create the new scoped credential.
Store it in the CI/CD secret manager.
Update the pipeline.
Run the pipeline against the intended package.
Confirm the package is published successfully.
Confirm no unexpected packages can be modified.
Check logs for accidental secret exposure.
Revoke the old credential.
This creates a safer migration path.
If the new key fails, the existing credential remains available while the problem is investigated.
Step 6: Rotate the Credential Without Creating Pipeline Downtime
The biggest operational mistake is treating expiration as the rotation process.
Suppose a credential expires at:
Day 30, 10:00 UTC
and the team discovers the problem at:
Day 30, 10:15 UTC
The next package release may fail.
A better model is:
Day 1
|
+-- Create credential
|
v
Day 20
|
+-- Prepare replacement
|
v
Day 21
|
+-- Update CI/CD secret
|
v
Day 21+
|
+-- Validate publishing
|
v
Before expiry
|
+-- Revoke old credential
The exact rotation schedule should be determined by the credential's actual expiration date and the organization's release process.
Do not assume that a 30-day credential means you should wait until day 30.
A Safer CI/CD Pattern
A production-oriented pipeline should separate package creation from package publishing.
For example:
steps:
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --configuration Release --no-build
- name: Pack
run: dotnet pack --configuration Release --no-build --output ./artifacts
- name: Publish
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: |
dotnet nuget push ./artifacts/*.nupkg \
--source https://api.nuget.org/v3/index.json
This structure has an important security property: the secret is introduced only at the publishing stage.
Build and test jobs do not need access to the publishing credential.
That reduces the number of places where a secret can potentially be exposed.
When Trusted Publishing Is Better Than API Keys
Short-lived API keys are not necessarily the final destination.
NuGet supports Trusted Publishing, which uses short-lived credentials issued through a CI/CD identity mechanism rather than requiring a long-lived API key to be stored as a persistent secret. Microsoft documents GitHub Actions support using OIDC-based authentication, with NuGet issuing a temporary API key for the publishing operation.
The conceptual flow is:
CI/CD workflow
|
v
OIDC identity token
|
v
NuGet validates trusted publisher
|
v
Temporary publishing credential
|
v
Package push
The temporary NuGet credential described by Microsoft is valid for one hour, making it significantly different from manually managed API keys.
For supported CI/CD environments, Trusted Publishing can eliminate the recurring secret-rotation problem altogether.
30-Day API Keys vs Trusted Publishing
| Capability | 30-Day API Key | Trusted Publishing |
|---|---|---|
| Stored secret required | Usually yes | No persistent API key |
| Rotation work | Required | Greatly reduced |
| Credential lifetime | Configured expiration | Short-lived |
| CI/CD identity | Secret-based | Identity-based |
| Least privilege | API key scopes | Trusted publisher policy |
| Setup complexity | Lower | Higher initially |
| Long-term direction | Useful migration option | Stronger keyless model |
For teams that cannot immediately adopt Trusted Publishing, short-lived scoped keys are still a meaningful improvement.
Common Migration Mistakes
Putting the New Key in the Repository
This defeats the purpose of credential rotation.
Use the CI/CD platform's secret management mechanism instead.
Giving the Key Excessive Package Scope
A pipeline publishing one package should not automatically receive unrestricted publishing access.
Use the narrowest package pattern and operation scope practical.
Rotating Without Testing
Changing a credential and waiting for the next production release to discover a configuration problem is risky.
Perform a controlled publishing test first.
Leaving the Old Key Active
After migration, the old credential becomes unnecessary attack surface.
Once the replacement has been verified, revoke the old key.
Forgetting Other Pipelines
Shared credentials are particularly easy to miss.
Search all repositories and deployment systems before revocation.
Logging Secrets
Avoid diagnostic commands that print environment variables or authentication configuration.
Also review CI/CD logs after migration to make sure the credential is not being exposed.
Troubleshooting NuGet Authentication Failures
A publishing command can fail for several reasons.
| Symptom | Likely cause | What to check |
|---|---|---|
| HTTP 403 | Invalid, expired, or unauthorized key | Key value, expiration, scope |
| Package cannot be pushed | Package scope mismatch | Glob pattern |
| Pipeline still uses old credential | Precedence issue | --api-key, environment, config |
| Local publish works but CI fails | Secret not available to runner | CI/CD secret configuration |
| New key works locally but not in CI | Environment mismatch | Secret name and job permissions |
| Credential appears ignored | Wrong environment variable | NUGET_API_KEY naming |
| Publish suddenly fails after several weeks | Credential expired | Expiration date |
NuGet documentation specifically identifies invalid or expired API keys and insufficient permissions as possible causes of HTTP 403 responses during publishing.
Also remember that NuGet credential sources have precedence rules. For authenticated feeds, NuGet can obtain credentials from environment variables, configuration files, or credential providers depending on the source and setup.
Best Practices for NuGet CI/CD Credential Security
A practical baseline is:
Use scoped API keys rather than broad credentials.
Set a short expiration period.
Store credentials in the CI/CD secret manager.
Introduce secrets only in the publishing job.
Never commit API keys to source control.
Avoid printing environment variables in pipeline logs.
Test replacement credentials before revoking existing credentials.
Track expiration dates.
Remove credentials that are no longer required.
Evaluate Trusted Publishing for supported workflows.
The important point is that credential security is both a technical and operational problem. A secure credential that nobody knows how to rotate is eventually an operational risk.
Frequently Asked Questions
Should I use one NuGet API key for all repositories?
Prefer separate, narrowly scoped credentials where practical. A shared credential increases the impact of a compromise and makes rotation more disruptive.
Can I pass the NuGet API key directly to dotnet nuget push?
Yes. The --api-key option is supported, but using the CI/CD environment variable mechanism can help avoid putting the secret directly into command arguments. NuGet documents NUGET_API_KEY as an alternative for push operations.
What happens when the API key expires?
Publishing operations using that credential fail authentication. The replacement credential must be configured before the next release that depends on it.
Is a 30-day key automatically secure?
No. Shorter lifetime reduces exposure, but you still need least privilege, secure storage, logging controls, and proper rotation.
Should I migrate directly to Trusted Publishing?
If your CI/CD environment and NuGet publishing workflow support it, Trusted Publishing is worth evaluating because it removes the need to maintain a persistent publishing API key.
Conclusion
Moving NuGet publishing credentials to a short-lived model is more than a credential replacement exercise. It is an opportunity to redesign the publishing pipeline around least privilege, controlled secret exposure, predictable rotation, and eventually keyless authentication.
For teams that still depend on API keys, a scoped 30-day credential provides a practical transition: create the replacement, store it securely, test the publishing workflow, rotate before expiration, and revoke the old key. For supported CI/CD environments, Trusted Publishing provides an even stronger model by replacing persistent secrets with short-lived workload identity.
The safest CI/CD pipeline is not the one with the most complicated security configuration. It is the one where credentials have the minimum permissions, exist for the minimum practical time, and are automatically or predictably replaced before they become a release-time problem.

Join the conversation! Your thoughts help the community grow.