Skip to content

ci: publish to NuGet via Trusted Publishing, not a long-lived key - #63

Merged
phmatray merged 1 commit into
devfrom
ci/nuget-trusted-publishing
Jul 27, 2026
Merged

ci: publish to NuGet via Trusted Publishing, not a long-lived key#63
phmatray merged 1 commit into
devfrom
ci/nuget-trusted-publishing

Conversation

@phmatray

Copy link
Copy Markdown
Contributor

The defect

This repo's publish step guarded on secrets.NUGET_API_KEY. Several workflows in this org carry a
comment saying that secret is absent, so the push is "a clean no-op". That was checked with
gh secret list, which reports repository secrets only.

Measured against the API instead:

probe result
actions/organization-secrets 1 — NUGET_API_KEY, created 2026-02-28
actions/secrets (repo) 0
actions/variables · organization-variables 0 · 0

The key resolved. The push was live, not inert.

The change

Trusted Publishing (OIDC): the login step exchanges the GitHub OIDC token for a key valid ~1 h, so
no long-lived secret is stored anywhere. NuGet/login is pinned by digest and kept adjacent to the
push so the key cannot expire in between.

Two details that are easy to get wrong:

  • The job-level permissions: block restates what the workflow-level one granted. A job-level
    block replaces the workflow-level one rather than adding to it — omitting contents would have
    broken the GitHub Release steps.
  • The login step is deliberately unguarded. This job only runs on a release trigger, so
    publishing is expected: a missing policy must fail loudly rather than skip and leave a tagged
    version unpublished behind a green check. Pattern taken from
    RoselineMCP#114, whose argument
    is better than a silent no-op here.

⚠️ Do not merge before doing this, in this order

  1. Create the Trusted Publishing policy on nuget.org, under the account that owns the
    package id, naming this repository and this exact workflow file.
  2. Set the NUGET_USER secret — the nuget.org profile name, not a credential.
  3. Cut one real release and confirm the package appears on the flatcontainer index.
  4. Only then delete NUGET_API_KEY.

Deleting the key first leaves no way back if the policy is wrong. Merging before step 1 means the
next tag fails — loudly, by design, but it fails.

Full procedure and the portfolio-wide worklist: repo-audit/TRUSTED_PUBLISHING.md.

Both publish paths in this org relied on `secrets.NUGET_API_KEY`, and several
workflows commented that the secret was absent so the push was a clean no-op.
That was checked with `gh secret list`, which reports REPOSITORY secrets only.

Measured against the API:
  repos/{owner}/{repo}/actions/organization-secrets -> 1: NUGET_API_KEY (2026-02-28)
  repos/{owner}/{repo}/actions/secrets              -> 0
  actions/variables + organization-variables        -> 0

So the key resolved and the push was live, not inert.

Switches to Trusted Publishing (OIDC): the login step exchanges the GitHub OIDC
token for a key valid ~1 h, so no long-lived secret is stored. NuGet/login is
pinned by digest and kept adjacent to the push so the key cannot expire between.

The job-level `permissions:` block restates what the workflow-level one granted:
a job-level block REPLACES it rather than adding to it, so omitting `contents`
would have broken the GitHub Release steps.

The login step is DELIBERATELY UNGUARDED. This job only runs on a release
trigger, so publishing is expected: a missing policy must fail loudly rather
than skip and leave a tagged version unpublished behind a green check. Pattern
taken from RoselineMCP#114, whose argument is better than a silent no-op here.

BEFORE MERGING, in this order:
  1. create the Trusted Publishing policy on nuget.org, under the account that
     OWNS the package id, naming this repository and this exact workflow file;
  2. set the NUGET_USER secret (the nuget.org profile name, not a credential);
  3. cut one real release and confirm the package appears on the flatcontainer
     index;
  4. only then delete NUGET_API_KEY.
Deleting the key first leaves no way back if the policy is wrong.

Full procedure: repo-audit/TRUSTED_PUBLISHING.md
@phmatray
phmatray merged commit 2f65be9 into dev Jul 27, 2026
5 checks passed
@phmatray
phmatray deleted the ci/nuget-trusted-publishing branch July 27, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant