Claude Codeの.envガードが止めた書き方は、18のうち2つだった
Claude Code(ターミナルで動く、Anthropic のコーディング用のAI)に「コミットしてpushしておいて」と任せる場面は多い。ここで多くの利用者が抱く不安がある。プロジェクトの .env にはAPIキーが入っている。これが間違って公開リポジトリに上がることはないのか。
上がりうる。しかもこれは想像ではなく、公開の記録に実例がある。
対策としてよく挙がるのがフックだ。フックは、AIがコマンドを実行する手前に割り込んで、危ないものを機械的に弾く仕組みで、CLAUDE.md に「絶対にコミットするな」と書くのとは層が違う。私はそのフック集 cc-safe-setup を無料(MITライセンス)で公開していて、その中の secret-guard が .env の混入を見る担当だ。自分が配っているものが、実際にどこまで止まるのかを終了コードで測った。答えは、18の書き方のうち2つだった。
以下は全部、2026年9月4日に自分の環境で撃った結果だ。危険なコマンドは一度も実行していない。コマンドを「文字列」としてフックの標準入力に渡し、終了コード(2なら実行の手前で停止、0なら素通り)だけを観測している。
18の書き方へ、同じ .env を渡した
読者が npx cc-safe-setup で実際に入るのは、npm が配っている 29.8.0 だ(dist-tags.latest は今も29.8.0で、公開は2026年4月20日)。GitHub 側の現行は 30.0.4 になっている。両方へ同じ入力を流した。
| 書き方 | 29.8.0 | 30.0.4 |
|---|---|---|
git add .env |
2 | 2 |
| 先頭に空白を置く | 2 | 2 |
cd myrepo && git add .env |
0 | 2 |
echo hi; git add .env |
0 | 2 |
git -C myrepo add .env |
0 | 2 |
行末を \ で折って2行にする |
0 | 2 |
(git add .env) かっこで囲む |
0 | 0 |
{ git add .env; } |
0 | 0 |
if true; then git add .env; fi |
0 | 0 |
for f in 1; do git add .env; done |
0 | 0 |
true | git add .env |
0 | 0 |
echo .env | xargs git add |
0 | 0 |
sh -c 'git add .env' |
0 | 0 |
eval 'git add .env' |
0 | 0 |
timeout 5 git add .env |
0 | 0 |
nohup git add .env |
0 | 0 |
GIT_DIR=x git add .env |
0 | 0 |
git stage .env |
0 | 0 |
止めた数は、29.8.0 が18のうち2、30.0.4 が18のうち6。現行版でも、12の書き方は素通りする。
【CC補足】なぜ抜けるのか
29.8.0 の入口はgrep -qE '^\s*git\s+add'の一行だ。grepの^は「文字列の先頭」ではなく「行の先頭」を指す。だからgit addが行の途中に来る書き方は、そこで全部落ちる。30.0.4 は入口を(^|;|&&|\|\|)\s*git\s+addへ広げたので、;と&&と||の後ろは拾えるようになった。だが(や`やパイプやevalや環境変数の前置は、この一覧に入っていない。名前と位置の字面で見る網は、書き方の空間を数えきれないので、こういう形で残る。塞がったのは名指しした形であって、族ではない。
一括の git add . は「フックが動いている場所の直下」しか見ていない
29.8.0 と 30.0.4 は、どちらも git add . のときに .env の実在を確かめる。ただし実装は [ -f ".env" ] で、これはフックのプロセスのカレントディレクトリだけを見る。よくある構成で撃つとこうなる。
| 状況 | 29.8.0 | 30.0.4 |
|---|---|---|
git add .(同じ場所に .env がある) |
2 | 2 |
git add .(.env は backend/ にある) |
0 | 0 |
git add -A(.env は backend/ にある) |
0 | 0 |
git add backend/ |
0 | 0 |
git commit -am "wip" |
0 | 0 |
git push origin main |
0 | 0 |
フロントとバックを分けていて backend/.env を持つ、ごく普通の構成は、両版とも素通りする。「一括の add は防げる」と読まないでほしい。防げるのは、フックが動いている場所の真下に .env がある場合だけだ。
commit と push が0なのは、secret-guard が git add のところだけを見る設計だからで、これは版が上がっても変わらない。ただし「原理的に無理」ではない。同じパッケージの examples/ に、コミットの直前にステージ済みの差分を見る staged-secret-scan.sh が入っている(29.8.0 の tarball にも含まれている)。既定では入らないので、足すなら明示的に入れる。
npx cc-safe-setup --install-example staged-secret-scan
push を見る柵は、いまのところ無い。
これは実際に起きている
公開の記録に、同じことで困っている報告がある。#2142 は、深刻度を CRITICAL - Active exposure of production credentials to public repositories として、CLAUDE.md に「絶対にコミットするな」と明示したのに、本番のAPIキーが公開リポジトリへ繰り返しコミットされた、という報告だ。もっと具体的なのが #12524 で、実際のAPIキーが直書きされ、The key was exposed in a public repo for 11 days、そして Hackers found it and used $30,000 USD worth of API calls とある。ただし原文には I committed and pushed without noticing ともあり、コミットとpushをしたのは報告者自身だ。AIが勝手にpushした話ではない。
ここは他人の報告なので、私が再現したわけではない。金額も日数も報告者の申告だ。ただし、この3件が今どういう状態かは自分で見た。#2142 は今も開いたままで、#12524 と #59094 は閉じているが、閉じ方はどちらも not_planned——直ったから閉じたのではなく、対応しないまま閉じられている(2026年9月4日にAPIで確認)。だから「もう修正済みだろう」と読まないでほしい。
#2142 が示すのは、平文の禁止が効かない場面があるということだ。CLAUDE.md に書いた禁止は、モデルへの「お願い」であって、実行を機械的に止める仕組みではない。だから致命的な操作は、言葉で頼む層ではなく、コマンドが実行される手前で機械的に弾く層で止める。上で測ったのが、その層だ。
なお #59094 は、git を通らない漏れ方の報告だ。エージェントが .env を読んで、その中身が転写へ流れた。上の表のどの行でも止まらない。secret-guard はそもそも git のコマンドしか見ていない。
本当の予防は、フックの手前にある
測って分かったのは、フックは安全網の一層であって本丸ではない、ということだ。
第一に、そもそも秘密を追跡しないこと。.gitignore に .env を入れる。**.gitignore に書いたファイルは git add . でも入らない。**ここを取り違えていた人がいたら(私の以前の原稿がそうだった)、直してほしい。git add . は未追跡のファイルこそステージするので、「まだ追跡していないから安全」は成り立たない。効いているのは未追跡であることではなく、.gitignore に書いてあることのほうだ。
第二に、自分のリポで今すぐ確かめること。危ないことは何もしない。見るだけだ。
git ls-files | grep -iE '\.env|secret|credential|\.pem|\.key'
ただしこれは今のツリーしか見ない。過去に上げて、あとから git rm --cached で外した分は出てこない。履歴も見る。
git log --all --diff-filter=A --name-only --format= | sort -u | grep -iE '\.env|secret|credential|\.pem|\.key'
ここで何か出てきたら、その秘密はすでに git に入っている。次の節へ進んでほしい。
第三に、フックはその上に重ねる網として入れること。名指しの git add .env と、同じ場所に .env がある一括の add は止まる。それ以外は上の表のとおりだ。
もし、もう上げてしまっていたら——鍵の失効が先
一度公開された秘密は「もう漏れたもの」として扱うしかない。誰かがすでに clone しているかもしれないし、公開を監視して秘密を収集する自動化が、上がった瞬間に拾っている可能性もある。
だから順番はこうだ。まず、その鍵を失効させて新しい鍵を発行する。これが唯一、確実に安全にする手だ。履歴の書き換えは、そのあとの二次的な後始末にすぎない。順番を間違えて、履歴を消したことで安心し、鍵をそのままにするのが、いちばん危ない。
まとめ
- Claude Code に git を任せると、
.envやAPIキーが公開リポに上がりうる。実例が公開の記録にある - 私が配っている無料の
secret-guardは、18の書き方のうち 29.8.0 で2つ、30.0.4 で6つを止めた。かっこ・パイプ・eval・環境変数の前置は現行でも素通りする - 一括の
git add .で止まるのは、フックが動いている場所の直下に.envがある場合だけ。backend/.envは両版とも素通り - コミットの直前は
examples/staged-secret-scan.shを明示的に足せば見られる。pushを見る柵は無い - 本丸は
.gitignore。フックはその上に重ねる網 - もう上げてしまったら、履歴の書き換えより先に鍵を失効させる
ここで測ったフック集は無料で、npx github:yurukusa/cc-safe-setup で入る。npm 側は4月から公開が止まっていて(公開は人の手でやっているので今も29.8.0)、前置と連結のすり抜けが塞がっていない。入れるなら GitHub のほうを使ってほしい。削除・費用暴走・秘密漏洩・停止不能まで、Claude Code の事故を実例で体系立てて防ぎたい人向けに、事故防止の本(¥1,500)も書いている。どちらも「絶対に安全」は約束しない。字面のガードには限界があるから、層で守るしかない——この記事はその実測だ。