「APIキーをコミットしたまま公開リポジトリにpushしてしまった」——Gitでもっとも焦る事故のひとつです。単にgit revertや次コミットで上書きしても、過去のコミットに残った機密情報はgit log -pや旧clone、fork、ミラー、検索インデックスから誰でも取り出せる状態のまま。履歴そのものを書き換えて痕跡を消す必要があります。
# 過去のcommitから簡単に取れる
$ git log -p
commit abc1234...
+ AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG...
# git showでも pickaxe検索でも出る
$ git log -S "AWS_SECRET" --all
$ git log --all -p | grep -E "AKIA[0-9A-Z]{16}"
# GitHubのbotは公開直後〜数秒で収集する
この記事は、機密誤コミットからの被害最小化フローを網羅したガイドです。最速60秒で動くべき対応、git filter-repo・BFG Repo-Cleanerの使い分け、漏洩種別(AWSキー/SSH鍵/.env/DBパスワード等)別のマスクパターン、GitHubキャッシュパージ依頼、CI/pre-commitによる機械的ガード、そして事後のセキュリティインシデント対応まで、現場で即使える形でまとめました。
この記事で学べること
- 漏洩発覚時の60秒ファーストアクション(rotate最優先)
- Public/Private/Enterprise別の対応優先度と被害評価
git filter-repoによる履歴からの完全削除- BFG Repo-Cleaner vs filter-repo の使い分け
- 漏洩した機密種別ごとの置換パターン(AWS/SSH/JWT/DB等)
- GitHubキャッシュ・forkパージ依頼の手順
- gitleaks・trufflehog・pre-commitによる機械的ガード構築
- インシデント対応テンプレ(社内報告・タイムライン作成)
【最速60秒】漏洩発覚時の最初の60秒
鉄則:履歴クリーンアップより先に機密をrotateしてください。GitHubの機密情報は公開から数秒〜数分で悪意ある収集botに取得されます。履歴を消しても既に外部に渡った認証情報は取り戻せないため、「この情報は既に全世界に公開された」前提で動くのが鉄則です。
# 0〜20秒:漏洩した機密を即時rotate/revoke # AWS : IAM > セキュリティ認証情報 > 対象アクセスキーを無効化 # GitHub: Settings > Developer settings > Personal access tokens > Revoke # DB : パスワード変更 / ユーザー再作成 # SSH鍵 : ~/.ssh/authorized_keys から該当公開鍵を削除 # JWT署名: 署名キー交換 / すべてのトークンを無効化 # 20〜40秒:漏洩範囲の記録(後の対応に必須) git log --all -p -S "漏洩した文字列" > /tmp/leak-evidence.txt # どのSHA・いつ・誰がcommitしたか証跡確保 # 40〜60秒:チームに通知&これから履歴書き換えをする旨を周知 # Slack:「@here repo X で機密漏洩発覚。rotate完了、履歴書き換え作業に入ります。 # 各自push作業は一時停止してください」
rotate先サービスのクイックリンク
- AWS IAM:
aws iam update-access-key --status Inactive - GCP:Service accounts → keys → delete
- GitHub PAT:
https://github.com/settings/tokens - Stripe:Dashboard → Developers → API keys → Roll
- Slack:App management → Credentials → Regenerate
- DB:各DBのパスワード変更SQL/GUI
STEP 0:被害評価(Public/Private/Enterprise)
漏洩リポジトリの性質で対応優先度と深刻度が大きく変わります。履歴書き換え作業に入る前に、どの属性に該当するかを正確に判断しましょう。
Publicへの漏洩は「世界に公開済み」と同義。履歴削除しても既に取得された情報は取り返せないので、rotate+関係者通知+ログ監査(不正使用痕跡の確認)を最優先で。履歴削除は「今後の露出を防ぐ清掃」と割り切ってください。
ツール選定:filter-repo vs BFG vs filter-branch
Gitには履歴書き換えツールが複数ありますが、現代の標準はgit filter-repoです。Git公式ドキュメントもgit filter-branchを非推奨としており、filter-repoが推奨ツールになっています。
結論:新規に学ぶならgit filter-repoのみ覚えればOK。BFGは過去に使用経験があるチームの継続利用なら問題ありませんが、filter-branchは避けてください(パフォーマンス問題・壊しやすさ・公式非推奨)。
git filter-repoのインストール
# pip(どのOSでも) pip install git-filter-repo # macOS(Homebrew) brew install git-filter-repo # Ubuntu 22.04+ sudo apt install git-filter-repo # Windows(scoop) scoop install git-filter-repo # 確認 git filter-repo --version
作業手順:ミラー→削除→掃除→force push
履歴書き換えは破壊的操作です。必ずミラークローンで作業し、問題があれば元リポジトリに戻せる体制を作ってから実行します。
# ミラークローン(全ref・reflogを含む完全コピー) git clone --mirror https://github.com/owner/repo.git repo.git cd repo.git # オブジェクト数を記録しておくと検証時に便利 git count-objects -v # 念のためのバックアップ cd .. cp -r repo.git repo.git.backup cd repo.git
注意:ミラーではなく通常のcloneで作業すると、他のブランチやタグが書き換わらないことがあります。--mirrorは必須です。また作業完了まで元リポジトリへのpushを止めるよう、Branch protection一時設定かチーム合意で作業期間のフリーズを入れるのも推奨。
漏洩種別別の削除パターン
パターンA:特定ファイルを履歴から完全削除
# 特定ファイルを全履歴から削除 git filter-repo --path .env --invert-paths # 複数ファイル/ディレクトリ git filter-repo \ --path .env \ --path config/secrets.yml \ --path credentials/ \ --invert-paths # globパターン git filter-repo --path-glob "**/*.pem" --invert-paths git filter-repo --path-glob "**/id_rsa*" --invert-paths
パターンB:ファイルは残しつつ値だけマスク
# replacements.txt の書式
# リテラル → 置換
wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY==>***REMOVED***
# 正規表現 → 置換
regex:AKIA[0-9A-Z]{16}==>AKIA****************
regex:ghp_[a-zA-Z0-9]{36}==>ghp_****REMOVED****
regex:(password|passwd|pwd)=[^\s&]+==>\1=***REMOVED***
# glob → 置換
glob:sk_live_*==>***REMOVED***
git filter-repo --replace-text replacements.txt # コミットメッセージも同時に置換対象 # コミット本文に機密が含まれる場合に有効
パターンC:機密種別別のマッチパターン集
# AWS Access Key ID
regex:AKIA[0-9A-Z]{16}==>AKIA_REDACTED_KEY
# AWS Secret Access Key(40文字base64風)
regex:(?i)aws(.{0,20})?(secret|key).{0,20}['\"][0-9a-zA-Z/+=]{40}['\"]==>AWS_SECRET_REDACTED
# GitHub Personal Access Token
regex:ghp_[a-zA-Z0-9]{36}==>GITHUB_PAT_REDACTED
regex:github_pat_[a-zA-Z0-9_]{82}==>GITHUB_FINE_GRAINED_PAT_REDACTED
# Slack Token
regex:xox[pboa]-[0-9]{12}-[0-9]{12}-[a-zA-Z0-9]{24}==>SLACK_TOKEN_REDACTED
# Google API Key
regex:AIza[0-9A-Za-z\-_]{35}==>GOOGLE_API_KEY_REDACTED
# Stripe秘密キー
regex:sk_live_[0-9a-zA-Z]{24,}==>STRIPE_KEY_REDACTED
# 一般的なパスワード記述
regex:(?i)(password|passwd|pwd|secret)[\s]*[:=][\s]*['\"]?[^\s'\"&]+['\"]?==>\1=REDACTED
# PEMプライベートキー(ブロック全体)
literal:-----BEGIN RSA PRIVATE KEY-----==>-----BEGIN RSA PRIVATE KEY BLOCK REMOVED-----
パターンD:しきい値超えの大容量blobを一括除去
# 大きいblobを確認
git rev-list --objects --all | \
git cat-file --batch-check="%(objectname) %(objecttype) %(objectsize) %(rest)" | \
awk '$2 == "blob" {print $3, $4}' | sort -n -r | head -20
# 10MB超のblobを全削除(機密判定は別途実施)
git filter-repo --strip-blobs-bigger-than 10M
# 1MB超
git filter-repo --strip-blobs-bigger-than 1M
機密種別の高精度検出は専用ツール併用
正規表現だけでは見逃しが発生するため、trufflehogやgitleaksといった機密検出専用ツールも必ず併用してください。「filter-repoで削除→gitleaksでスキャン→残った機密があれば再度削除」のサイクルが確実です。
検証:本当に消えたか機械的に確認
# 特定パターンの全履歴検索
git log --all -p -S "漏洩した文字列" | head
# 出力なしならOK
# 全blob内容を検索
git rev-list --objects --all | cut -d' ' -f1 | \
xargs -I{} sh -c "git cat-file -p {} 2>/dev/null | grep -l AKIA"
# gitleaksで全履歴スキャン
gitleaks detect --source . --verbose
# trufflehogで検証
trufflehog git file://. --only-verified
# ファイル存在確認
git log --all --full-history -- .env
# 空の出力なら完全削除
参照掃除とパック再構築
# reflogを即時無効化 git reflog expire --expire=now --all # gcで参照されないオブジェクトを物理削除 git gc --prune=now --aggressive # オブジェクト数が減ったか確認 git count-objects -v
注意:filter-repo実行直後はまだ古いオブジェクトが.git内に残っています。必ずreflog expire+gc --prune=nowで物理削除してからpushしてください。さもないとリモート側にも古いオブジェクトが転送されて機密が残ります。
リモート反映:ミラーforce push+チーム通知
# --mirror push で全ブランチ・タグ・refs/ を一括反映(推奨) git push --force --mirror origin # あるいは個別 git push --force origin --all git push --force origin --tags # --force-with-lease は --mirror と併用不可のため単純force # ただし事前に作業凍結して合意を取る
警告:ミラーpushはリモート側の全ての履歴を上書きします。事前にチーム全員に通知し、作業凍結期間を設けてから実行してください。並行して進行中のPRは一度クローズ、再cloneでブランチ作り直しを依頼します。
チームメンバーへの再同期テンプレート
# ========================================== # 【重要】リポジトリ repo の履歴書き換え完了 # ========================================== # # 概要: 機密情報コミットを履歴から除去 # 時刻: 2026-XX-XX HH:MM # # 各自、以下のいずれかで再同期してください: # # ■ 推奨:再clone # cd .. && mv repo repo.old # git clone https://github.com/org/repo.git # # ■ 既存を維持する場合: # git fetch --prune # git checkout main # git reset --hard origin/main # git reflog expire --expire=now --all # git gc --prune=now # # 作業中のブランチは以下を実行: # git rebase --onto origin/main 古い分岐元 feature/xxx # # 質問があれば #devops-incident へ
Git外のキャッシュ・ミラーもパージ
履歴書き換えが完了しても、Git外のキャッシュや派生物に機密が残っている可能性があります。見落としやすい箇所を順にチェックしましょう。
Git外で機密が残る代表箇所
- GitHub/GitLab内のキャッシュ:検索インデックス、API応答、アーカイブZIP
- fork/templateリポジトリ:元を消してもforkに残る
- Issue・PR・コミットコメント:本文に貼った文字列は別保存
- CI/CDのキャッシュ:GitHub Actions・CircleCI等のビルドキャッシュ
- Googleキャッシュ・Web Archive:インデックス済みページ
- デプロイ先サーバー:コピーされた.envファイル
- Slack等のチャット:貼り付けた出力メッセージ
GitHubへのキャッシュパージ依頼
# GitHub Support にキャッシュクリアを依頼 # https://support.github.com/contact # # リクエスト例: # Subject: Request to purge cache for force-pushed repository # Body: # Repository: github.com/owner/repo # Issue: Force-pushed to remove accidentally committed secrets. # Request: Please purge cached references and views to prevent # retrieval of removed commits. # Specific SHAs (optional): abc1234, def5678 # forkが多数ある場合 # → fork元から削除してもforkは残る # → 各fork所有者に連絡して削除依頼(legal escalation も検討)
重要:機密コミットのSHAはforce push後もしばらくGitHub APIで取得可能です(https://github.com/org/repo/commit/abc1234)。GitHub Support経由のキャッシュパージが完了するまで、数日〜数週間かかる場合があります。rotate完了を最優先にし、キャッシュクリアは事後清掃として進めましょう。
再発防止:機械的ガードで水際対策
人的対策(確認する・気を付ける)は必ず漏れます。commit前の自動検出とCI層での二重チェックで機械的に止める体制を作りましょう。
pre-commit hookで手元から止める
# pre-commit フレームワークの導入
pip install pre-commit
# .pre-commit-config.yaml を作成
cat > .pre-commit-config.yaml << "EOF"
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
name: Detect hardcoded secrets
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: detect-private-key
- id: detect-aws-credentials
- id: check-added-large-files
args: [--maxkb=500]
EOF
# チーム全員に適用
pre-commit install
# これ以降commit時に自動スキャンが走る
CI側での二重チェック
# .github/workflows/gitleaks.yml
name: Secret Scanning
on:
push:
branches: [ main, develop ]
pull_request:
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 }}
GitHub Secret Scanning(無料で使える)
GitHub Secret Scanningの活用
- Public repo:自動で機密検知、プロバイダ(AWS等)に通知される
- Private repo:GitHub Advanced Securityまたは無料版(2023〜)で有効化
- Push Protection:commit段階で機密検知時にpushをブロック
- Settings → Code security and analysis → Secret scanning
.gitignore テンプレート
# 環境変数ファイル .env .env.* !.env.example !.env.template # 認証情報 credentials/ secrets/ *.pem *.key *.p12 *.pfx id_rsa* id_ed25519* # AWS .aws/credentials .aws/config # GCP *-service-account.json # ターミナル履歴 .bash_history .zsh_history
ポイント:「コードに機密を書かない」運用設計が最強の予防策です。環境変数+シークレットマネージャ(AWS Secrets Manager、1Password、Doppler、HashiCorp Vault等)で機密は中央管理し、.env.exampleを配布してメンバーにローカル設定を促すのが現代的なベストプラクティス。
インシデント対応:社内報告とタイムライン作成
機密漏洩は技術的な対応だけでは完了しません。社内規程・顧客への説明・コンプライアンス上の報告義務にも配慮が必要です。以下はインシデントレポートのテンプレートです。
# セキュリティインシデント報告書 ## 概要 - 発生日時: 2026-XX-XX HH:MM - 発覚日時: 2026-XX-XX HH:MM - 影響範囲: [Public / Private / 社内限定] - 漏洩した機密: [AWS IAM Key / DBパスワード / JWT署名キー 等] - 影響リポジトリ: org/repo - コミットSHA: abc1234 - コミット者: [名前] ## タイムライン - T+0分: 漏洩を発覚 - T+2分: 認証情報をrotate完了 - T+10分: チーム通知+作業凍結 - T+30分: filter-repoで履歴削除完了 - T+45分: force push完了 - T+60分: GitHub Support にキャッシュパージ依頼 - T+2h: 不正使用痕跡のログ監査(AWS CloudTrail等) - T+1日: GitHub Support からパージ完了通知 ## 影響調査 - 漏洩鍵の不正使用痕跡: [あり/なし/調査中] - 顧客データへの影響: [あり/なし] - GDPR/PCI-DSS等の報告義務: [該当/非該当] ## 再発防止策 1. pre-commit hookにgitleaks導入(X月Y日まで) 2. CI層で二重スキャン実装 3. GitHub Secret Scanning + Push Protection 有効化 4. .env をすべて Secrets Manager 経由に移行 5. チーム向けに機密管理研修実施
コンプライアンス上の確認事項
- GDPR:個人情報が含まれる場合、72時間以内に監督機関報告が必要
- PCI-DSS:クレジットカード情報漏洩は厳格な報告手順あり
- SOC 2/ISO 27001:インシデント記録と対応の文書化
- 顧客契約:データ漏洩時の通知義務(契約書確認)
- 社内規程:情報セキュリティ責任者への報告
実践シナリオ
シナリオ① AWSキーをpublic repoにpushしたと発覚
# 0秒:AWS IAMでキー無効化 aws iam update-access-key --access-key-id AKIAXXXXX --status Inactive # 10秒:バックアップ取得 git clone --mirror https://github.com/org/repo.git repo.git cd repo.git # 1分:filter-repo で.env削除+値置換 git filter-repo --path .env --invert-paths --force # 2分:検証→force push git gc --prune=now --aggressive git push --force --mirror # 5分:GitHub Support にキャッシュパージ依頼+CloudTrail監査
シナリオ② .envファイルがコミットされ続けていた(数週間分)
# まず全値をrotate(数週間分なので複数キー) # 履歴から.env完全削除 git filter-repo --path .env --invert-paths # .gitignoreに追加 echo ".env" >> .gitignore git add .gitignore git commit -m "chore: .envをignore" # force push git push --force --mirror # pre-commit導入で再発防止 pre-commit install
シナリオ③ JWT署名キーを漏洩(全ユーザーログアウト必須)
# 1. 新しい署名キーを生成 # 2. 全ユーザーのJWTを無効化(DBのtoken_version++等) # 3. 履歴から署名キー削除 git filter-repo --replace-text jwt-key-replacements.txt # 4. 全ユーザーに再ログインを案内
シナリオ④ 社員のSSH秘密鍵を誤コミット
# 1. 当該社員のSSH公開鍵を全サーバーのauthorized_keysから削除 # 2. 社員は新しい鍵ペアを生成、公開鍵を再配布 # 3. 履歴から秘密鍵ファイル削除 git filter-repo --path-glob "**/id_rsa*" --invert-paths git filter-repo --path-glob "**/id_ed25519*" --invert-paths # 4. ログ監査(SSHログから不正ログイン確認)
やってはいけない落とし穴
rotate前に履歴削除を始める
「履歴を消せば安全」は誤り。既にbotや第三者に取得された機密は履歴削除では消えません。必ずrotate(無効化)を最優先で行い、時間差で履歴削除を進めてください。
ローカル作業のまま検証せずpush
filter-repo後にgitleaks/trufflehogでの再スキャンを省略すると、見落としがそのままpushされます。必ず機械的検証を挟んでからforce pushを。
–mirrorを使わず通常cloneで作業
通常cloneだと他ブランチ・タグの書き換えが漏れて機密が残ります。必ず--mirrorでクローンして全参照を扱いましょう。
git filter-branchで無理に処理
遅くて壊れやすく、公式で非推奨です。処理時間が長いとミスも増えます。filter-repoに移行してください。
forkリポジトリの存在を忘れる
force pushしてもforkには旧履歴が残ります。public repoでfork数が多い場合、各fork所有者に連絡+GitHub Support経由でパージ依頼が必要になる場合も。forkの扱いは事前に確認を。
インシデント報告を怠る
技術的に履歴を消しても、コンプライアンス上の報告義務を怠るとより重大な問題になります。特にGDPR/PCI-DSSに該当する情報漏洩は期限付きの報告義務があるため、情報セキュリティ責任者・法務担当と連携しましょう。
よくある質問
git filter-repo一択です。Git公式推奨、高速、多機能、活発なメンテ体制。BFGもシンプル用途には使えますが、最近更新頻度が落ちているため将来性を考えるとfilter-repoが有利。gitleaks detectとtrufflehog gitを並行して実行、両ツールで見つからなければかなりクリーン。不安なら専門のセキュリティレビュー(社内/外部)を依頼するのが確実です。関連記事
- 【Git】pushを取り消す方法 — push後の一般対処と
--force-with-lease - 【Git】管理からファイルを外す方法 —
.gitignore+rm --cachedで未来の混入を防ぐ - 【Git】.gitignoreが効かない原因と解決方法 — 追跡済み機密ファイルの解除
- 【Git】巨大ファイルを誤ってコミットしたときの削除方法 — 関連する履歴クリーン
- 【Git】コミットメッセージの変更方法 — コミットメッセージ内の機密削除
- 【Git】pushしようとしたら”non-fast-forward”で拒否されたときの解決方法 — force push関連
- 【Git】コミットの取り消し方 — 軽微な取り消し全般
- 【Git】よく使うgitコマンドまとめ — 日常コマンドの早見表
まとめ
- 最優先はrotate(無効化/再発行)——履歴削除は二次対応
- Public/Private/Enterprise別の被害評価を最初に
- ツールは
git filter-repo一択(BFGも可、filter-branchは非推奨) - ミラークローン→削除→
reflog expire+gc→--force --mirrorpush - 検証は
gitleaks+trufflehogの2本立てで確実に - Git外のfork/キャッシュ/CI/デプロイ先もパージ対象
- 予防:pre-commit+CI+GitHub Secret Scanningの三重防御+Secrets Manager化
- GDPR/PCI-DSS等のコンプライアンス報告義務を忘れずに
機密情報の誤コミットは「起きないように」ではなく「起きた時に最小被害で封じ込める」仕組みで防ぎます。rotate最優先で被害を止め、git filter-repoで履歴を清掃、gitleaks等で検証、チームへの周知まで一貫して完遂すれば、実運用でも確実にリスクを抑えられます。事後は必ずpre-commit+CI+Secret Scanningの三重防御を導入し、二度目の事故を防ぐ体制へ進化させましょう。漏洩は技術だけでなくプロセスの問題です。

