きっかけ
午前中に同じ日付で書いた姉妹記事で、「ある会計ソフトAの GitHub ソースコード流出事件」を読んで自分のリポジトリにどう落とし込むかを整理した。網羅的に対策を並べはしたものの、肝心の「今、自分のアカウントで何が有効になっていて、何が抜けているのか」が分からない。
棚卸しせずに対策リストだけ作っても、同じことを来月もう一度やる気がした。だから午後はひたすら現状把握に振った。
最終的にこのメモは「明日のTODO」になる。読み返した自分が、Plan A/B/Cの3択からチェックボックスを1つ埋めて、コマンドをコピペするだけで動ける形を目指している。
Push ProtectionとSecret Scanningとgitleaksの違いを整理
最初にユーザー(自分)に説明する形で言葉を整理した。3つともシークレット検知系だが、配置場所がまったく違う。
- gitleaks: 手元のファイルを正規表現で舐めるスキャナ。pre-commit hook で
git commitを止める「家の中の煙感知器」。 - Push Protection: GitHubサーバー側で
git pushを物理的に拒否する「玄関の関所」。検知済みパターンに合致する文字列を含む push をリモートが弾く。 - Secret Scanning: pushされた後(または既存履歴)にGitHubが定期的にスキャンしてアラートを上げる「監視カメラ」。
この3つは排他ではなく多層防御として重ねる前提で、関所(Push Protection)を一番手前に置くと費用対効果が高い、という話に落ちた。
eurekapu-nuxt4 のセキュリティ設定状況を gh CLI で覗く
まず一番大事な eurekapu-nuxt4(私的な本番リポジトリ、private)から見た。
gh api repos/keikomatsu/eurekapu-nuxt4 \
--jq '{private, has_vulnerability_alerts: .has_vulnerability_alerts, security_and_analysis}'
返ってきた結果で分かったこと:
- private リポなので Branch Protection は GitHub Free Plan の制約で利用不可(有料プラン必須)。これは諦める枠。
- Dependabot Security Updates は有効。これは過去の自分が偉い。
- Secret Scanning と Push Protection の状態は
security_and_analysisフィールドで取れるはずだが、自分のトークンにsecurity_eventsスコープが足りずにnullで返ってきた。
スコープ不足については別途PATを発行する必要があると気付いたので、いったん「未確認」マークだけ立てて先に進んだ。
Code security 設定画面の各項目を意味で整理
GitHub の Settings > Code security の各項目を、用語を曖昧にしたまま放置していたので一通り言語化した。
| 項目 | 役割 | プラン制約 |
|---|---|---|
| Dependabot alerts | 依存ライブラリの脆弱性を通知 | Free個人OK |
| Dependabot security updates | 脆弱性修正のPRを自動生成 | Free個人OK |
| Dependabot version updates | バージョン更新のPRを自動生成(要 dependabot.yml) | Free個人OK |
| Secret scanning | コミット内のシークレットを検出してアラート | Free個人OK(公開・非公開とも) |
| Push protection | シークレット検出時に push 自体を拒否 | Free個人OK |
| Code scanning (CodeQL) | 静的解析でコードの脆弱性を検出 | private は Advanced Security 必須(有料) |
つまりCode Scanning(CodeQL) と Branch Protection 以外は GitHub Free 個人アカウントでも全部無料で使える。これに気付いてから一気にやる気が出た。
「Enable all」ボタンの罠
個人アカウントの Code security and analysis ページには Enable all ボタンがある。押せば全部ONになると思っていたが、押した後で覗いてみると Dependabot 系3項目のみ一括適用され、Secret Scanning と Push Protection(private repo 用)は対象外だった。
これらは1リポずつ API を叩くか、CLI ループで一括適用する必要がある。「ボタン1つで終わると思ったのに罠だった」と気付くまでに15分くらい無駄にした。
158リポの現状を gh API でバックグラウンド一括取得
頭で考えても分からないので、まず全リポの現状をJSONで吸い出すことにした。
gh api graphql --paginate -f query='
query($endCursor: String) {
viewer {
repositories(first: 100, after: $endCursor, ownerAffiliations: OWNER) {
pageInfo { hasNextPage endCursor }
nodes {
name
isPrivate
isFork
hasVulnerabilityAlertsEnabled
}
}
}
}'
private 145、public 13、合計158リポ。多い。
バッチ処理にするためシェルスクリプトに落としたところ、2つバグを踏んだ。
バグ1: 行末の CR が混入
PowerShellで生成したリポ名リストを Git Bash で読ませたら、リポ名末尾に \r が混入して gh api repos/keikomatsu/foo\r/... という URL を投げて404の山が出た。
# 修正版: tr で CR を除去してから読む
cat repos.txt | tr -d '\r' | while read repo; do
gh api "repos/keikomatsu/$repo" --jq '.security_and_analysis' >> out.json
done
バグ2: JSON 空値の処理
security_and_analysis がそもそも null で返ってくるリポがあって、--jq の出力が空行になり、後段の集計で行ズレを起こした。// {} でデフォルト値を入れて潰した。
gh api "repos/keikomatsu/$repo" \
--jq '{repo: "'$repo'", sa: (.security_and_analysis // {})}' \
>> out.json
修正後に再実行。順調に進んでいたが 95件目あたりで急激に遅くなった。レートリミットに近づいているか、security_and_analysis を返すエンドポイントが重いのか切り分けは保留。
「明日 Plan A を実行すれば結局全リポONにするんだから、95件取れた時点の傾向で十分判断できる」と割り切って打ち切った。
95件分の集計結果
集計してみたら、想像以上にひどかった。
- Dependabot 系がONなのは eurekapu-nuxt4 の1件だけ
- 他に「ON」と出ているのは Anthropic 公式リポの fork(claude-code-action 系)で、これは fork 元のデフォルト継承
- 自分が能動的にONにしたリポは実質1件
- Secret Scanning / Push Protection は全リポでOFF(取得できた範囲)
実質ノーガード状態だった。記事を読んで対策を整理した自分の午前中の偉さが、午後の現状把握で完全に相殺された。
Plan A / B / C を3択に整理
明日の自分が選びやすいように、対応方針を3つに分けた。
Plan A: 全リポ一括ON(推奨)
158リポ全部にDependabot系3つ + Secret Scanning + Push Protection を一括適用。CLIループで30分くらい。
# Push Protection を private repo に有効化する例
gh api -X PATCH "repos/keikomatsu/$repo" \
-f security_and_analysis[secret_scanning_push_protection][status]=enabled
メリット: 一気に終わる。デメリット: fork した他人のリポにもPRが飛び始める可能性。
Plan B: アクティブな10リポだけON
過去90日にcommitがあったリポだけ抽出してON。Plan A の8割の効果を1割の手間で。
Plan C: eurekapu-nuxt4 だけON
最重要1リポだけ完璧にする。残り157リポはノーガードを受け入れる。
明日のTODOドキュメントに落とす
memo/2026-05-02/github-security-current-state.md に Plan A/B/C のチェックボックスと、それぞれのコマンド例をコピペできる形で残した。明日の朝、寝起きの自分がチェックボックスを1つ埋めて、ターミナルにペーストすれば動き始めるはず。
## TL;DR
- 158リポ中、実質的に守られているのは1リポだけ
- Plan A/B/Cから1つ選んで実行する
## 選択
- [ ] Plan A: 全リポ一括ON(30分)
- [ ] Plan B: アクティブ10リポのみ(10分)
- [ ] Plan C: eurekapu-nuxt4 のみ(5分)
学び
- 「対策の網羅リスト」と「現状把握」はセットで作らないと、来月もう一度同じ作業をする
- gh API は便利だが、95件目で重くなる現象に遭遇した。次回は最初から並列度を絞るか、
--paginateで1回投げるか検討 - security_events スコープがないと
security_and_analysisフィールドが null で返ってくる。最初にgh auth refresh -s security_eventsしてから始めるべきだった
明日の実行コマンド(Plan A の場合)
# 1. スコープを追加
gh auth refresh -s security_events,repo
# 2. 全リポリストを取得
gh repo list keikomatsu --limit 200 --json name,isPrivate \
--jq '.[] | select(.isPrivate==true) | .name' > private_repos.txt
# 3. Dependabot alerts + Secret Scanning + Push Protection を一括ON
cat private_repos.txt | tr -d '\r' | while read repo; do
gh api -X PUT "repos/keikomatsu/$repo/vulnerability-alerts"
gh api -X PUT "repos/keikomatsu/$repo/automated-security-fixes"
gh api -X PATCH "repos/keikomatsu/$repo" \
-f 'security_and_analysis[secret_scanning][status]=enabled' \
-f 'security_and_analysis[secret_scanning_push_protection][status]=enabled'
echo "done: $repo"
done
寝る前に書いておくと、起きた自分がコピペするだけで済む。これがひとり開発の引き継ぎ術。