Claude Media
Claude Codeでシークレットをコミットした後の対応手順 — 失効から履歴除去まで

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

    1. 失効とローテーション

    提供元の管理画面でキーを無効にし、新しいキーを発行します。履歴の掃除より先に終えます。

  2. 2

    2. 使われた形跡の確認

    提供元の利用ログで、漏れていた期間に心当たりのない利用がないかを見ます。

  3. 3

    3. 手元で履歴を書き換える

    新しいクローンでgit filter-repoを実行し、除去できたかを検索で確かめます。

  4. 4

    4. サーバーへforce push

    全てのrefを上書きします。GitHubではrefs/pull/*だけ別の手順が要ります。

  5. 5

    5. 他のクローンを掃除する

    共同作業者には再クローンを頼みます。git pull && git pushは禁物です。

  6. 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 origin

git 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のマニュアルにあります。

  1. 全てのタグを削除してからgit fetch --prune --tagsを実行する。タグを消さずに取得すると、古いタグが残って値も残る
  2. 手元の変更を、新しい履歴の上にrebaseする。mergeは禁物
  3. 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 --mirrorClaude 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で人間の確認に残しておく構成が、取り返しのつかない操作を避けやすい形です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/permissions
  2. code.claude.com/docs/en/settings-reference
  3. github.com/newren/git-filter-repo
  4. docs.github.com/en/authentication/keeping-your-account-and-data-secure/rem…
この記事を共有:XはてブLinkedIn