🛡️

GitHubに機密情報をpushしてしまった日のために — 無効化、履歴除去、多層防御の組み立て方

に公開

はじめに

GitHubに機密情報が混入してしまったとき、最初に取るべき行動は履歴削除ではありません。漏れたものが何なのかで対応の順序が大きく変わります。本記事では、GitHub Enterprise CloudやGitHub Enterprise Serverを前提に、機密情報の混入を止める仕組みと、混入してしまったあとに残存物を取り除くための作業を、コマンドが手元で再現できる粒度で整理します。

GitHub Docsの該当ページは、漏えいしたものが認証情報であれば、まず無効化やローテーションを行うべきだとしています。無効化できれば履歴の書き換えまで踏み込まなくてよい場合もあります。一方で、個人情報や顧客情報、秘密鍵、設定ファイル、社内URL、証明書のように、ローテーションができないか、残っていること自体が問題になるデータは、Git履歴やGitHub上のPR参照、キャッシュ、fork、cloneまで含めた除去を考える必要があります。

対象読者は、Gitとgithub.comの基本操作ができ、git pushの挙動とCI/CDの仕組みに触れたことがある方です。GitHub Enterpriseの管理者でなくとも、開発者の立場で予防策を導入したい場面でも使える内容にしています。

まず無効化、それから履歴除去

混入した機密情報の対応は、無効化と履歴除去を分けて考えるところから始まります。

無効化が現実的なのは、APIキー、トークン、パスワード、デプロイキーのように、サービス側で発行し直せるものです。漏えいした認証情報の場合は、Git履歴の書き換えよりも先にrevokeとrotateを行うべきだと、GitHub Docsでも明記されています。無効化が間に合えば、攻撃者が同じ値を再利用しても通らないため、履歴削除まで踏み込むなくても済む場合があります。

一方で、残っていること自体が問題になるデータは履歴除去が必要です。次のものはローテーションでは解決しません。

  • 顧客情報や個人情報
  • 秘密鍵や証明書の原本
  • 漏えいすれば再生成できない構成情報

このようなデータの場合、Gitリポジトリ全体だけでなく、GitHub上に残るPR参照、キャッシュ、fork、clone、CIログ、artifact、リリースアセット、バックアップまで意識した除去設計が必要になります。「commitを消したら安全」と思い込みがちな部分ですが、GitHub Docsは履歴を書き換えてforce pushしても、SHA-1ハッシュ経由のキャッシュビューやPR参照、forkに残ることがあると説明しています。force pushは完全削除を意味しません。

多層防御の全体像

GitHub Enterpriseで現実的に組める防御層は、混入を止める層と、混入してしまったあとに対応する層に分かれます。

レイヤー 目的 代表的な手段
コミット前 開発者の手元で止める Gitleaks, git-secrets, detect-secrets, pre-commit
push時 リモートに入る前に止める GitHub Push Protection, GHES pre-receive hook
PRとCI レビュー前後で検知する Gitleaks GitHub Action, TruffleHog, GitHub Secret Scanning
既存履歴調査 過去の混入を洗い出す Gitleaks, TruffleHog, detect-secrets
履歴削除 Git履歴から除去する git-filter-repo, BFG Repo-Cleaner
GitHub側残存除去 PR参照、キャッシュ、GC対応 GitHub Support、GHES管理者対応
再発防止 運用、教育、権限、secret管理 HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, OIDC

これらは単独で適用してもまだ穴があります。pre-commitだけでは開発者がスキップでき、push protectionだけでは既存履歴をカバーできず、履歴削除だけではGitHub側のPR参照やキャッシュが残ります。それぞれの層でできることとできないことを把握したうえで、全体を組み立てる必要があります。

レイヤー別のツールと使い分け

Gitleaks

Gitleaksは、Gitリポジトリやディレクトリから機密情報を検出するツールです。GitHub Actions、pre-commit、CLIスキャン、ベースライン機能が公式で案内されています。pre-commitでは.pre-commit-config.yamlにGitleaks hookを追加し、pre-commit installすることで、機密情報を含むcommitを失敗させられます。

ここで意識しておきたいのは、pre-commit hookは開発者がスキップできる点です。Gitleaksの公式にもSKIP=gitleaks git commit ...でスキップできる例が載っています。手元の予防としては有効ですが、組織として強制力を持たせたい場合は、push時のサーバー側ブロックと組み合わせる必要があります。

pre-commitでGitleaksを有効化するコマンド
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.24.2
    hooks:
      - id: gitleaks
brew install pre-commit gitleaks
pre-commit install
pre-commit run --all-files
GitHub ActionsでGitleaksを動かす設定
name: gitleaks
on:
  pull_request:
  push:
  workflow_dispatch:
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2

fetch-depth: 0を指定すると、履歴を含めた検査がしやすくなります。

GitHub Push Protection

GitHubのPush Protectionは、ハードコードされた認証情報がリポジトリに到達する前にブロックする機密情報スキャン機能です。CLIによるpush、GitHub UI上のcommit、ファイルアップロード、REST API経由のpushを対象にできます。

GitHub Enterprise CloudやGitHub Enterprise Serverで利用可能な場合、まず有効化するのが妥当です。Repository、Organization、Enterpriseのいずれの単位でも有効化でき、リポジトリ単位のPush ProtectionはGitHub Secret Protectionの有効化が前提になります。デフォルトでは無効です。

リポジトリのSettingsからCode securityに進み、Secret scanning配下のPush protectionをオンにします。

Push Protectionの有効化画面
リポジトリのSettingsからCode securityに進み、Secret ProtectionとPush protectionをオンにする

実際にPush Protectionが効くと、違反するcommitやpushがブロックされ、検出された機密情報の種別と該当行が表示されます。Disableが表示されている状態は、現在その機能が有効化されていることを示します。

注意点として、false positiveは発生します。既定ではbypass可能な設定があり、bypassされた場合はSecurityタブやaudit log、メールアラートに記録が残ります。GitHubがサポートする機密情報パターンに依存する点も意識しておく必要があります。独自トークン形式を運用している場合は、custom patternの設計が必要です。

GHES pre-receive hook

GitHub Enterprise Serverでは、pre-receive hookによってpushを受け入れる前に任意のスクリプトでチェックできます。pre-receive hookはpush時に隔離環境で実行され、exit statusが0なら受け入れ、非0ならrejectします。機密情報をkeyword、pattern、file typeでブロックする使い方が公式の用途例として挙げられています。

ただし、GHESのpre-receive hookは性能影響が大きいです。GitHub Docsは外部API呼び出しや長時間のGit操作を避けるよう注意しています。pre-receive hook全体のタイムアウトは5秒で、hookが長引くとpushが失敗します。

このため、サーバー側でGitleaksを動かす場合は、リポジトリ全履歴ではなく差分だけを高速にスキャンする設計にする必要があります。

pre-receive hookで差分だけをスキャンする例
#!/usr/bin/env bash
set -euo pipefail
while read oldrev newrev refname; do
  # 新規ブランチの場合、oldrevはall-zeroになることがある
  if [[ "$oldrev" =~ ^0+$ ]]; then
    range="$newrev"
  else
    range="$oldrev..$newrev"
  fi
  # 受信した差分だけを対象にする
  git diff --name-only "$range" | while read file; do
    git show "$newrev:$file" 2>/dev/null | gitleaks stdin --redact
  done
done

実運用では、バイナリ除外、巨大ファイル除外、allowlist、タイムアウト、ログ、メッセージ整形、緊急bypassの承認フローを追加してください。

git-filter-repo

git-filter-repoは、現時点でGitHub Docsが機密情報削除の手順として案内している標準ツールです。--sensitive-data-removalフラグを利用するには2.47以上が必要で、特定ファイルを全branchやtag、refから削除する例や、特定文字列を--replace-textで置換する例が示されています。

ファイル単位で履歴から消すコマンド
git clone https://github.com/ORG/REPO.git
cd REPO
git-filter-repo \
  --sensitive-data-removal \
  --invert-paths \
  --path path/to/secret-file.env
文字列を置換するコマンド
cat > ../passwords.txt <<'EOF'
literal:ACTUAL_SECRET_VALUE
regex:AKIA[0-9A-Z]{16}
EOF
git-filter-repo \
  --sensitive-data-removal \
  --replace-text ../passwords.txt
force pushで反映するコマンド
git push --force --mirror origin

このmirror pushは全branch、tag、refを強制更新するため、他人の変更を破棄する可能性があります。refs/pull/はGitHub側でread-onlyのため、これらへのpushは失敗します。Branch protectionが有効な場合は一時的に解除が必要になることがあります。

ファイルがrenameやmoveされていた場合、過去のpathもすべて指定する必要があります。GitHub Docsも、機密データを含むファイルが別pathに存在したことがあるなら、追加の--path指定または再実行が必要だと説明しています。これは見落としやすいので注意してください。

BFG Repo-Cleaner

BFG Repo-Cleanerは、巨大ファイルや、パスワード、認証情報のような不適切なデータをGit履歴から取り除くためのツールです。git-filter-branchより単純で高速な代替として位置づけられており、巨大ファイルの削除や、パスワード、認証情報、個人情報の除去に向いています。

BFGで履歴を整理するコマンド
git clone --mirror git@github.com:ORG/REPO.git
cd REPO.git
# 指定したファイル名を履歴から削除
bfg --delete-files secret.env
# banned.txtに書いた文字列を置換
bfg --replace-text banned.txt
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force --mirror

git-filter-repoとの使い分けは、次のように整理できます。

観点 git-filter-repo BFG
推奨度 GitHubドキュメントの現行手順で中心 用途が合えば便利
柔軟性 高い 比較的シンプル
文字列置換 可能 可能
pathやref、callback制御 強い 限定的
大容量ファイル削除 可能 得意
組織の標準手順 第一候補にしやすい 補助候補

TruffleHog

TruffleHogは、機密情報の発見、分類、検証に強いツールです。公式の説明では800以上の機密情報タイプを分類でき、分類できるタイプについては実際にloginして生きているかどうかを確認する機能があります。

Gitleaksと比べると、検出後に「実際にまだ有効な機密情報なのか」を判定する用途で使い分けやすくなります。インシデント対応では、Gitleaksで広く拾い、TruffleHogで生きている機密情報を優先的に処理する組み合わせが現実的です。

git-secretsとdetect-secrets

git-secretsはAWS Labsのツールで、pre-commit、commit-msg、prepare-commit-msgのhookによって禁止パターンを検査します。変更ファイル、commit message、non-fast-forward mergeで入る履歴に対して禁止regexパターンを検査するhookが公式で説明されています。

detect-secretsはYelpのツールで、pre-commit hookとベースライン運用に向いています。公式のcaveatでは、これは確実に機密情報の混入を防ぐ仕組みではなく、明らかなケースを止めるためのヒューリスティックであり、複数行にまたがる機密情報や一部の既定パスワードは防げないと説明されています。「Gitleaksを入れたから絶対漏れない」と考えてしまうのと同じく、これらのツールも完璧な防御を保証するものではありません。

インシデント発覚時の標準ランブック

混入が発覚したあと、どの順序で何をすべきかを段階的にまとめます。

Phase 0 封じ込め

最初の数十分で次を実施します。

  1. 該当する機密情報を即座にrevokeまたはrotateする
  2. 影響範囲を確認する
  3. 該当リポジトリを一時的にfreezeする
  4. CI/CD secret、deploy key、GitHub App token、クラウドの認証情報を棚卸しする
  5. fork、clone、artifact、log、release asset、packageへの流出可能性を確認する

GitHub Docsでも、認証情報の場合は履歴除去より先にrevokeとrotateを行うべきだと案内しています。

Phase 1 対象特定

GitleaksとTruffleHogでスキャンし、混入箇所を確定させます。

スキャンコマンド
# Gitleaksで履歴をスキャン
gitleaks git . --report-path gitleaks-report.json --redact
# TruffleHogで検証つきスキャン
trufflehog git file://$(pwd) --json > trufflehog.json

確認すべき情報は次の通りです。

  • 機密情報の種類
  • file path
  • commit SHA
  • branch、tag、PR ref
  • 生きているか、すでに無効化されているか
  • 個人情報や顧客情報、秘密鍵などローテーション不可能なデータか
  • fork、clone、CIログ、artifactに残っていないか

Phase 2 履歴から削除

ファイル単位で消す場合は次のようになります。

ファイル単位で削除するコマンド
git clone https://github.com/ORG/REPO.git
cd REPO
git-filter-repo \
  --sensitive-data-removal \
  --invert-paths \
  --path config/production.env

文字列だけを置換する場合は次のようになります。

文字列を置換するコマンド
cat > ../secrets-to-replace.txt <<'EOF'
literal:sk_live_xxxxxxxxxxxxxxxxx
literal:-----BEGIN PRIVATE KEY-----
regex:ghp_[A-Za-z0-9_]{36}
EOF
git-filter-repo \
  --sensitive-data-removal \
  --replace-text ../secrets-to-replace.txt

Phase 3 検証

書き換えた結果が想定通りかを確認します。

検証用のコマンド
# 対象文字列が残っていないかを全refから探す
git grep -n "sk_live_xxxxxxxxxxxxxxxxx" $(git rev-list --all)
# Gitleaksで再スキャン
gitleaks git . --report-path after-cleanup.json --redact
# 削除対象ファイルの履歴差分を確認
git log --all -- path/to/secret-file.env

Phase 4 GitHubへ反映

書き換えた履歴をリモートに反映します。Branch protectionが有効な場合、force pushが弾かれることがあるので、対象branchの保護を一時的に外す必要があります。

Branch protectionの設定画面
force push前に対象branchのprotectionを一時的に解除する

force pushコマンド
git push --force --mirror origin

ここで注意したいのは、force pushしただけでは完全削除にはならない点です。GitHub Docsは、履歴を書き換えてforce pushしても、clone、fork、SHA-1ハッシュ経由のキャッシュビュー、対象commitを参照するpull requestなどから依然としてアクセスできる場合があると明記しています。「force pushすれば完全削除」というのは誤解です。

Phase 5 GitHub側の残存削除

GitHub Enterprise Cloudの場合、git-filter-repoの実行後にGitHub Supportへ連絡し、owner/repo、影響を受けるPR数、最初に変更したcommit、LFS orphanの情報を提供します。条件が揃うと、GitHub Support側で影響PRのdereferenceや削除、サーバー側のgarbage collection、キャッシュビューの除去、LFSオブジェクトのpurgeなどが実行されます。

GitHub Enterprise Serverの場合は、GitHub.com Supportではなく自社のGHES管理者がPR refs、キャッシュ、GC、バックアップ、replica、ログ、forkを含めて対応する必要があります。ここは環境設計に依存するため、自組織のGHES運用手順に落とし込んでおく必要があります。CloudとServerでGitHub Supportに依頼できる範囲が変わるので、契約形態の前提を最初に確認しておくと、対応が止まりにくくなります。

Phase 6 再汚染を防ぐ

履歴を書き換えるとcommit hashが変わります。古いcloneを保有している開発者がgit pullした後にgit pushすると、書き換え前の履歴が再度混入する可能性があります。これはGitHub Docsでも明記されている挙動で、共同作業者は古い履歴上のbranchをmergeではなくrebaseする必要があります。

開発者には次の方針を周知してください。

再cloneする手順
mv repo repo-tainted-backup
git clone git@github.com:ORG/REPO.git

どうしても既存のcloneを使い続ける場合は、対象branchをrebaseし、mergeで古い履歴を取り戻さないようにします。

GitHub Enterpriseでの実装パターン

GitHub Enterprise Cloud

おすすめする構成は次の通りです。

  1. GitHub Secret Scanningを有効化する
  2. Push Protectionを有効化する
  3. 独自トークンにはCustom patternsを定義する
  4. CIにGitleaks GitHub Actionを組み込む
  5. Repository rulesetsで強制力を担保する
  6. Branch protectionで主要branchを守る
  7. インシデント時はgit-filter-repoとGitHub Supportを併用する

GitHub Enterprise Cloudなら、Push ProtectionとSecret Scanningを中心に組むのが自然です。独自形式の機密情報がある場合はcustom patternを定義します。Repository rulesetsは、branch protectionより細かい単位でルール化でき、require_last_push_approvalrequired_status_checksを組み合わせるとforce pushや自己承認の防止にも使えます。

Repository rulesetの作成画面
Repository rulesetでforce push禁止や必須レビューを宣言する

GitHub Enterprise Server

おすすめする構成は次の通りです。

  1. GHESのバージョンが対応しているなら、標準のSecret ScanningとPush Protectionを有効化する
  2. pre-receive hookで差分の軽量スキャンを行う
  3. CIでGitleaksとTruffleHogを併用する
  4. 定期的に全履歴スキャンを行う
  5. インシデント時はgit-filter-repoで履歴除去する
  6. GHES管理者がPR refs、GC、バックアップ、キャッシュ、forkまで含めた削除運用を行う

pre-receive hookは強力ですが、性能影響と5秒タイムアウトの制約があるため、全履歴スキャンを毎回走らせるのは避け、受信差分の軽量チェックに限定するのが安全です。

なお、Push ProtectionやSecret Scanningが利用できるかは、GitHub Enterpriseの契約形態、GHESのバージョン、設定状態によって変わります。pre-receive hookでGitleaksを動かす設計も、リポジトリサイズ、push頻度、GHESの性能、5秒タイムアウト、allowlistの運用次第で形が変わります。これらは自組織の前提に合わせて調整してください。

予防策の実用セット

.gitignore

最低限、次のパターンは追跡対象から外してください。

.gitignoreの例
.env
.env.*
*.pem
*.key
*.p12
*.pfx
id_rsa
secrets.yml
credentials.json

.gitleaks.toml

カスタムルールとallowlistを設定する例です。

.gitleaks.tomlの例
title = "company gitleaks config"

[extend]
useDefault = true

[[rules]]
id = "company-internal-token"
description = "Company internal token"
regex = '''company_[A-Za-z0-9]{32,}'''
keywords = ["company_"]

[allowlist]
description = "Allow test fixtures"
paths = [
  '''test/fixtures/.*''',
  '''docs/examples/.*'''
]

CI設定

main pushとPRに加えて、定期スキャンも組み合わせるのが現実的です。

GitHub Actionsの例
name: secret-scan
on:
  pull_request:
  push:
    branches: [main]
  schedule:
    - cron: "0 3 * * *"
jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

ローカルpre-commit

開発者の手元でcommit前に検査する設定です。

pre-commitの導入コマンド
brew install pre-commit gitleaks
pre-commit install
pre-commit run --all-files

履歴を削除すべきかの判断基準

混入したものの種類によって、対応の重さは変わります。

状況 対応
APIキー、token、パスワード まずrevokeまたはrotate。履歴削除はリスクと監査要件に応じて判断する
秘密鍵 revokeとrotateに加えて、履歴削除を強く検討する
個人情報や顧客情報 履歴削除、GitHub側のPR参照削除、cloneやforkまで含めた対応が必要
社内URLや構成情報 公開範囲とリスクに応じて判断する
テスト用のダミー機密情報 allowlistまたはbaselineで管理する。ただし本物に見える値は避ける
大容量ファイル BFGまたはgit-filter-repoで対応する
LFSに入った機密情報 通常のGit履歴削除では不十分で、LFSオブジェクトのpurgeが必要になる

履歴削除は常に正解とは限りません。GitHub Docsは、履歴の書き換えには再汚染、他開発者の作業の喪失、commit hashの変更、branch protectionの一時解除、PR diffの破損、コミット署名の喪失といった副作用があると説明しています。漏れたものが「ローテーション可能な認証情報」なのか、「個人情報や顧客情報、秘密鍵、証明書のように残存自体が問題になるもの」なのかで、履歴削除の必要度は大きく変わります。

forkが存在する場合、中央リポジトリだけを掃除しても完全な除去にはなりません。GitHub Docsも、forkの所有者と調整するか、forkを削除する必要があるとしています。GitHub Enterprise CloudかGitHub Enterprise Serverかでも、GitHub Supportに依頼できる範囲と、管理者が自前で行うべき範囲が変わります。これらは自組織の構成に応じて事前に運用手順へ落とし込んでおいてください。

まとめ

混入した機密情報の対応は、無効化と履歴除去をまず分けて考えるところから始まります。

無効化が現実的なものは、まずrevokeとrotateで攻撃面を閉じます。それでもなお残存自体がリスクになるデータは、git-filter-repoで履歴から取り除き、GitHub側のPR参照やキャッシュ、forkまでを意識した削除を行います。

混入を未然に止める層は、Gitleaksなどの手元検知、GitHub Push Protection、GHES pre-receive hook、CIでの定期スキャンを組み合わせて多層化します。それぞれの層には穴があるため、単一のツールに頼らず、層をまたいだ運用設計に落とし込むことが大切です。

組織の運用に組み込む際は、Push ProtectionやSecret Scanningの利用可否、pre-receive hookの性能制約、forkの存在、GitHub Enterprise CloudかGitHub Enterprise Serverかといった条件によって、最適な構成が変わります。本記事のランブックを土台に、自組織の構成に合わせて手順を調整してみてください。

Discussion