ci(release): publish via NuGet trusted publishing (OIDC), drop the stored API key - #15
Conversation
…ored API key nuget.org now promotes trusted publishing over long-lived API keys. Switch release.yml to OIDC: - Add the id-token:write permission so the job can mint a GitHub OIDC token. - Add a NuGet/login@v1 step that exchanges the OIDC token for a short-lived (~1h) nuget.org API key (no NUGET_API_KEY secret is stored anywhere). - Push with that ephemeral key; keep --skip-duplicate and the symbol-package glob. Requires (one-time, maintainer): a Trusted Publishing policy on nuget.org for owner=moisesja, repo=credentials-dotnet, workflow file=release.yml, environment=nuget-release; and a repository variable NUGET_USER = the nuget.org account username. The policy is owner-scoped (no package-name field), so it covers the brand-new Credentials.* package IDs on first publish. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
moisesja
left a comment
There was a problem hiding this comment.
Good motivation — dropping a long-lived secret in favour of OIDC is the right call, and the change is small and focused. Two issues worth fixing before merge.
1. Unpinned action SHA — high severity
uses: NuGet/login@v1The v1 tag is mutable; anyone who can push to the NuGet/login repository can silently redirect it. This step runs with id-token: write, meaning a compromised version of the action could steal your OIDC token and exchange it for a short-lived nuget.org key before you notice.
Pin to the exact commit SHA instead:
uses: NuGet/login@<commit-sha> # v1.x.xCheck the current tip SHA at https://github.com/NuGet/login/commits/v1 before merging and annotate it with the tag name for human readability.
The same concern applies to the pre-existing actions/checkout@v4, actions/setup-dotnet@v4, and actions/upload-artifact@v4, though those do not hold an OIDC write grant. The NuGet login step is the urgent one; the others are worth a follow-up.
2. Step output used inline — confirm masking
--api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}"Step outputs are not automatically masked by GitHub Actions the way secrets.* values are. If NuGet/login@v1 does not call ::add-mask:: internally, the key appears in plain text in the run log.
Safe pattern regardless of what the action does:
- name: Push to nuget.org
env:
NUGET_API_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }}
run: |
dotnet nuget push "./artifacts/packages/*.nupkg" \
--api-key "$NUGET_API_KEY" \
...Environment variable injection triggers GitHub's secret masker on the value at assignment time. If you've confirmed from the action's source that it calls ::add-mask::, add a comment to that effect; otherwise switch to the env: pattern.
Everything else looks correct: the environment: nuget-release gate, id-token: write scoped to the job (not workflow), --skip-duplicate, and the one-time setup instructions in the description.
Generated by Claude Code
nuget.org now promotes trusted publishing over long-lived API keys. The first
v1.0.0publish failed because noNUGET_API_KEYsecret was set; rather than store a long-lived key, this switchesrelease.ymlto OIDC trusted publishing — no stored secret at all.Pipeline change (
release.yml)permissions:addsid-token: write(lets the job mint a GitHub OIDC token).NuGet/login@v1step (verified against the action'saction.yml/README) exchanges the OIDC token for a short-lived (~1h) nuget.org API key, outputNUGET_API_KEY.${{ steps.nuget-login.outputs.NUGET_API_KEY }};--skip-duplicate+ the symbol-package glob are unchanged.user:is read from a repo variableNUGET_USER(the nuget.org account username, not an email).One-time maintainer setup (required before re-tagging)
moisesjacredentials-dotnetrelease.yml(filename only — no.github/workflows/path)nuget-releaseThe policy is owner-scoped (no package-name field), so it covers the brand-new
Credentials.Core/.Extensions.DependencyInjection/.RdfcIDs on first publish. (A new policy on a private repo starts in a 7-day temporary-active window; this repo is public, and a successful publish makes it permanent.)NUGET_USER= your nuget.org username.After merge + setup
The
v1.0.0tag currently points at the pre-fix commit (oldrelease.yml), and its publish failed — nothing was published. So once this merges and the setup above is done, thev1.0.0tag is moved to the merge commit and re-pushed to trigger a clean OIDC publish. (Safe to move since1.0.0was never published.)🤖 Generated with Claude Code