From 8c80ae74215aadd3f9cbe42b9ed624aa843f1bed Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Jul 2026 09:57:51 +0000 Subject: [PATCH] ci: grant release.yml write scopes at the job level, not top level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit release.yml declared contents/id-token/attestations write at the top level, so OpenSSF Scorecard's Token-Permissions check scored 0 ("detected GitHub workflow tokens with excessive permissions") — a top-level write applies to every job. Move the three write scopes onto the single pack-push job that uses them and keep the top-level token read-only (contents: read). Behaviour is unchanged (the one job gets exactly the same permissions), but the workflow now follows the least-privilege shape the check rewards. English and French maintainer notes kept in sync. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014W1zrcBNy3jEs8ngAQKwZg --- .github/workflows/release.yml | 20 ++++++++++++-------- maintainers/workflows/release.en.md | 6 ++++-- maintainers/workflows/release.fr.md | 5 ++++- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8117c15..740d905 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,15 +28,10 @@ concurrency: group: release-${{ github.ref }} cancel-in-progress: false +# Least privilege at the top level: read-only by default. The single job below widens only the write scopes it +# needs, which is what OpenSSF Scorecard's Token-Permissions check rewards (a read-only top level, writes per job). permissions: - # Create the GitHub Release and upload the attested .nupkg/.snupkg as its assets (see the publish step). - contents: write - # Allow the job to request a GitHub OIDC token, exchanged for a short-lived NuGet API key - # via trusted publishing (no long-lived NUGET_API_KEY secret is stored). - id-token: write - # Store the signed build-provenance attestations in the repository's attestation store, - # so anyone can verify a released package with `gh attestation verify`. - attestations: write + contents: read env: DOTNET_NOLOGO: 'true' @@ -50,6 +45,15 @@ jobs: runs-on: ubuntu-latest # build + test + pack + attest + push is ~4 min; cap a hung release rather than the 6-hour default. timeout-minutes: 20 + permissions: + # Create the GitHub Release and upload the attested .nupkg/.snupkg as its assets (see the publish step). + contents: write + # Request a GitHub OIDC token, exchanged for a short-lived NuGet API key via trusted publishing + # (no long-lived NUGET_API_KEY secret is stored). + id-token: write + # Store the signed build-provenance attestations in the repository's attestation store, + # so anyone can verify a released package with `gh attestation verify`. + attestations: write steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 diff --git a/maintainers/workflows/release.en.md b/maintainers/workflows/release.en.md index b68522d..4568aae 100644 --- a/maintainers/workflows/release.en.md +++ b/maintainers/workflows/release.en.md @@ -40,8 +40,10 @@ SPDX SBOM) → upload artifacts → **attest build provenance** → **NuGet logi The workflow needs three write scopes: `contents: write` (create the Release and upload assets), `id-token: write` (mint the short-lived NuGet key via trusted -publishing), and `attestations: write` (store the signed provenance). No -long-lived `NUGET_API_KEY` is stored. +publishing), and `attestations: write` (store the signed provenance). They are +granted on the `pack-push` job only; the top-level token stays read-only +(`contents: read`) — the least-privilege shape OpenSSF Scorecard's +Token-Permissions check rewards. No long-lived `NUGET_API_KEY` is stored. ## Handle with care diff --git a/maintainers/workflows/release.fr.md b/maintainers/workflows/release.fr.md index 8f8d392..6e7e16c 100644 --- a/maintainers/workflows/release.fr.md +++ b/maintainers/workflows/release.fr.md @@ -42,7 +42,10 @@ run. Le workflow a besoin de trois périmètres en écriture : `contents: write` (créer la Release et uploader les assets), `id-token: write` (générer la clé NuGet éphémère via trusted publishing) et `attestations: write` (stocker la provenance -signée). Aucun `NUGET_API_KEY` durable n'est stocké. +signée). Ils sont accordés uniquement sur le job `pack-push` ; le token top-level +reste en lecture seule (`contents: read`) — la forme de moindre privilège que +récompense le check Token-Permissions d'OpenSSF Scorecard. Aucun `NUGET_API_KEY` +durable n'est stocké. ## À manipuler avec précaution