GitHubのリポジトリに、APIキーが漏れていないか。
今のファイルだけでなく、過去の全コミットまで確認しました。
先に結論を書くと、キーを消したコミットがあっても、消す前のキーは履歴に残ります。今のファイルを検索するだけでは、それが見つかりません。
自分の非公開リポジトリ5つは、履歴まで検索して、調べたキーの形に一致する追加行は0件でした。
🎥 この記事の内容は動画でも解説しています。
APIキーそのものの説明は、APIキーとは?の記事に書いています。
この記事は、確認のしかたの話です。
Xの投稿が、ずっと気になっていた
前に、Xで「GitHubでAPIキーを検索すると、公開されたキーが大量に見つかる」という投稿を見ました。
そのときも自分のリポジトリを点検して、0件でした。
でも、どうやって調べたのか、手順が手元に残っていませんでした。
0件と言われても、何を探して0件だったのかが分からない。
それがずっと気になっていたので、手順ごとやり直しました。
点検は、Claude Codeにやらせてます。
本番の5つのリポジトリには、読むだけのコマンドしか使っていません。中身は何も変えていません。
点検したのは、非公開のリポジトリ5つ
cat’s handで使っているリポジトリは5つで、どれも非公開(private)です。
- 動画制作(コミット45件)
- note記事(6件)
- YouTubeのアップロード用スクリプト(15件)
- 今のWordPressテーマ(28件)
- 前に使っていたCocoonの子テーマ(89件)
コミットは、全部で183件です。
非公開なら、見られないから大丈夫じゃない?
あとで公開に切り替えたら、履歴ごと見えるからね。
消したキーが履歴に残るか、偽のキーで試した
本番のリポジトリを調べる前に、確認のしかたが合っているかを試しました。
別に使い捨てのテスト用リポジトリを作って、そこに偽のキーをコミットしています。
- 設定ファイルに、偽のキー(
sk-proj-FAKE…)を書いてコミットする - 次のコミットで、そのキーを消す
- 今のファイルと、履歴の両方を検索する
今のファイルを検索するgit grepでは、0件でした。
キーはもう消してあるので、当然です。
git grep 'sk-proj'
(何も出ない)
履歴を検索するgit logでは、見つかりました。
git log -p --all -G 'sk-proj'
キーを足したコミットと、消したコミットの両方が出てきて、消す前のキーがそのまま読めました。
消したつもりでも、履歴には残っている、ということです。
-pは変更の中身を出すオプション、--allはすべてのブランチを対象にするオプション、-Gは、その文字列が増えたり減ったりしたコミットだけに絞るオプションです。
本番のリポジトリは、キーの形で探した
本番のリポジトリでは、キーの値は分からないので、「キーの形」で探しました。
まずは、先頭の文字に特徴があるキーです。
- OpenAI:
sk-で始まる - Anthropic:
sk-ant-で始まる - GoogleのAPIキー:
AIzaで始まる - GitHubのトークン:
ghp_かgithub_pat_で始まる - AWS:
AKIAで始まる
あわせて、Slackのトークン(xoxで始まる)、秘密鍵(BEGIN PRIVATE KEY)、OAuthのclient_secret・refresh_tokenの値も探しました。
やり方は、git log -p --allで表示される変更から、追加された行だけを、上の形で検索する方法です。
Claude CodeがPythonの小さなスクリプトを書いて調べていて、結果には、見つかった値そのものは出さず、リポジトリ名・ファイル名・件数だけを出すようにしました。
万一見つかったときに、画面やログにキーが残らないようにするためです。
スクリプトはこの記事に載せていませんが、件数だけ数える同じ考え方のコマンドは、下の手順に書いています。
結果は0件。引っかかった1件は、ただのファイル名だった
5つのリポジトリ、計183件のコミットを対象にした今回の検索では、指定した形に一致する追加行は0件でした。
ちなみに、git log -pは、ふだんの表示ではマージコミットの変更を省きます。
今回の5つは、マージコミットが1件もなかったので、この点の取りこぼしはありません。
マージをよく使うリポジトリなら、git log -p --all -mのように-mを付けると、マージコミットの変更も出ます。
あわせて、「危なそうな名前のファイル」が履歴に入ったことがないかも見ました。.envや、名前にtoken・client_secret・credentialsが入ったJSONなどです。
JSONなどに絞ったこの点検では、1件だけ引っかかりました。
動画制作のリポジトリにある、claude-code-token-usage-breakdown.json。
tokenって入ってる!
それ、AIの使用量の動画の台本。
Claude Codeの使用量(トークン)についての動画の台本で、認証とは関係ありませんでした。
名前だけで探すと、こういうものも混ざります。引っかかったら、中身を開いて確かめます。
キーは、そもそもGitに入れない場所に置いている
少なくともYouTubeの認証情報は、Gitに入れない場所に分けていました。
- YouTubeのアップロード用スクリプトのリポジトリは、
.gitignore(Gitに入れないファイルの一覧)にclient_secret_*.jsonを入れている - YouTubeの認証トークンは、Gitで管理していない別のフォルダに置いている
ただ、.gitignoreに足す前に一度コミットしていたら、そのファイルは履歴に残ります。.gitignoreは「これから入れない」ための設定で、過去のコミットは消してくれません。
自分のリポジトリで確かめるなら
先に注意です。
履歴を検索すると、本物のキーが見つかった場合、その値が画面にそのまま出ます。
画面を共有しているときや、AIに結果を貼るときは気をつけてください。
まずは件数だけ数えて、1件以上あったときに中身を見ると安心です。
自分のリポジトリで試すなら、この順番です(sk-などは、使っているサービスのキーの先頭に変えてください)。
- Gitで管理している今のファイルを検索する:
git grep -c 'sk-'(ファイルごとに、一致した行数が出る) - 手元にある全履歴で、
sk-を含む追加行の数だけ数える(キーの個数ではなく、一致した行の数):git log -p --all --format= | grep '^+' | grep -v '^+++' | grep -Ec 'sk-'(grep -v '^+++'は、差分のファイル名の行を外すため) - 1件以上あったら、まずどのコミットかだけ見る:
git log --all -G 'sk-' --format='%h %s'(ここではまだ値は出ない) - 履歴に入ったことのあるファイル名から、危なそうな名前を探す:
git log --all --name-only --format= | sort -u | grep -Ei 'env|token|secret|credential' - 引っかかったら、本物のキーかどうかを確かめる。今のファイルなら中身を開き、過去のコミットにしかないものは
git log -p --all -G 'sk-'やgit log -p --all -- ファイル名で変更を見る(ここで値が表示されるので、画面の共有や記録を止めてから)
2番のコマンドは、テスト用リポジトリ(偽のキーを入れて消したもの)では1件、自分の動画制作のリポジトリでは0件と、どちらも想定どおりの件数が出ました。sk-のように短い形だと、関係ない文字列も数えることがあるので、件数が出たら3番で中身を確かめます。
4番のファイル名の検索は、さっきの点検より広い条件なので、自分の動画制作のリポジトリでは、名前に「token」が入った動画のファイルが4件出ました。どれも認証とは関係ない、AIの使用量の動画でした。
もし本物のキーが見つかったら、まず、そのキーを各サービスの管理画面で無効にして、作り直します。
履歴からファイルを消しても、すでにコピーされていたら止められないからです。
専用の検出ツール(gitleaksなど)もありますが、今回は入れていません。
Gitと、Macに最初から入っている基本的なコマンドで、ここまでは確かめられました(本番の点検では、Claude Codeが書いたPythonのスクリプトも使っています)。
💡 今回の点検の範囲
調べたのは、自分の非公開リポジトリ5つで、手元に取り込んである全ブランチのコミットです(--allは手元にある参照が対象なので、取り込んでいないリモートのブランチは含みません)。探したのは、上に書いたキーの形だけなので、それ以外の形式のキーやパスワードは対象外です。0件は「この形では見つからなかった」という意味で、漏れが絶対にないとまでは言えません。
まとめ
キーを消したコミットがあっても、消す前のキーは履歴に残ります。git grepで0件でも、git log -p --allで見ると出てくることがある。
自分のリポジトリは5つ・183コミットで0件でしたが、確かめるなら、今のファイルと履歴の両方を見てください。