diff --git a/.github/workflows/update-release.yml b/.github/workflows/update-release.yml index cfd5379..80771e5 100644 --- a/.github/workflows/update-release.yml +++ b/.github/workflows/update-release.yml @@ -20,7 +20,7 @@ # bundle and schema to the draft (draft-first because published # releases freeze their assets), # 4. confirms origin's refs and the draft's assets before the managed -# publish stage attests every asset (attestation.jsonl; public +# publish stage attests every asset (attestation.json; public # repositories only, by fleet policy) and flips the release live. # When the draft is published, GitHub binds it to the existing # vX.Y.Z tag, so the release lands on the packaged commit. @@ -38,7 +38,7 @@ # Finish a dead run by re-running its FAILED jobs. The same steps by hand # ( is the draft release's target commitish; a hand-packaged # commit carries no workflow-run trailer, and a hand-finished release no -# attestation.jsonl - both need the workflow's identity): +# attestation.json - both need the workflow's identity): # git checkout # bun install --frozen-lockfile --ignore-scripts && bun run build # TAG=vX.Y.Z GITHUB_SHA= bun .github/scripts/release-pipeline.ts package @@ -159,16 +159,21 @@ jobs: run: GITHUB_SHA="$SOURCE_SHA" bun .github/scripts/release-pipeline.ts verify # Publishing freezes the asset list, so an incomplete one must stop # the pipeline here, while the release is still a draft (v2.0.0 - # shipped assetless exactly this way). attestation.jsonl is the - # managed publish stage's asset: attached after this job, replaced on - # its reruns - ignore it rather than racing it. + # shipped assetless exactly this way). attestation.json is the managed + # publish stage's asset, attached after this job and replaced on its + # reruns, so the check ignores it. A legacy attestation.jsonl (the + # name before the template renamed it) is stale provenance that stage + # no longer cleans up, so it is deleted here before the check. - name: Confirm the release carries both packaged assets env: GH_TOKEN: ${{ github.token }} TAG: ${{ inputs.tag }} run: | + if gh release view "$TAG" --json assets --jq '.assets[].name' | grep -qx 'attestation.jsonl'; then + gh release delete-asset "$TAG" attestation.jsonl --yes + fi assets="$(gh release view "$TAG" --json assets \ - --jq '[.assets[].name | select(. != "attestation.jsonl")] | sort | join(" ")')" + --jq '[.assets[].name | select(. != "attestation.json")] | sort | join(" ")')" expected="index.js settings.schema.json" if [ "$assets" != "$expected" ]; then echo "::error::release $TAG carries assets [$assets], expected [$expected]; re-run package-release before anything publishes." diff --git a/SECURITY.md b/SECURITY.md index 9d07641..94ad024 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -31,7 +31,7 @@ This action holds a repository-admin token and writes repository settings, so th - Settings escalation. A crafted settings file should never be able to touch a repository or setting it does not declare, nor bypass the preflight barrier or the required-sections policy. - Supply chain. Every ref a `uses:` pin can name - the `vX.Y.Z` release tags and the moving major - points at a packaged commit parented on the audited release commit, produced by the release workflow run named in its provenance message; main carries no executable bundle. The release-tags ruleset freezes version tags for everything except deliberate repository-admin action (the bypass exists for repair; the release workflow itself never moves a version tag, reruns verify the existing one byte-for-byte instead). A packaged commit whose bundle a rebuild of its parent's src/ does not reproduce is a vulnerability. The release pipeline also attests every release asset (`lib/index.js` and `lib/settings.schema.json`) in one build-provenance attestation while the repository is public (GitHub offers no attestations elsewhere): fetch either from the `vX.Y.Z` tag or the release assets, then check it with `gh attestation verify -R vivswan/github-settings-as-code` (add - `--signer-workflow vivswan/github-settings-as-code/.github/workflows/release.yml` to also pin the producing workflow), or without the attestations API via `--bundle` against the `attestation.jsonl` release asset. A release finished by hand carries no attestation at all, and its packaged commit no workflow-run trailer: both need the workflow's OIDC identity. + `--signer-workflow vivswan/github-settings-as-code/.github/workflows/release.yml` to also pin the producing workflow), or without the attestations API via `--bundle` against the `attestation.json` release asset. A release finished by hand carries no attestation at all, and its packaged commit no workflow-run trailer: both need the workflow's OIDC identity. Fixes ship in the next release and are not backported; upgrade the `uses:` pin to pick them up.