開発mdx-playgroundアクティブ

きっかけ

午前中に同じ日付で書いた姉妹記事で、「ある会計ソフト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.ymlFree個人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

寝る前に書いておくと、起きた自分がコピペするだけで済む。これがひとり開発の引き継ぎ術。