[2026-02-23-1] で紹介した masutaka/actions では、jactionlint などの CLI バイナリを、各ツールの GitHub Releases からダウンロードして使っています。

ダウンロードしたバイナリは、SHA256 チェックサムと gh attestation verify の両方で検証しています。今回は、その方法と理由を書いていきます。

SHA256 チェックサムと attestation で検証する

masutaka/actions では、jactionlint をこのように検証しています。

🔗 .github/workflows/gh_action_lint.yml

- name: Install jactionlint
  env:
    GH_TOKEN: ${{ github.token }}
    REPO: jdx/jactionlint
    ASSET: "*_linux_amd64.tar.gz"
    TAG: v2.0.2
    ASSET_SHA256: 026bce6d0ef196f5b22bf43acb1348d47c5d0a5ae61d6ec3bdba1229a823161b
    SIGNER_WORKFLOW: jdx/jactionlint/.github/workflows/release.yaml
    EXECUTABLE: jactionlint
  run: |
    gh release download "$TAG" \
      --repo "$REPO" \
      --pattern "$ASSET" \
      --output asset.tar.gz
    # 正規の release workflow がそのタグからビルドした成果物であることを検証する
    gh attestation verify asset.tar.gz \
      --repo "$REPO" \
      --signer-workflow "$SIGNER_WORKFLOW" \
      --source-ref "refs/tags/$TAG"
    # ピンした時点から中身が変わっていないことを検証する
    echo "${ASSET_SHA256}  asset.tar.gz" | sha256sum -c -
    tar -xzf asset.tar.gz "$EXECUTABLE"

GitHub Releases からアセットをダウンロードし、gh attestation verify と sha256sum -c の両方の検証を通ったものだけを使います。

--signer-workflow は workflow のパスしか確認せず、ref やコミットまでは固定しません。そのため --source-ref で、記録したタグからビルドされたものに限定しています。

gh attestation verify で検証する attestation は、配布元が release workflow で生成している必要があります。配布元の話は、Songmu さんの記事が詳しいです。

🔗 GitHub Actions上で証明書を付与するサプライチェーン構築 | おそらくはそれさえも平凡な日々

拙作の github-nippou も、この記事を読んで actions/attest に移行しました。

ちなみに --signer-workflow には、--repo と別のリポジトリが指定されることもあります。例えば ghalint は、release.yaml から呼ばれる reusable workflow の suzuki-shunsuke/go-release-workflow/.github/workflows/release.yaml で署名されます。

なぜ両方で検証するのか

SHA256 チェックサムと gh attestation verify では、検証対象が違うからです。

  • SHA256 チェックサム
    • 「チェックサムを記録した時点から中身が変わっていない」ことを検証する
    • CI を通さずに手元でビルドしたバイナリでも、その値を記録すれば一致してしまう
  • gh attestation verify
    • 「正規の release workflow が、そのタグからビルドした成果物か」を検証する
    • ただし、immutable release でなければタグは付け替えられる。書き込み権限を奪われ、改変したコミットにタグを付け替えて再ビルドされると通ってしまう
    • 同じタグのまま再ビルドされ、アセットを上書きされても通ってしまう。後からビルド時の依存が汚染された場合などが該当する

公式に併用を勧める情報は見つけられませんでしたが、補強になりそうな情報はありました。

GitHub Docs の Artifact attestations には、“artifact attestations are not a guarantee that an artifact is secure” とあります。

また、SLSA (Supply-chain Levels for Software Artifacts)という、ソフトウェアサプライチェーンの安全性を段階的に定めた OpenSSF のフレームワークがあります。gh attestation verify で検証する build provenance も、SLSA の形式です。

その SLSA の Threats & mitigations では、ビルド時の依存が汚染される脅威は SLSA の対象外とされています。その対策として挙げられているのが、依存を digest で固定することです。

You may be able to mitigate this threat by pinning your build dependencies, preferably by digest rather than version number.

配布元に向けた記述ですが、利用者がチェックサムで固定するのも同じ考え方だと思います。

ただし、チェックサムで防げるのは、記録した後の差し替えだけです。最初のリリースの時点で依存が汚染されていれば、チェックサムも attestation も通ってしまいます。

個人的には、gh attestation verify が記録したチェックサムと一致するかまで確認してくれるとうれしいのですが、今のところは 2 つの方法で検証しています。

build provenance がなければ release attestation を使うと良いのかも

ここからは、この記事を書く途中で知った話です。手元で試しただけで、masutaka/actions では使っていません。

gh attestation verify は build provenance を検証します。配布元が生成していなければ使えません。

例えば、Terraform 本体や provider のバージョン指定を一括で更新する tfupdate は、リポジトリの Attestations ページから分かるように、build provenance を生成していません。

そのため、gh attestation verify は 404 で失敗します。

$ gh attestation verify asset.tar.gz --repo minamijoyo/tfupdate
Error: HTTP 404: Not Found (https://api.github.com/repos/minamijoyo/tfupdate/attestations/sha256:559bc7c6c15496682a3207cbdc4e6327de252f5f1a4348f5098027d6bcc4ebe2?per_page=30&predicate_type=https%3A%2F%2Fslsa.dev%2Fprovenance%2Fv1)

その場合でも、immutable releases を有効にしたリポジトリなら release attestation が使えます。有効にした後に公開したリリースに、GitHub が自動で生成してくれるものです。

release attestation では「このアセットは、このタグのリリースに含まれていたものか」を検証できます。Attestations ページでは確認できないので、リリースページ を見て immutable release かどうかを確認します。tfupdate v0.10.2 は immutable release なので、gh release verify-asset で検証できます。

$ gh release verify-asset v0.10.2 asset.tar.gz --repo minamijoyo/tfupdate
Calculated digest for asset.tar.gz: sha256:559bc7c6c15496682a3207cbdc4e6327de252f5f1a4348f5098027d6bcc4ebe2
Resolved tag v0.10.2 to sha1:9d61d78d73e1325629e46c9e92d713ab258b50cd
Loaded attestation from GitHub API

✓ Verification succeeded! asset.tar.gz is present in release v0.10.2

この検証方法は、GitHub Docs の Verifying the integrity of a release で紹介されています。

release attestation では、正規の workflow でビルドされたかまでは分かりません。それでも、ダウンロードしたファイルがそのリリースのアセットと同じものかは確認できます。

immutable release ならタグの付け替えもアセットの上書きもできないので、「なぜ両方で検証するのか」で書いたタグの付け替えの抜け道も塞がります。

まとめ

GitHub Actions などで使う CLI バイナリは、SHA256 チェックサムと gh attestation verify の両方で検証すると良さそうです。チェックサムで中身が変わっていないことを、attestation で正規の release workflow がビルドしたことを、確かめられます。

配布元が build provenance を生成していなくても、immutable release なら gh release verify-asset が使えそうです。こちらは手元で試しただけです。

3 つの検証を比較すると、こうなります。

検証 確認できること 配布元に必要なもの
SHA256 チェックサム チェックサムを記録した時点から中身が変わっていない なし(checksums.txt があると、記録する値を照合できる)
gh attestation verify 正規の release workflow がそのタグからビルドした build provenance の生成
gh release verify-asset そのタグのリリースに含まれていた immutable releases の有効化

ほかに、mise-action でインストールする方法もあります。aqua backend のツールなら、mise.lock にチェックサムが記録され、attestation があればその情報も記録されます。

AI 時代は、こうした多重防御が特に大事になったはずです。どなたかの参考になれば幸いです。

参考情報