From 414e27a65e5b048cae29e8d7c0647594b45be7eb Mon Sep 17 00:00:00 2001 From: 0xLeif Date: Tue, 28 Jul 2026 23:46:03 -0600 Subject: [PATCH] fix(release): use checkout credentials for release fetches --- .github/workflows/release.yml | 25 ++++--------- .specsync/change-sequence.json | 4 +-- .../approvals.json | 19 ++++++++++ .../change.md | 24 +++++++++++++ .../context.md | 12 +++++++ .../state.json | 36 +++++++++++++++++++ .../tasks.md | 11 ++++++ .../testing.md | 12 +++++++ Scripts/test-release-distribution.sh | 2 ++ Scripts/test-release-provenance.sh | 15 ++++---- 10 files changed, 133 insertions(+), 27 deletions(-) create mode 100644 .specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/approvals.json create mode 100644 .specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/change.md create mode 100644 .specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/context.md create mode 100644 .specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/state.json create mode 100644 .specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/tasks.md create mode 100644 .specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/testing.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61ea71d..382c4d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,18 +40,14 @@ jobs: # commit rather than resolving a tag that may have moved while queued. ref: ${{ github.event_name == 'workflow_dispatch' && github.event.repository.default_branch || github.sha }} fetch-depth: 0 - persist-credentials: false + persist-credentials: true - name: Fetch selected tag and attest notes env: - GITHUB_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ inputs.tag || github.ref_name }} run: | - authorization="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64)" - git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${authorization}" \ - fetch --force origin "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" - git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${authorization}" \ - fetch origin "+refs/notes/attest:refs/notes/attest" + git fetch --force origin "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" + git fetch origin "+refs/notes/attest:refs/notes/attest" - name: Resolve exact tag commit id: resolve @@ -63,12 +59,9 @@ jobs: - name: Confirm candidate is reachable from the protected default branch env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - GITHUB_TOKEN: ${{ github.token }} RELEASE_COMMIT: ${{ steps.resolve.outputs.commit }} run: | - authorization="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64)" - git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${authorization}" \ - fetch origin \ + git fetch origin \ "refs/heads/${DEFAULT_BRANCH}:refs/remotes/origin/${DEFAULT_BRANCH}" git merge-base --is-ancestor \ "$RELEASE_COMMIT" "refs/remotes/origin/${DEFAULT_BRANCH}" @@ -217,7 +210,7 @@ jobs: # so a moved tag cannot replace the gate or release policy. ref: ${{ github.event_name == 'workflow_dispatch' && github.event.repository.default_branch || needs.provenance.outputs.commit }} fetch-depth: 0 - persist-credentials: false + persist-credentials: true - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -234,14 +227,10 @@ jobs: - name: Refresh release tag and attest notes env: - GITHUB_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ needs.provenance.outputs.tag }} run: | - authorization="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64)" - git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${authorization}" \ - fetch --force origin "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" - git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${authorization}" \ - fetch origin "+refs/notes/attest:refs/notes/attest" + git fetch --force origin "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" + git fetch origin "+refs/notes/attest:refs/notes/attest" - name: Reverify signed release evidence id: attest diff --git a/.specsync/change-sequence.json b/.specsync/change-sequence.json index 89871df..5859683 100644 --- a/.specsync/change-sequence.json +++ b/.specsync/change-sequence.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "sequence": 63, - "id": "CHG-0063-prepare-aps-v1-1-0-atomically-build-and-test-a-portable-linux-bundle-align-hom", + "sequence": 64, + "id": "CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be", "acknowledged_collisions": [] } diff --git a/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/approvals.json b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/approvals.json new file mode 100644 index 0000000..903df2f --- /dev/null +++ b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/approvals.json @@ -0,0 +1,19 @@ +{ + "approvals": [ + { + "gate": "definition", + "actor": "codex", + "timestamp": 1785303728, + "digest": "ad92603b2d7a23f5d3a0a33ed40e518bbc335ea1ce30fbc2333a163a310f9c23", + "note": "Approved targeted CI authentication repair after two identical release fetch failures." + }, + { + "gate": "definition", + "actor": "codex", + "timestamp": 1785303960, + "digest": "88f806418c29a76c109b528f669ea49b8ecef20b3bfab2d16fb8a15626339ff3", + "note": "Refreshed after adding the provenance regression contract to affected paths." + } + ], + "reopenings": [] +} diff --git a/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/change.md b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/change.md new file mode 100644 index 0000000..ab717ca --- /dev/null +++ b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/change.md @@ -0,0 +1,24 @@ +--- +id: CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be +state: implementing +type: bug_fix +base_commit: 7373d124ebb3823c1f7f19651dfffe4d7ed83f51 +--- + +# Fix release workflow fetch authentication so signed tags and attest notes can be fetched on GitHub-hosted runners + +## Intent + +Fix release workflow fetch authentication so signed tags and attest notes can be fetched on GitHub-hosted runners + +## Affected Canonical Specs + +- `aps-cli` + +## Acceptance Criteria + +- Release fetches use one checkout-managed credential, the release distribution contract rejects manual duplicate Authorization headers, and a v1.1.0 workflow dispatch passes provenance. + +## No-spec Rationale + +This repairs CI authentication plumbing without changing the aps CLI contract or release artifact semantics. diff --git a/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/context.md b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/context.md new file mode 100644 index 0000000..4e628d4 --- /dev/null +++ b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/context.md @@ -0,0 +1,12 @@ +--- +change: CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be +artifact: context +--- + +# Context + +The v1.1.0 tag push failed twice before provenance verification because the +release workflow constructed a manual HTTP Authorization header. Git reported +`Failed sending HTTP request` while fetching the tag. The checkout action can +manage the job-scoped credential itself, avoiding custom header construction +while retaining least-lived GitHub token authentication. diff --git a/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/state.json b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/state.json new file mode 100644 index 0000000..f15432b --- /dev/null +++ b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/state.json @@ -0,0 +1,36 @@ +{ + "schema_version": 1, + "id": "CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be", + "slug": "fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be", + "title": "Fix release workflow fetch authentication so signed tags and attest notes can be fetched on GitHub-hosted runners", + "description": "Fix release workflow fetch authentication so signed tags and attest notes can be fetched on GitHub-hosted runners", + "kind": "bug_fix", + "state": "implementing", + "base_commit": "7373d124ebb3823c1f7f19651dfffe4d7ed83f51", + "created_at": 1785303663, + "updated_at": 1785303960, + "affected_specs": [ + "aps-cli" + ], + "affected_paths": [ + ".github/workflows/release.yml", + "Scripts/test-release-distribution.sh", + "Scripts/test-release-provenance.sh", + ".specsync/change-sequence.json" + ], + "no_spec_change": true, + "no_spec_change_rationale": "This repairs CI authentication plumbing without changing the aps CLI contract or release artifact semantics.", + "acceptance_criteria": [ + "Release fetches use one checkout-managed credential, the release distribution contract rejects manual duplicate Authorization headers, and a v1.1.0 workflow dispatch passes provenance." + ], + "selected_artifacts": [ + "context", + "testing", + "tasks" + ], + "dependencies": [], + "answers": { + "architecture_risk": "no", + "public_contract": "no" + } +} diff --git a/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/tasks.md b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/tasks.md new file mode 100644 index 0000000..32b1191 --- /dev/null +++ b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/tasks.md @@ -0,0 +1,11 @@ +--- +change: CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be +artifact: tasks +--- + +# Tasks + +- [x] Replace manual Authorization headers with checkout-managed credentials. +- [x] Add a regression contract for the release authentication configuration. +- [x] Run the local verification lane. +- [ ] Merge the repair and dispatch the existing v1.1.0 tag. diff --git a/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/testing.md b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/testing.md new file mode 100644 index 0000000..360ffc0 --- /dev/null +++ b/.specsync/changes/CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be/testing.md @@ -0,0 +1,12 @@ +--- +change: CHG-0064-fix-release-workflow-fetch-authentication-so-signed-tags-and-attest-notes-can-be +artifact: testing +--- + +# Testing + +- `Scripts/test-release-distribution.sh` asserts both release checkouts persist + their job credential and rejects manual Authorization-header construction. +- `fledge lanes run verify` exercises the release distribution contract. +- A `workflow_dispatch` run for `v1.1.0` proves tag, default-branch, and attest + note fetches succeed on the GitHub-hosted runner. diff --git a/Scripts/test-release-distribution.sh b/Scripts/test-release-distribution.sh index 56fc343..e4d0d3e 100755 --- a/Scripts/test-release-distribution.sh +++ b/Scripts/test-release-distribution.sh @@ -32,6 +32,8 @@ grep -Fq 'RELEASE_TAG: ${{ needs.provenance.outputs.tag }}' "$workflow" grep -Fq 'APS_VERSION="${RELEASE_TAG#v}"' "$workflow" grep -Fq 'test "$RELEASE_TAG" = "v$(cat VERSION)"' "$workflow" grep -Fq 'test "$("$BIN_DIR/aps" --version)" = "${RELEASE_TAG#v}"' "$workflow" +test "$(grep -c 'persist-credentials: true' "$workflow")" -eq 2 +! grep -Fq 'authorization="$(printf' "$workflow" grep -Fq 'fetch aps-linux-x86_64-portable.tar.gz' "$formula_workflow" grep -Fq 'Scripts/render-homebrew-formula.py' "$formula_workflow" grep -Fq 'Homebrew formula updates require a stable SemVer tag' "$formula_workflow" diff --git a/Scripts/test-release-provenance.sh b/Scripts/test-release-provenance.sh index 6ddaf5d..98705ef 100755 --- a/Scripts/test-release-provenance.sh +++ b/Scripts/test-release-provenance.sh @@ -272,9 +272,10 @@ if [[ "$exact_range_count" -ne 2 ]] || grep -Fq '^..' "$workflow"; then echo "release provenance contract: Attest must verify the exact commit with ^!" >&2 exit 1 fi -persist_count="$(grep -Fc 'persist-credentials: false' "$workflow")" -if [[ "$persist_count" -ne 4 ]]; then - echo "release provenance contract: every checkout must disable persisted credentials" >&2 +persist_disabled_count="$(grep -Fc 'persist-credentials: false' "$workflow")" +persist_enabled_count="$(grep -Fc 'persist-credentials: true' "$workflow")" +if [[ "$persist_disabled_count" -ne 2 ]] || [[ "$persist_enabled_count" -ne 2 ]]; then + echo "release provenance contract: only authenticated fetch jobs may persist credentials" >&2 exit 1 fi # shellcheck disable=SC2016 @@ -284,14 +285,14 @@ grep -Fq "environment: release" "$workflow" grep -Fq "git merge-base --is-ancestor" "$workflow" grep -Fq "Verify remote tag after publication" "$workflow" token_env_count="$(grep -Fc 'GITHUB_TOKEN: ${{ github.token }}' "$workflow")" -if [[ "$token_env_count" -ne 4 ]]; then - echo "release provenance contract: every explicit fetch step must receive the job token" >&2 +if [[ "$token_env_count" -ne 1 ]]; then + echo "release provenance contract: only remote tag verification needs an explicit job token" >&2 exit 1 fi # shellcheck disable=SC2016 authenticated_fetch_count="$(grep -Fc 'http.https://github.com/.extraheader=AUTHORIZATION: basic ${authorization}' "$workflow")" -if [[ "$authenticated_fetch_count" -ne 6 ]]; then - echo "release provenance contract: every private-repository fetch must use command-scoped authentication" >&2 +if [[ "$authenticated_fetch_count" -ne 1 ]]; then + echo "release provenance contract: only post-publication verification uses command-scoped authentication" >&2 exit 1 fi # shellcheck disable=SC2016