ここ数ヶ月でサプライチェーン攻撃に関連していくつかベストプラクティスが出ていたので、GitHubのリポジトリに適用しておいたほうがいいものをまとめた。
被害を受けないために
Dependabotにcooldownを設定する
過去のサプライチェーン攻撃では、ほとんどは問題のあるリリースが公開されてから数時間で発見されているので、自分のリポジトリが汚染されないためにリリースから一定期間はアップデートを保留するという手段が取られるようになったと記憶している。もともとRenovateには minimumReleaseAge オプションがあったのだが、Dependabotでも cooldown オプションが使えるので設定する。
このオプションを設定しても、上の記事中に
Key benefits
- Stay responsive to critical security patches.
とあるように脆弱性の修正は cooldown の設定よりも優先してアップデート対象となるらしいので、ある程度日数を開けても問題ないと思う。設定例は以下の通り。
version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly cooldown: default-days: 5
上記では、サプライチェーン攻撃に関していえばパッチバージョンだから安全という訳でもないので default-days を設定しているが、この他にもマイナーバージョンやパッチバージョンごとに期間を設定できる。
Actionsのバージョンをハッシュで強制する
GitHub ActionsではGitのタグを使ってバージョンを特定しているので、もともと安全だったタグが攻撃者によって悪意のあるコミットに向けられてしまって汚染されることがある。こういった攻撃を防ぐため、GitHubは公式にSecure use reference#Using third-party actionsで
You can help mitigate this risk by following these good practices:
- Pin actions to a full-length commit SHA
のようにハッシュによるバージョン参照を推奨していたのだが、2025年8月にハッシュで参照することを強制する Require actions to be pinned to a full-length commit SHA オプションが追加された。
公式がハッシュによる参照を推奨しているので上記のオプションも有効にするといいと思うけれど、このオプションはワークフローが依存するすべてのアクションでハッシュ指定を強制するので、外部のアクションにタグ指定がひとつでもあるとエラーになる。例えば筆者は一部のワークフローでgovulncheck-actionを使っているのだけど、2025年10月時点では govulncheck-action にはタグを使ってバージョンを参照するコードが含まれているので、上記オプションを有効にするとエラーとなってワークフローを実行できないので状況を確認しつつ有効にしていくといい。
被害を広げないために
CodeQLで検査を行う
CodeQLはGitHubが公式に提供するコードスキャナで、2025年10月時点ではCodeQLの設定は以下2通りの方法がある。
CodeQLはリポジトリの設定画面から Advanced Security タブの Code Scanning セクションで有効にできる。2025年10月時点では、上記で Require actions to be pinned to a full-length commit SHA オプションを設定していると
Code scanning with GitHub Actions is not available for this repository
と警告が出てセットアップを行えない状態となるが、その状態でも自分でワークフローを記述すれば高度なセットアップとしてCodeQLが動作する。高度なセットアップでは以下のようなGitHub Actionsのワークフローとして記述する。
name: "CodeQL Advanced" on: push: branches: - main pull_request: branches: - main schedule: - cron: '17 2 * * 6' jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest permissions: security-events: write packages: read actions: read contents: read strategy: fail-fast: false matrix: include: - language: actions build-mode: none - language: go build-mode: autobuild steps: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}"
ワークフローとして記述する内容については以下のドキュメントにまとまっている。
コード署名を必須にする
名前とアイコンだけでは不安があるのでコード署名も有効にできればより良いが、これは過去に記事を書いた。
Immutable Releaseを設定する
いちどGitHub Releasesで公開したリリースを変更できなくするオプションも追加された。
これを有効にした状態でリリースを作成すると、リリースした内容を変更できなくなる
このオプションを使うには、リポジトリの設定で General タブにある Release セクションで Enable Release Immutability を有効にする。Immutable Releasesを有効にした状態でリリースを作成すると、該当するリリースには鍵付きのアイコンに Immutable というラベルが付与される。
ただし不変となるのはコミットの内容とリリース成果物だけで、リリースのタイトル、リリースの説明文、プレリリース状態は依然として変更可能となっている。
Immutable Releasesでアセットを追加する
上記の通り、Immutable Releasesが有効になっていると、リリースにファイルのアップロードが行えなくなる。成果物をリリースに含めたい場合にどうするのかといえば、バージョンをいちどドラフトで作成して、成果物のアップロードが完了してから正式リリースに変更する手順を踏む必要がある。GitHub Releasesで各バージョンの状態は
- Draft
- Pre-release
- Release
の3つで推移するので、手動でリリース成果物を追加した後で Pre-release または Release に変更すればいい。
手動またはGitHub CLI等で人間がアップロードする場合はそれでいいのだけれども、GitHub Actionsのワークフローを使って成果物のアップロードを行っていた場合は問題がある。具体的には以下のワークフローについて
on: release: types: - published jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: make zip - uses: actions/upload-artifact@v4 with: name: artifacts path: "*.zip" deploy: needs: build permissions: contents: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/download-artifact@v5 with: path: artifacts merge-multiple: true - name: release run: | gh release upload "$TAG" artifacts/*.zip env: TAG: ${{ github.ref_name }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
このとき、Immutable Releasesが有効になっていると gh release upload によるアップロードが行えなくなる。上にも書いたようにドラフトであれば成果物の追加が行えるけれども、ドラフトの状態でワークフローをトリガーするイベント が、少なくとも今の時点では存在しない*1。
ではどうするのかというと、公式ドキュメントにUsing immutable releases and tags to manage your action’s releasesがあって、このドキュメントではGitのタグとして作る方法が紹介されていたのでこの方法でリリースを行うことにした。ワークフローの差分は以下の通り。
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ef97f43..c1db329 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,9 +1,9 @@ name: Release on: - release: - types: - - published + push: + tags: + - v[0-9]+.[0-9]+** jobs: build: @@ -47,7 +46,10 @@ jobs: merge-multiple: true - name: release run: | - gh release upload "$TAG" artifacts/*.zip + gh release create "$TAG" \ + --title="$TAG" \ + --generate-notes \ + --draft artifacts/*.zip env: TAG: ${{ github.ref_name }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
リリースを行いたいときGitのタグをプッシュすれば、ワークフローによってドラフトのリリースが作られる。このとき、上記ワークフローではリリースノート生成と成果物のアップロードまで行うように変更しているが、リリースノートの内容は後からGitHubのUIで変更できるし、リリースと関連付けられていないタグなら削除できるので、タグ付けを間違えた場合はドラフトリリースとGitのタグそのものを削除してやり直せばいい。
ただ、誤って先にGitHubのUIでリリースとして作ってしまった場合は(もう消せないしアップロードもできないので)新しくタグを作るしかなくなってしまうけれども、Immutable Releasesを使わないよりはメリットが大きいだろうから諦める。