From db71a1ecfb8df3b57bad8bc262aa94b06623bef1 Mon Sep 17 00:00:00 2001 From: Philippe Matray Date: Sat, 25 Jul 2026 15:10:49 +0200 Subject: [PATCH] ci: publish to NuGet via Trusted Publishing, not a long-lived key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit continuous.yml passed a NUGET_API_KEY repository secret into the Nuke build. It now exchanges the GitHub OIDC token for a key valid ~1 hour (NuGet/login v1.2.0) and passes that through the same NUGET_API_KEY variable, so build/Build.cs is unchanged — Nuke reads an env var and neither knows nor cares that the key expires. The only remaining secret is NUGET_USER, the nuget.org profile name. The login step is gated on a version tag, mirroring the build's own gate: Continuous triggers PublishIfNeeded, which is OnlyWhenStatic(IsOnVersionTag() && IsServerBuild) before it reaches Publish's Requires(NuGetApiKey). Pushes to main/dev and pull_request runs leave the key unset today and must keep doing so — minting one on a pull request would be pointless and would fail outright for a fork, which cannot obtain this repository's OIDC token. build/README.md's environment-variable note updated: in CI the variable is fed by the OIDC exchange, and only a local manual publish uses a real key. Note this repo's CI is currently red for an unrelated reason (NU1902, AngleSharp 1.4.0 advisory treated as an error); this change does not address that and cannot go green until the package is bumped. --- .github/workflows/continuous.yml | 23 ++++++++++++++++++++++- build/README.md | 6 +++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous.yml b/.github/workflows/continuous.yml index 1ce87b4..b771549 100644 --- a/.github/workflows/continuous.yml +++ b/.github/workflows/continuous.yml @@ -15,6 +15,7 @@ on: permissions: contents: write packages: write + id-token: write # request the GitHub OIDC token for NuGet Trusted Publishing jobs: ubuntu-latest: @@ -35,10 +36,30 @@ jobs: .nuke/temp ~/.nuget/packages key: ${{ runner.os }}-${{ hashFiles('global.json', '**/*.csproj') }} + # Trusted Publishing: exchanges the OIDC token for a NuGet key valid ~1 hour, so no long-lived + # secret is stored. Nuke reads it from the NUGET_API_KEY environment variable exactly as + # before, so build/Build.cs is unchanged — it neither knows nor cares that the key expires. + # + # Gated on a version tag, mirroring the build's own gate: Continuous triggers PublishIfNeeded, + # which is OnlyWhenStatic(IsOnVersionTag() && IsServerBuild) before it reaches Publish's + # Requires(NuGetApiKey). So a push to main/dev and every pull_request run leave the key unset + # today, and must keep doing so — minting a key on a pull request would both be pointless and + # fail outright for a fork, which cannot obtain this repository's OIDC token. + # + # Requires a Trusted Publishing policy on nuget.org for the FormCraft and + # FormCraft.ForMudBlazor packages, naming this repository and continuous.yml, plus the + # NUGET_USER secret (the nuget.org profile name). + - name: NuGet login (OIDC -> short-lived key) + id: nuget-login + if: startsWith(github.ref, 'refs/tags/v') + uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0 + with: + user: ${{ secrets.NUGET_USER }} + - name: 'Run: Continuous' run: ./build.cmd Continuous env: - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + NUGET_API_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: 'Publish: test-results' uses: actions/upload-artifact@v7 diff --git a/build/README.md b/build/README.md index 35acf1b..5c13850 100644 --- a/build/README.md +++ b/build/README.md @@ -138,7 +138,11 @@ scoop install git-cliff ### Environment Variables -- `NUGET_API_KEY`: Required for publishing to NuGet.org +- `NUGET_API_KEY`: Required for publishing to NuGet.org. In CI this is **not** a stored secret — + `continuous.yml` obtains a key valid about an hour from NuGet + [Trusted Publishing](https://learn.microsoft.com/nuget/nuget-org/trusted-publishing) via GitHub's + OIDC token, and passes it in through this same variable, so the build needs no change. Only a + local, manual publish uses a real API key you supply yourself. ### Parameters