先日 tj-actions/changed-files などの人気の GitHub Actions のセキュリティインシデントがありました (CVE-2025-30066)。

https://zenn.dev/shunsuke_suzuki/articles/tj-actions-incident-2025

自分は OSS の開発者として様々な OSS を公開しており、こういったセキュリティインシデントは他人事ではありません。
そこでこの 1 ヶ月弱セキュリティ周りを見直し、かなり改善することが出来ました。

本記事では GitHub のセキュリティを改善する方法について紹介します。
主なターゲットしては自分のような OSS の開発者ですが、 GitHub を使っている方全てに参考になる内容かなと思います。

皆さんが本記事を参考にセキュリティを改善し、セキュリティインシデントを未然に防ぐことが出来れば幸いです。

先日登壇した GitHub Actions 関連の資料

先日 2025-03-11 に GitHub Actions のベストプラクティスについて登壇しました。
資料を公開しているのでこちらもご参照ください。

https://findy.connpass.com/event/346633/

https://suzuki-shunsuke.github.io/slides/github-actions-best-practice-2025

セキュリティ周りでは主に以下のようなことを話しました。

先日のインシデントでは tag の書き換えが行われましたが、これは action のバージョンを hash で固定しているだけでかなりリスクを軽減できたはずです。

本記事では上記の内容に加えて更なる対策を紹介します。

  • リポジトリの整理
    • 不要な fork の削除
    • 不要なリポジトリの archive
    • 不要なリポジトリを別の org に移行
  • サードパーティの OAuth App や GitHub App の整理
  • Personal Access Token (PAT) や GitHub App を極力使わない
    • 必要な場合は、本当に必要か見直す
    • GitHub App の用途の整理
    • GitHub App を autofix.ci に置き換える
    • Homebrew tap や Scoop manifest などを CI で生成してローカルからリリース
    • 他のリポジトリの GitHub Pages へのリリースを pull 型方式にする
    • 不要な GitHub App や PAT の削除
  • gh auth refresh -r で不要な権限の削除
  • GitHub App から不要な権限の削除
  • GitHub App を不要なリポジトリから uninstall
  • 用途に基づく GitHub App の分離
  • Rulesets の設定
    • 全ての branch で署名を必須化
    • 全ての tag の作成を禁止 (organization admin や repository admin のみ bypass を許可)
    • 全ての tag の更新・削除を禁止
  • secret の管理
    • Repository Secrets を Environment Secret に置き換えてアクセス制限
    • secret を laptop から消す
  • Renovate の minimumReleaseAge を設定
  • public repository で 極力 pull_request_target を使わない

リポジトリの整理

まず、自分は大量にリポジトリを作っているのでそれの整理をしました。
大量にリポジトリがあると、一括で修正をしたいときに無駄に時間がかかりますし、修正すべき箇所を検索したりする際にノイズにもなります。

  • 不要な fork の削除
  • 不要なリポジトリの archive
  • 不要なリポジトリを別の org に移行

自分は検証とかのために test-* みたいなリポジトリを作ることがあるのですが、新しく GitHub Organization を作ってそれらを全部そこに移しました。
今後同じようなリポジトリを作るときもこの org 配下に作ることになるかと思います。

OSS に PR を投げる際に fork を作りますが、 PR がマージされた後も fork が放置されることがあるので、そういった fork を削除しました。
簡単なスクリプトで PR のない fork の一覧を生成し、一部を除いて一括で削除しました。

サードパーティの OAuth App や GitHub App の整理

不要な OAuth App などは削除するようにしましょう。

PAT や GitHub App を極力使わない

PAT や GitHub App は便利ですが、セキュリティリスクにも繋がります。
実際 tj-actions のインシデントも PAT の流出によるものです。
PAT や GitHub App が本当に必要か見直しましょう。

使うとしたら優先順位的には以下の通りになります。
GitHub Actions token 以外を使っている箇所ではより安全な token に置き換えられないか検討しましょう。

  1. GitHub Actions Token
  2. GitHub App
  3. Fine-grained PAT
  4. Classic PAT

まずは CI などで PAT や GitHub App を使っている箇所を検索し、用途を洗い出しましょう。
そして用途ごとに見直し、 PAT や GitHub App なしで実現できないか検討しましょう。

自分の場合、以下のような改善をしました。

  • public repository の PR のコード修正を GitHub App から autofix.ci に置き換え
  • GoReleaser で生成した Homebrew tap や Scoop manifest などを CI でリリースする代わりにローカルからリリース
  • 他のリポジトリの GitHub Pages へのリリースを pull 型方式にする

これらの改善により、 PAT の利用を完全にやめることが出来ましたし、 GitHub App の利用もほぼやめることが出来ました (残っている App はインストール範囲や権限が限定的なので悪用されてもリスクが低い)。
GitHub App や PAT の利用を見直したら、不要なものを削除しましょう。

public repository の PR のコード修正を GitHub App から autofix.ci に置き換え

autofix.ci も GitHub App だろと言われればそのとおりですが、 autofix.ci であれば fork からの PR であっても安全にコードを修正できます。

https://autofix.ci/

GoReleaser で生成した Homebrew tap や Scoop manifest などを CI でリリースする代わりにローカルからリリース

自分は Go を使って多くの CLI を OSS として公開しており、 CI で GoReleaser を実行してリリースしています。
Homebrew tap や Scoop Bucket, Winget Manifest は別のリポジトリで管理されているため、従来は GitHub App を使っていました。
また、 Winget の場合リリースのたびに microsoft/winget-pkgs に PR を投げなければならず、このために PAT を使っていました。
しかし、これはセキュリティ的によろしくないため、 GoReleaser で生成したファイルを GitHub Actions Artifacts に upload し、 workflow 完了後にローカルから push する方式に切り替えました。
GoReleaser の Homebrew, Scoop, Winget などの設定には skip_upload という設定があり、 これを true にするとファイルを生成するだけになり、自分で push できるようになります。
e.g. https://goreleaser.com/customization/homebrew/

  1. ローカルから tag を push
  2. workflow が実行される
  3. workflow の完了を gh run watch で待つ
  4. workflow で GoReleaser が実行され、ファイルが生成される
  5. workflow で GoReleaser が生成されたファイルを GitHub Actions Artifacts に upload
  6. workflow が完了したら GitHub Actions Artifacts から file をダウンロードし、各リポジトリに push

これらを自動化する簡単なスクリプトを書きました。
現状自分以外が使うことはあまり想定していなくて、汎用的な作りになっていません。

https://github.com/suzuki-shunsuke/rgo

このアプローチのもう一つ良い点は、 commit に署名ができるようになったことです。
CI で GoReleaser を使って commit を生成する場合は commit に署名をするのが困難でした。
このアプローチではローカルから commit を生成するため、問題なく署名が出来ます。
そのため、対象リポジトリの全 branch で署名を必須化することができるようになりました。

他のリポジトリの GitHub Pages へのリリースを pull 型方式にする

github-comment, tfcmt, tfaction などは GitHub Pages を公開しているリポジトリとドキュメントのコードを管理しているリポジトリが別になっています。

e.g.

従来はドキュメントを管理しているリポジトリから GitHub App を使ってドキュメントをリリースしていました。
しかし、これはセキュリティ的によろしくないため、 GitHub Pages を公開しているリポジトリで schedule event で workflow を実行しドキュメントを定期的に更新する方式に切り替えました。
現状 30 分毎に実行しています。すぐに更新したければ workflow_dispatch で更新もできます。
workflow が失敗したら専用の issue が open され、問題に気付けるようにしています。

PAT や GitHub App の permissions や repositories の見直し

PAT や GitHub App がどうしても必要な場合、 permissions や repositories を見直し、不要な権限を削除しましょう。

Secret 管理の見直し

  • マシン上の secret の削除
  • Repository Secrets を Environment Secret に置き換えてアクセス制限
  • 不要な GitHub Secrets の削除

普段使っているマシン上に平文のまま雑に扱われている secret の管理を見直しましょう。
.bashrc や .zshrc, direnv の .envrc などに PAT がそのまま書かれている場合、流出の恐れがあるので見直しましょう。
GitHub App の Private Key なども削除したほうが良いでしょう。

GitHub の Repository Secrets で管理する場合、可能であれば Environment Secrets に置き換えてアクセス制限をかけるようにしましょう。

GitHub App や PAT の利用を見直すことで不要になった GitHub Secrets は削除しましょう。

Rulesets の設定

ほぼすべてのリポジトリに API で一括で Rulesets を設定しました。

  • 全ての branch で署名を必須化
  • 全ての tag の作成を禁止 (organization admin や repository admin のみ bypass を許可)
  • 全ての tag の更新・削除を禁止

新規でリポジトリを作る際はスクリプトを実行することで上記の Rulesets を含む基本的な設定をするようにしています。
特に重要なのは tag の作成・更新・削除の禁止です。
これによりセキュリティリスクをかなり軽減できます。
tag の作成も admin にのみ許可することで GitHub Actions token や GitHub App, write 権限を持つ collaborator の PAT が悪用されても tag を作成することが出来ません。
ただし、リリース時に tag の作成を GitHub Actions token などで自動化している場合は動かなくなるので注意が必要です。
自分の場合、今回の取り組み以前から tag は必ず自分で作成しています。
上記の Ruleset で攻撃者が tag を作成するのを防ぐためというのもありますし、そもそも自動で tag を作成した場合、 tag の署名が困難だからです。
commit の署名は GitHub Actions token や GitHub App を使って API で commit を生成すればできるのですが、自分の認識では tag はどうもできないようです。

commit の署名を必須にすることでなりすましも防ぐようにしました。
元々 default branch では署名を必須化していましたが、全 branch で必須にしました。
CI で署名付きの commit を生成する際は ghcp, commit-action, gh-pages-action が便利です。

Renovate の minimumReleaseAge を設定

Renovate を使っている場合、 minimumReleaseAge を設定し、リリースされてから数日後に PR が作られるようにしました。
これは、対象のバージョンがリリースされてから指定した期間後に update されるようにする設定です。
期間をどの程度置くかは難しいですが、自分は全ての update に対して 3 days に設定しました。
大きなインシデントは 3 日もあれば見つかるだろうという想定です。
期間を空けると安全になる分更新が遅れるのでトレードオフですが、やはり安全側に倒して 3 日くらいは空けるのが良いのかなと思いました。
自分の場合はほぼすべてのリポジトリに適用する Renovate Config Preset を管理しています。

public repository で 極力 pull_request_target を使わない

tj-actions のインシデントでは pull_request_target が悪用されました。
https://zenn.dev/shunsuke_suzuki/articles/tj-actions-incident-2025 にも書いた通り、 public repository では pull_request_target は基本使うべきではありません。
PR のコードなどを一切参照しないような安全なケースに限って使いましょう。

課題

上記のような改善によってだいぶセキュリティは改善しましたが、まだ課題も幾つかあります。

  • GitHub CLI の OAuth App が強い権限を持っている
  • 異常なアクティビティの検知

GitHub CLI の OAuth App が強い権限を持っている

自分は普段 GitHub CLI を良く使っています。
GitHub CLI では通常 OAuth App を使って GitHub と連携します。
gh auth token コマンドを実行すると access token を生成することも出来ます。
ただこの access token は classic PAT 並に強い権限を持っていて、流出すると大変危険です。

普段は権限を最小限にしつつ必要に応じて一時的に権限を付与する (無条件に付与してたら意味がないので MFA を挟む感じになる) のがセキュリティ的には理想ですが、それは結構利便性を損なうことになるかと思います。

異常なアクティビティの検知

上記のような対策は攻撃を未然に防ぐのには役立ちますが、攻撃されたときの検出にはなっていません。
理想を言うと異常なアクティビティを検知したいです。

  • 異常な tag の作成 (tag が署名されてない)
  • 異常な tag の修正・削除
  • 異常な release asset の更新
  • 異常な Rulesets の修正
  • etc

補足: ツールのインストール時の検証

Release Asset の改竄については、以下のような検証が役立ちます。

  • checksum
  • Cosign, Minisign, slsa-verifier, GitHub Artifact Attestations

自分が公開している Go 製の CLI では Minisign 以外はサポートしています。
各ツールの install guide をみてください。

https://github.com/suzuki-shunsuke/pinact/blob/main/INSTALL.md#verify-downloaded-assets-from-github-releases

CLI version manager の aqua はこれらの検証をサポートしています。

https://aquaproj.github.io/docs/reference/security/

フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link