リポジトリの履歴にAPIキーやパスワードなどの機密情報を誤ってコミットしてしまった場合、単にrevert
で変更を取り消すだけでは不十分です。
過去のコミットに情報が残り続け、クローンやフォークから露出します。本記事では git filter-repo
を用いて
「履歴から痕跡ごと完全に削除」し、強制プッシュと周知まで含めた安全な運用手順を解説します。
最優先:機密のローテーション(無効化)
履歴クリーンアップより先に、漏えいした資格情報を直ちに無効化またはローテーションします。公開から数分で自動収集されることも多いため、
まずは被害を止めます。クラウド鍵・トークン・Webhookシークレット等の再発行を完了してから、以下の作業へ進みます。
作業の全体像
1) ミラークローンで保険を取り、2) git filter-repo
で履歴から該当データを削除・置換し、3) 参照掃除とパック再構築を行い、
4) --force-with-lease
でリモートへ反映、5) コントリビュータへ再同期(再クローン/厳密リセット)を周知、という流れです。
issues/PR/リリース資産など「Git外」の痕跡確認も忘れずに実施します。
準備:安全な作業環境を作る
履歴書き換えは破壊的操作です。元リポジトリではなくミラー上で実行し、いつでもやり直せるようにします。
# リモートURLは差し替え
git clone --mirror https://example.com/owner/repo.git repo.git
cd repo.git
# filter-repo が未インストールなら(Python環境)
# pip install git-filter-repo
# or: brew install git-filter-repo など
パターンA:特定ファイル(鍵/設定)を履歴から完全削除
秘密鍵や.envなど、path/to/secret.file
として明確なら、該当パスを全コミットから除去します。
# 例:特定ファイルを全履歴から削除
git filter-repo --path path/to/secret.file --invert-paths
ディレクトリや複数パスは--path
を複数指定します。誤って残したくないバイナリ群にも有効です。
# 例:credentials/配下とold_keys/配下を全削除
git filter-repo \
--path credentials/ --path old_keys/ \
--invert-paths
パターンB:ファイルは残しつつ「値」だけマスク(置換)
設定ファイルそのものは必要だが中の値(APIキーなど)を消したい場合は、置換ルールを定義します。
# 置換ルールファイル(replacements.txt)の例
# 書式: regex:<パターン>==><置換文字列>
regex:AKIA[0-9A-Z]{16}==>AKIA****************
regex:(?<=password=)[^&\n]+==>********
# 置換を適用(コミット本文・ツリーに対して実行)
git filter-repo --replace-text replacements.txt
メールやユーザー名などメタ情報も修正したい場合は、--mailmap
や--force
オプションの併用を検討します。
パターンC:巨大/危険ブロブをしきい値で一掃
どのファイルが危険か把握しきれない場合は、まず大容量ブロブを洗い出し、しきい値で除去します(サイズだけでは機密判定できない点に注意)。
# 大きい順にブロブを確認
git rev-list --objects --all | \
git cat-file --batch-check='%(objectname) %(objecttype) %(objectsize) %(rest)' | \
grep ' blob ' | sort -k3 -n | tail -n 20
# 例:10MB超のブロブを除去(サイズ軸。機密は別途replace-textで)
git filter-repo --strip-blobs-bigger-than 10M
副作用の掃除:dangling参照とパックを整理
書き換え後は古い参照が残ると再流出の温床になります。reflogやパックを積極的に掃除します。
git reflog expire --expire=now --all
git gc --prune=now --aggressive
リモートへ反映:安全な強制プッシュと周知
履歴が変わるため、--force-with-lease
で上書きします。タグも書き換えた場合は--tags
や--mirror
を利用します。
# ミラーでの一括反映(推奨)
git push --force-with-lease --mirror
# またはブランチ/タグを個別に
# git push --force-with-lease --all
# git push --force-with-lease --tags
実行前後でチームに必ず周知し、既存クローンは再クローン、もしくは厳密リセットでの再同期を案内します。
# 既存クローン向け再同期手順(周知用)
git fetch --prune
git checkout main
git reset --hard origin/main
git gc --prune=now
確認:本当に消えたか検証する
置換/削除の漏れがないか、差分とオブジェクトを機械的に確認します。CI用の“空フォーク”で検証するのも有効です。
# シークレットのパターンを全履歴検索(例:AKIA)
git grep -I -n -e 'AKIA[0-9A-Z]\{16\}' $(git rev-list --all) || echo "OK"
# 直近だけでなく、ブロブ全体から検索
git rev-list --objects --all | cut -d' ' -f1 | \
xargs -I{} sh -c "git cat-file -p {} | grep -q 'SECRET_VALUE' && echo Found:{}" || echo "OK"
# 大容量ブロブが残っていないか再点検
git rev-list --objects --all | \
git cat-file --batch-check='%(objectname) %(objecttype) %(objectsize) %(rest)' | \
grep ' blob ' | sort -k3 -n | tail -n 10
Git外の痕跡にも注意(必ず併走)
ソースツリー外(Issues/PRコメント/コードレビューの差分ビュー/スクリーンショット/リリースアセット/ログ出力/ドキュメント)に機密が残っていないか確認し、必要なら削除や再発行を行います。
ホスティングのキャッシュ(検索インデックス/ZIPアーカイブ)も時間差で残る場合があるため、サポート窓口へのパージ依頼も検討します。
よくある落とし穴
ブランチだけ消してタグを放置、フォークやミラーに旧履歴が残る、CI/CDのキャッシュに秘匿値が展開され続ける、といったケースが頻出です。
可能な限り--mirror
で統一反映し、フォーク・テンプレート・ミラーの取り扱いも周知します。サブモジュールがある場合は、参照先リポジトリも同様にクリーンアップが必要です。
再発防止:仕組みで防ぐ
人的対策だけでなく、機械的ガードを入れます。秘密はコードに置かず、環境変数/シークレットマネージャを用い、例示は.env.example
で配布します。
さらにコミット前・CI段階でのシークレット検出を導入します。
# 例:pre-commitでgitleaksを実行(導入イメージ)
# .pre-commit-config.yaml に gitleaks フックを設定し、コミット前に自動スキャン
# CIでも gitleaks/trufflehog を走らせ、PR時にブロック
# 例:.gitignore に秘匿ファイルを登録
echo ".env" >> .gitignore
echo "credentials/*.json" >> .gitignore
git add .gitignore
git commit -m "Ignore secrets by default"
トラブル時のチェックリスト(保存版)
① 直ちに鍵を無効化/ローテーション。② ミラーで作業開始(バックアップ確保)。③ filter-repo
で削除/置換。
④ reflog expire
とgc
で掃除。⑤ --force-with-lease
で反映。⑥ 既存クローンの再同期を周知。
⑦ Git外の痕跡(Issues/PR/アセット)も削除。⑧ 事後スキャンと再発防止(検出フック/運用ルール)を実装。
まとめ
機密情報を「完全に」削除するには、git filter-repo
で履歴そのものを改変し、参照掃除と強制プッシュ、周辺エコシステムのパージまで一気通貫で進める必要があります。
まずは鍵の無効化で被害を止め、その後にミラー上で安全にクリーンアップ。最後にチーム全体で再同期と再発防止の仕組みを整えれば、実運用でも確実にリスクを低減できます。