diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8e209b1d..f75c829a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -424,7 +424,10 @@ jobs: needs.release-seal.result == 'success')) runs-on: ubuntu-latest permissions: - contents: read + # GitHub hides drafts from contents:read tokens. This short-lived, + # repository-scoped token can inspect the sealed draft without exposing + # the organization credential used by release publication and automerge. + contents: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 diff --git a/tests/releasecontrol/release_control_test.go b/tests/releasecontrol/release_control_test.go index 763e6e1e..66c26871 100644 --- a/tests/releasecontrol/release_control_test.go +++ b/tests/releasecontrol/release_control_test.go @@ -581,11 +581,15 @@ func TestReleaseToolingIsPinnedAndPermissionsAreLeastPrivilege(t *testing.T) { requireContains(t, signer, "id-token: write", path) requireContains(t, signer, `gh release upload "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY"`, path) // 5 references: the admission presence check, the three immutable-release - // gates, and the admission draft read (which uses CI_GITHUB_TOKEN because the + // gates, and the admission draft read (which use CI_GITHUB_TOKEN because the // built-in token is contents: read and cannot see draft releases). if got := strings.Count(workflow, "secrets.CI_GITHUB_TOKEN"); got != 5 { t.Errorf("%s CI_GITHUB_TOKEN secret references = %d, want 5", path, got) } + verify := requireWorkflowJob(t, workflow, "verify-sealed-release", path) + requireContains(t, verify, "contents: write", path) + requireContains(t, verify, "RELEASE_GH_TOKEN: ${{ github.token }}", path) + requireNotContains(t, verify, "secrets.CI_GITHUB_TOKEN", path) requireNotContains(t, workflow, "\nenv:\n CI_GITHUB_TOKEN:", path) const cdPath = ".github/workflows/cd.yml"