これは何?
先日こちらのツイートに影響を受け、自分の公開しているリポジトリのReleasesに対して改ざん対策を行った。
ツイートとツイートで引用されているリポジトリ12を参考にさせていただきつつ、自分でもいろいろ調べた結果をまとめた。
想定する読者
GitHubのReleaseページのセキュリティ対策を行いたい開発者
(見分ける方法は元ツイをご覧ください)
GitHubでReleaseする実行ファイルの改ざん防止が必要な背景
セキュリティ的な観点だけで言えば、ソースが公開されているソフトウェアに対しては
- ソースをチェック
- 自前でビルド3
すべきだとは思う。
しかし、自前でビルドするのは手間がかかるし、非エンジニアの方に使ってもらうハードルが高くなる等のデメリットもある。
そのため、筆者を含めGitHubリポジトリのReleasesページに配置されている実行ファイル(以後、artifactと記載)をダウンロードして使うこともあると思う。
この際に注意すべき点として、GitHubのReleasesページから悪意のある実行ファイルをダウンロードさせる攻撃4が存在していることだ
これには、
- リポジトリのソース自体が汚染されてしまい、悪意のあるコードを含んだartifactが配布されてしまうケース
- 2024年のXZ Utilsのように開発者に悪意があるケース5
- アカウントが乗っ取られてしまうケース(本記事で紹介するコミットとtagに
- artifactだけを改ざんするケース(本記事のメインの対象)
が考えられる。
そのため、Releasesページから後者の危険がないことを確認できるようにすることはユーザに安心して利用してもらう上で重要である。
紹介するGitHubリポジトリの設定概要
- Release自体の方式変更 --> Release artifactの差し替え防止
- Immutable Releasesを利用する
- Releaseの作者をGitHub Actionsにする
- 署名の付与 --> GitHub アカウント乗っ取り時の不正リリース防止
- Release Tagに署名をうつ
- commitに署名をうつ
これにより、
実施前イメージ6↓
実施後イメージ↓
Release方式変更
Immutable Releaseを利用する
リポジトリのSettings --> General --> Releases Enable relase immutabilityを有効にする
この設定をすることで
- 一度作成したRelease tagは特定のcommitにロックされるため、変更/削除負荷
- Releaseに載せるアセットの変更や削除不可
- Release attestationsファイルが自動作成される7
と、artifactが改ざんに強い状態になる。
Releaseの作成者をGitHub Actionsにする
Immutable Release単体ではartifactが差し替えられていないことしか保証できない。
そのため、少なくとも Release時点でのリポジトリ状態に基づいてビルドされたartifactであることが分かる。
release tagを手動で作り、これをトリガーにGitHub Actionsを実行してartifactを自動アップロードする形から、
name: Release
on:
release:
types: [created]
jobs:
build-and-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Make build script executable
run: chmod +x ./build.sh
- name: Build JAR file
run: ./build.sh
- name: Create Release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
with:
files: ./build/airis-burp.jar
generate_release_notes: true
draft: false
prerelease: false
release tagのpushをローカルから実行し、これをトリガーにしてRelease自体もGitHub Actionsで作成する形に変更した。
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- run: chmod +x ./build.sh
- run: ./build.sh
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: jar
path: build/airis-burp.jar
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: jar
path: build
- run: gh release create "$GITHUB_REF_NAME" build/airis-burp.jar
env:
GH_TOKEN: ${{ github.token }}
softprops/actions-gh-releaseを使うと以下のようなエラーが発生した。
softpropsが先にreleaseを作ってしまい、これがimmutableのため変更できないのが原因だったため、直接gh release create2したところうまくいった。
Error: Failed to upload release asset airis-burp.jar. received status code 422 Cannot upload assets to an immutable release. undefined
署名の付与
commitやtagの署名は、GitHub の UI 上の表示ではなく、対応する秘密鍵を保持している主体が実際にそのcommitやtagを作成したことを保証する。
そのため、GitHub アカウントが乗っ取られても秘密鍵が漏洩していなければ、署名付きの不正なリリースは作成できない。
SSHの秘密鍵と公開鍵を作成
Release tagとcommitに署名をつける手順はまとめて実施した。
既存のGitHubログイン用のSSH鍵を使いまわすことも考えたが、セキュリティの権限分離の観点から再作成することにした。
ちゃんとやるなら、YubiKeyのような物理鍵を使ったり、GPGを使うほうが良いかもしれない。
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_signing -C "git-signing"
公開鍵をGitHubに登録する
GitHubの個人設定からAccess --> SSH and GPG keysから追加できる。
git configの設定
- ssh鍵を使って署名を作成すること
- 使う公開鍵のPATH
- commitに署名をつけること
- tagに署名をつけること
を設定に追加した。
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519_signing.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
これだけだとうまくいかなかったのだが、~/.config/git/配下に信頼する鍵を記載したファイルを作成し、このPathをgit configで設定したらうまく行った。
mkdir -p ~/.config/git
touch ~/.config/git/allowed_signers
cat ~/.config/git/allowed_signers
GitHubのユーザ名 ssh-ed25519 公開鍵....
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
# tagに署名がついたかチェック
git tag -a v0.1.8 -m "release v0.1.8"
git push origin v0.1.8
gh release verify v0.1.8
Resolved tag v0.1.8 to sha1:11c52920eebb3d411e9b62eeb4b5d199bd97c001
Loaded attestation from GitHub API
✓ Release v0.1.8 verified!
Assets
NAME DIGEST
airis-burp.jar sha256:0257e3d2c434595b5ee1231ce5afe9ce26bc94902d301991f87eb6...
# 署名設定前のRelease tag
gh release verify v0.1.0
no attestations for tag v0.1.0 (sha1:a2dda180acd89b64b5198bd947696c9852355198)
# commitに署名がついたか確認
commit c3c0fad9548596f990291f26d8bf5d4eeab5fda5
Good "git" signature for sigma with ED25519 key SHA256:xxxxxxxxxxxxxxxxxx
Author: sigma
Date: Mon Feb 2 23:28:55 2026 +0900
chore: fix release.yml
# 署名をつける前のcommit
git log --show-signature
commit 57036f7504156934612148e1bd4c2718a2eee7ec
Author: sigma
Date: Thu Aug 28 20:06:11 2025 +0900
first commit
一応Webからの見え方も再掲
おまけ: ビルドの再現性を高めるためのNix Flake
今回実験台に利用したリポジトリはDockerを使ってビルドしているので、Nix Flakeを使ってビルドしたほうがビルドの再現性が担保されるのでより良いと思う。
HaskellとDenoの一部のみにしかNix Flake導入できてないので本記事ではこれ以上触れないがみんなNix Flake使っていこうぜ!
Reference
-
https://www.reddit.com/r/Scams/comments/1b8ckap/cryto_developer_metamask_scam/ 偽のリクルータ経由で誘導されたリポジトリをclone & runしたら情報抜かれた話もあるので、リポジトリ自体が怪しくないかを確認すべし ↩
-
https://www.trendmicro.com/ja_jp/research/25/b/lumma-stealers-github-based-delivery-via-mdr.html ↩
-
https://ja.wikipedia.org/wiki/XZ_Utils%E3%81%AE%E3%83%90%E3%83%83%E3%82%AF%E3%83%89%E3%82%A2 ↩
-
commitに署名がついているのは
gh prを使って操作したmerge commitが署名されているだけで開発時のcommitには署名は付与されていなかった。 ↩ -
https://github.blog/changelog/2025-10-28-immutable-releases-are-now-generally-available/ ↩



