From 2f87b97f7f9b66b30eb256e74a2b2b73ad76e97c Mon Sep 17 00:00:00 2001 From: The01Geek Date: Sat, 29 Aug 2026 17:34:35 -0600 Subject: [PATCH] ci: a fork can pass the release check, and absent provenance cannot Two defects in this repository's required check, both in its artifact verification step. A pull request from a fork could never pass it. The exemption that skips artifact verification for an ordinary pull request was gated on the pull request coming from this repository, so a fork matched no arm, fell through to full verification, and failed on every file it changed. That gate was collateral from the v2.38.1 fork-bypass fix, whose actual subject is the judge-comparison step above -- untouched here, and still refusing any fork that edits the verifier. Skipping grants a fork exactly what a same-repository pull request already gets, and it reduces candidate code execution, since a fork no longer reaches the verifier invocation. A tree carrying no provenance reported success. The guard refusing an absent .release/source.json keyed on the branch name alone, and a push carries the branch name "main" rather than a release/* name -- so deleting that one file turned this check green on the published branch. A pull request that deletes provenance the base carries is now refused as well. Co-Authored-By: Claude Opus 5 --- .github/workflows/distribution-verify.yml | 45 +++++++++++++++-------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/.github/workflows/distribution-verify.yml b/.github/workflows/distribution-verify.yml index f50e65811..75540a6fa 100644 --- a/.github/workflows/distribution-verify.yml +++ b/.github/workflows/distribution-verify.yml @@ -44,8 +44,9 @@ jobs: BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_REF: ${{ github.event.pull_request.head.ref }} # A fork chooses its own branch name, so a branch-name test is NOT an - # identity test. Every exemption below is gated on the pull request - # coming from this repository; a fork always takes the strict path. + # identity test. Both exemptions in THIS step are gated on the pull + # request coming from this repository; a fork can never exempt itself + # from the judge comparison. IS_SAME_REPO: ${{ github.event.pull_request.head.repo.full_name == github.repository }} run: | set -euo pipefail @@ -99,32 +100,46 @@ jobs: env: HEAD_REF: ${{ github.event.pull_request.head.ref || github.ref_name }} GITHUB_EVENT_NAME: ${{ github.event_name }} - IS_SAME_REPO: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | set -euo pipefail # A tree carrying no .release/ metadata is not a release candidate. That # is the normal state before the first generated release lands, and it - # must not be reported as a failure. It is NEVER acceptable from a - # release branch, though: that is exactly the missing-provenance release - # this check exists to stop, so the two cases are separated by branch. + # must not be reported as a failure. It is NEVER acceptable on what is + # actually published - main, or a release branch - nor from a pull + # request that deletes provenance the base carries: that is exactly the + # missing-provenance release this check exists to stop. if [ ! -f .release/source.json ]; then - case "$HEAD_REF" in - release/*) - echo "::error::a release/* candidate carries no .release/source.json — refusing to publish a release without provenance" + case "${GITHUB_EVENT_NAME}:${HEAD_REF}" in + push:*|pull_request:release/*) + echo "::error::this tree carries no .release/source.json — refusing to publish without provenance" exit 1 ;; - *) - echo "::notice::no .release/ metadata: this tree is not a generated release candidate, so there is no artifact to verify." - exit 0 ;; esac + # A pull request that DELETES provenance the base carries is not a + # pre-release tree; without this the whole check reports success on a + # tree whose manifest was simply removed. + if git cat-file -e "${BASE_SHA}:.release/source.json" 2>/dev/null; then + echo "::error::this pull request removes .release/source.json, which the base branch carries — refusing to drop release provenance" + exit 1 + fi + echo "::notice::no .release/ metadata: this tree is not a generated release candidate, so there is no artifact to verify." + exit 0 fi # Once a release has landed, main carries .release/files.sha256 and EVERY # edited file is a digest mismatch. Full artifact verification therefore # belongs to what is actually being published - a release branch, or main # itself - and not to an ordinary pull request, which would otherwise be # unmergeable for changing so much as a typo. - case "${GITHUB_EVENT_NAME}:${IS_SAME_REPO}:${HEAD_REF}" in - pull_request:*:release/*|push:*:*) : ;; - pull_request:true:*) + # + # Origin is deliberately NOT a discriminator here, unlike in the judge + # step above: gating on it leaves every outside contribution permanently + # red on the required check, while skipping grants a fork exactly what a + # same-repo pull request already gets. A release/* candidate is verified + # whatever its origin, and a fork that touches the judge is already + # refused above. + case "${GITHUB_EVENT_NAME}:${HEAD_REF}" in + pull_request:release/*|push:*) : ;; + pull_request:*) echo "::notice::not a release branch: the digest manifest describes the published release, so an ordinary pull request is not verified against it." exit 0 ;; esac