From cb78dbb5dfaaa2f3aa071e126bc52dfd2ea08f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammet=20=C5=9Eafak?= Date: Wed, 4 Nov 2026 19:58:41 +0300 Subject: [PATCH 1/3] ci(release): publish to NuGet via Trusted Publishing Replace the long-lived NUGET_API_KEY secret with a short-lived key issued by NuGet/login@v1 over GitHub OIDC. Add a workflow_dispatch dry run (verify-trusted-publishing job) that only exercises the login, so the nuget.org policy can be verified without publishing a version. --- .github/workflows/release.yml | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 56d097c..8bf936e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,12 +3,15 @@ name: Release on: push: tags: [ "v*" ] + workflow_dispatch: permissions: contents: write + id-token: write jobs: release: + if: github.event_name == 'push' runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -36,10 +39,16 @@ jobs: - name: Pack run: dotnet pack src/BabelQueue.Core/BabelQueue.Core.csproj -c Release -o artifacts + - name: NuGet login (Trusted Publishing) + id: nuget-login + uses: NuGet/login@v1 + with: + user: ${{ vars.NUGET_USER }} + - name: Publish to NuGet run: > dotnet nuget push "artifacts/*.nupkg" - --api-key "${{ secrets.NUGET_API_KEY }}" + --api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate @@ -49,3 +58,26 @@ jobs: generate_release_notes: true files: results.json fail_on_unmatched_files: true + + # Manual dry run: exercises only the NuGet Trusted Publishing login (no test, pack, push + # or GitHub release), so the nuget.org policy can be verified without cutting a version. + verify-trusted-publishing: + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - name: NuGet login (Trusted Publishing) + id: nuget-login + uses: NuGet/login@v1 + with: + user: ${{ vars.NUGET_USER }} + + - name: Verify the short-lived API key was issued + env: + KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }} + run: | + if [ -n "$KEY" ]; then + echo "Trusted Publishing login OK" + else + echo "Trusted Publishing login returned an empty API key" >&2 + exit 1 + fi From 5559324bf81fe05f9ede8d2601d60ef8fe6ae0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammet=20=C5=9Eafak?= Date: Wed, 4 Nov 2026 20:01:57 +0300 Subject: [PATCH 2/3] ci(release): split NuGet publish into a least-privilege job Build, test and pack now run with read-only permissions and no OIDC token. The .nupkg files (and the conformance results asset where used) are handed over as artifacts to a separate publish job that needs the build job and alone holds id-token: write and contents: write for NuGet/login, the push and the GitHub release. The dry-run job only gets id-token: write. --- .github/workflows/release.yml | 43 +++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8bf936e..9eb86c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,7 @@ on: workflow_dispatch: permissions: - contents: write - id-token: write + contents: read jobs: release: @@ -39,6 +38,44 @@ jobs: - name: Pack run: dotnet pack src/BabelQueue.Core/BabelQueue.Core.csproj -c Release -o artifacts + - name: Upload packages + uses: actions/upload-artifact@v7 + with: + name: nupkg + path: artifacts/*.nupkg + if-no-files-found: error + retention-days: 1 + + - name: Upload conformance results + uses: actions/upload-artifact@v7 + with: + name: conformance-results + path: results.json + if-no-files-found: error + retention-days: 1 + + # Runs only after build + tests passed. The only job (besides the dry run) that can request + # an OIDC token, so third-party build/test code never sees ACTIONS_ID_TOKEN_REQUEST_*. + publish: + if: github.event_name == 'push' + needs: release + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - name: Download packages + uses: actions/download-artifact@v8 + with: + name: nupkg + path: artifacts + + - name: Download conformance results + uses: actions/download-artifact@v8 + with: + name: conformance-results + path: . + - name: NuGet login (Trusted Publishing) id: nuget-login uses: NuGet/login@v1 @@ -64,6 +101,8 @@ jobs: verify-trusted-publishing: if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest + permissions: + id-token: write steps: - name: NuGet login (Trusted Publishing) id: nuget-login From d4299091b3e552af2a78baff81bea883207c00a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammet=20=C5=9Eafak?= Date: Wed, 4 Nov 2026 20:03:57 +0300 Subject: [PATCH 3/3] ci(release): hand symbol packages to the publish job All csproj files emit a .snupkg next to the .nupkg, and dotnet nuget push picks up the sibling symbol package. Upload both globs so the publish job pushes the same set the old single job did. --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9eb86c7..3a6fdc7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,7 +42,10 @@ jobs: uses: actions/upload-artifact@v7 with: name: nupkg - path: artifacts/*.nupkg + # Same set the old inline push sent: dotnet nuget push also picks up the sibling .snupkg. + path: | + artifacts/*.nupkg + artifacts/*.snupkg if-no-files-found: error retention-days: 1