Claude Codeでシークレットをコミットした後の対応手順 — 失効から履歴除去まで
APIキーなどをコミットしてしまったときの順序を、失効、git filter-repoでの履歴除去、force push、再発防止の順にまとめます。Claude Codeに任せる範囲と止める設定も扱います。
秘密情報をコミットしてしまったときの正しい順序は、先に失効とローテーション、その後に履歴の除去です。GitHubのドキュメントも、対象がパスワードやトークンなら最初の一歩は無効化または再発行だと書いています。失効した時点で、履歴に残った値は認証に使えなくなります。
この記事は、Claude Codeが書いたコードにAPIキーが混ざっていた場面を想定し、失効、git filter-repoでの履歴書き換え、force push、他のクローンの掃除、再発防止までを順に並べます。Claude Codeに任せてよい調査と、人間の確認を挟むべき操作の線引きも設定例つきで示します。
全体の順序 — 履歴を消す前に値を殺す
作業は6段に分かれます。履歴の書き換えは3段目で、失効より後です。
シークレットをコミットしたときの対応順
- 1
1. 失効とローテーション
提供元の管理画面でキーを無効にし、新しいキーを発行します。履歴の掃除より先に終えます。
- 2
2. 使われた形跡の確認
提供元の利用ログで、漏れていた期間に心当たりのない利用がないかを見ます。
- 3
3. 手元で履歴を書き換える
新しいクローンで
git filter-repoを実行し、除去できたかを検索で確かめます。 - 4
4. サーバーへforce push
全てのrefを上書きします。GitHubでは
refs/pull/*だけ別の手順が要ります。 - 5
5. 他のクローンを掃除する
共同作業者には再クローンを頼みます。
git pull && git pushは禁物です。 - 6
6. 再発防止
.gitignore、Readのdenyルール、pre-commit検査、push protectionを揃えます。
順序の根拠は単純です。履歴の書き換えは、コミットハッシュが変わり、共同作業者の作業を巻き込む重い操作です。その間も、漏れた値は有効なままです。先に値を殺しておけば、書き換えに数日かかっても被害は広がりません。
1段目と2段目 — 失効、再発行、利用ログの確認
失効の手順は提供元ごとに違うので、ここでは共通の考え方だけを置きます。
- 古いキーを無効にしてから、新しいキーを発行する。逆にすると、古いキーが有効な時間が延びる
- 新しいキーは、コードや
.envの追跡対象ではなく、シークレット管理の仕組みに置く - そのキーを使っているサービスを洗い出し、差し替え漏れで本番が止まらないようにする
Anthropic APIキーの場合は、無効化と削除の違い、支出上限の見方までClaude APIキーの漏洩対応にまとめています。ほかのクラウドやSaaSのキーでも、「止める、使われた形跡を見る、新しく持つ」の流れは同じです。
履歴の書き換えを始める前に、リポジトリが公開かどうかも確認します。公開リポジトリでは、コミットされた時点で第三者のスキャナーに拾われる前提で考えるほうが安全です。失効が済んでいれば、拾われた値は使えません。
履歴を書き換えるのは失効の後でよい理由
失効だけで済ませる選択もあります。GitHubのドキュメントは、履歴の書き換えに次の副作用があると列挙しています。
- 再汚染の危険: 古いクローンを持つ人が
git pullの後にgit pushすると、除去した値が戻る - 共同作業者の作業を失う危険: 掃除中に他の人がpushした変更は、force pushで消える
- コミットハッシュの変更: ハッシュに依存する自動化が壊れる
- ブランチ保護の一時解除: force pushを禁じている設定は、いったん外す必要がある
- クローズ済みPRの差分表示が壊れる
- コミットとタグの署名が外れる
さらに、GitHubサポートが扱うのは、認証情報のローテーションでリスクを下げられない場合に限られます。値を失効させたリポジトリで履歴の除去まで行うかは、副作用と、残す場合のリスクを天秤にかけて決める事柄です。パスワードのように、失効しても別の場所で使い回している値や、個人情報のように無効化できないデータなら、履歴除去の意味は大きくなります。
APIキーのように提供元で確実に無効化できる値は、失効だけで終える判断もあります。ただしスキャナーの検出が履歴に残り続け、以後のレポートがノイズになる点は考えに入れてください。
3段目 — git filter-repoで履歴から外す
履歴の書き換えにはgit filter-repoを使います。git filter-branchの代わりとして、BFG Repo Cleanerの代替でもあるツールです。必要なのはgit 2.36.0以上とpython3 3.6以上で、本体は1ファイルのPythonスクリプトです。後述の--sensitive-data-removalには2.47以上が要ります。
新しいクローンで作業する
git filter-repoは、新しいクローンでないリポジトリでの実行を中断します。ローカルにしか存在しない履歴を、誤って書き換えるのを防ぐための仕組みです。
落とし穴
--forceで安全確認を飛ばさない
マニュアルは、--forceを習慣的に付けることと、フォーラムなどで勧めることを強く戒めています。通らないときは、--forceを足すのではなく、新しいクローンを作り直してください。Claude Codeにコマンドを任せるときも、エラーを避けるために--forceを足させないよう指示します。
作業用のクローンを作ります。ローカルのファイルシステムから複製するなら、--forceではなく--no-localを付けるのが望ましいとマニュアルにあります。
brew install git-filter-repo
git clone https://github.com/YOUR-ORG/YOUR-REPO cleanup-repo
cd cleanup-repoファイルごと消す
.envのように、ファイル全体が秘密情報なら--invert-pathsと--pathの組み合わせです。履歴上のどの版も残さず、全ブランチとタグから外れます。
git filter-repo --sensitive-data-removal \
--invert-paths --path config/.envそのファイルが過去に別のパスにあった場合は、--pathを足すか、別のパスで2回目を実行します。名前の変更を追わないので、git log --all --name-status -- config/.envで履歴上の動きも確かめておくと漏れを防げます。
文字列だけ置き換える
コードの中に値が埋まっているなら、--replace-textで置換します。式の書かれたファイルを渡し、各行は既定でリテラル扱いです。regex:とglob:の接頭辞も使えます。==>の後に置換後の文字列を書けて、省略すると***REMOVED***になります。
sk_live_EXAMPLE_VALUE
regex:ghp_[A-Za-z0-9]{36}==>***TOKEN-REMOVED***git filter-repo --sensitive-data-removal \
--replace-text ../passwords.txt--sensitive-data-removalは、次の処理を追加します。
- 全てのrefを取得する。ブランチとタグ以外のrefに値が残る場合も掃除できる
- 最初に変わったコミットを記録して報告する
- Git LFSのオブジェクトが孤立したかを報告する
- 元のサーバーと他のクローンを掃除する手順を最後に表示する
ローカルにだけ存在する未pushの変更があると、refの取得で失われるおそれがあります。新しいクローンで作業していれば、この心配はありません。
除去できたかを検索で確かめる
書き換えのあと、push前に、値が履歴に残っていないことを自分で確認します。
git log --all --name-status -- config/.env
git log -S"sk_live_EXAMPLE_VALUE" --all -p --どちらも空であれば除去できています。まだ出てくるなら、追加のgit filter-repoを実行してから先へ進みます。ここがやり直せる最後の場所で、force pushの後は元に戻せません。
履歴を調べる作業は、Claude Codeに任せやすい部分です。git log -Sの出力を読み、除去漏れのブランチやパスを一覧にさせます。実行するのは読み取りだけで、書き換えはしません。
4段目 — force pushと、GitHubに残るもの
書き換えた履歴で元のリポジトリを上書きします。ブランチだけでなく全てのrefを送るために--mirrorを使います。
git remote add origin https://github.com/YOUR-ORG/YOUR-REPO
git push --force --mirror origingit filter-repoは、実行後にoriginのリモートを外します。古い履歴と新しい履歴を混ぜて、コミットが倍になる事故を防ぐためです。サーバーへ押し返すときは、上のように自分でリモートを足し直します。
このpushは、refs/pull/で始まるrefの更新に失敗します。GitHubが読み取り専用にしているためで、次の対応が要ります。
- 影響するPRの数は、
.git/filter-repo/changed-refsをgrep -c '^refs/pull/.*/head$'で数えて控える - GitHubサポートに連絡して、PRの参照の解除、サーバー側のガベージコレクション、キャッシュされた表示の削除を依頼する。連絡には、リポジトリ名、影響するPRの数、
git filter-repoが表示したFirst Changed Commit(s)を添える - フォークにも古いコミットが残る。フォークの持ち主に削除を頼むしかなく、GitHubは持ち主の連絡先を教えられない
- ブランチ保護が
pushを拒む場合は、一時的に外して再実行し、失敗がrefs/pull/*だけになるまで繰り返す
サーバー側の掃除がここで終わりではない点に注意してください。キャッシュ、PR、フォークのいずれかから、ハッシュを直接指定して値を読める状態が残る可能性があります。だからこそ、1段目の失効が最終的な防衛線になります。
5段目 — 他のクローンを掃除する
共同作業者のクローンは、あなたの側から掃除できません。いちばん確実なのは、全員に既存のクローンを捨てて再クローンしてもらうことです。
再クローンできない場合の手順は、git filter-repoのマニュアルにあります。
- 全てのタグを削除してから
git fetch --prune --tagsを実行する。タグを消さずに取得すると、古いタグが残って値も残る - 手元の変更を、新しい履歴の上に
rebaseする。mergeは禁物 git reflog expire --expire=now --allとgit gc --prune=nowで、古い履歴を捨てる。この操作でスタッシュも消えます
最後に、git filter-repoが表示した最初に変わったコミットのハッシュで、次を実行します。
git cat-file -t <最初に変わったコミットのハッシュ>fatalで失敗すれば、そのコミットは取り除かれています。commitと返るなら、タグの削除、rebase、reflogの失効、git gcをやり直します。
同じgit filter-repoコマンドを各自に実行させる方法は、マニュアルが明確に推奨していません。同じコマンドでも、各自のクローンでコミットのハッシュが一致するとは限らず、同じコミットが2本ずつ並ぶ状態になりかねないからです。
Claude Codeに任せる範囲と、止めておく操作
履歴の書き換えは取り消せないため、Claude Codeに自動で実行させる前に線を引いておきます。
| 作業 | Claude Codeに任せる | 理由 |
|---|---|---|
git log -Sで値の残存を探す | Claude Codeに任せる任せやすい | 理由読み取りだけで、結果を目で確認できる |
changed-refsの集計、影響PRの一覧化 | Claude Codeに任せる任せやすい | 理由出力を整理するだけ |
git filter-repoの実行 | Claude Codeに任せる指示を見てから | 理由新しいクローンかを確かめる必要がある |
git push --force --mirror | Claude Codeに任せる毎回確認する | 理由他の人の変更を消し、取り消せない |
git pushを確認制にするには、permissions.askにルールを置きます。askルールは、より具体的なallowルールがあっても確認が出ます。
{
"permissions": {
"ask": ["Bash(git push *)"],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}deny側は、.envとsecretsディレクトリの読み取りを止める例です。Readのdenyは、組み込みのファイルツールと、Bashの中でcatなどと認識されるコマンドに効きます。一方、grep -r pattern .のようにファイル名を挙げずに読むコマンドや、PythonやNodeのスクリプトが自分でファイルを開く場合には効きません。OSの層で止めるには、サンドボックスの有効化が必要です。
.envを読ませない設定の効く範囲は、Claude Codeで.envを読ませないRead denyの書き方で詳しく扱っています。
6段目 — 再発させない仕組み
履歴を片づけたら、同じ事故を止める層を重ねます。GitHubのドキュメントが挙げる対策と、Claude Code側の設定を組み合わせると、次のようになります。
- 追跡させないファイルは
.gitignoreに書き、変更をコミットして共有する - 秘密情報をコードに直書きせず、環境変数やシークレット管理サービスで実行時に渡す
- pre-commitフックに、
git-secretsやgitleaksのようなツールを組み込む。協力者にも設定を頼む - リポジトリのpush protectionを有効にし、秘密情報を含むpushを検出して止める
git add .とgit commit -aを避け、git diff --cachedでステージ済みの差分を見る- Claude Codeの
Readのdenyルールで、.envやsecrets/を読ませない
gitleaksをClaude CodeのPreToolUse hookとpre-commitの二段で置く手順は、Claude Codeでgitleaksを使うにあります。混入を止める役割で、混入後の対応は本記事の範囲です。秘密情報そのものを.envから外す選択肢として、1Passwordプラグインもあります。
まとめ
秘密情報のコミットは、値の失効が先、履歴の書き換えが後です。失効だけで足りるか、git filter-repoで履歴まで外すかは、値が無効化できるかと、書き換えの副作用を受け入れられるかで決まります。
履歴を書き換えるなら、新しいクローンで--sensitive-data-removalを付け、git log -Sで消えたことを確かめてからgit push --force --mirrorへ進みます。Claude Codeには調査を任せ、force pushはpermissions.askで人間の確認に残しておく構成が、取り返しのつかない操作を避けやすい形です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。