AIに渡す許可には射程がある
前回、Claude Codeに設定のバックアップを任せたらGCPの鍵をGitHubに漏らした話を書いた。最後はこう締めた。間違える前提で安全網をgit層の仕組みに置く。そして間違いを mistakes.md という外部記憶に刻んで、次に先回りで避けさせる。授業料としては安い方だ、と。
その翌日、同じ失敗をもう一度やられた。やられたのは鍵漏洩じゃなくて、確認なしのpushのほうだ。
翌日、また確認なしでpushされた
やっていたのは別の作業だった。dotfilesのバックアップスクリプトが commands/ を取りこぼしていたので、worklogなどの自作コマンドをバックアップ対象に足す。中身としては正しい修正だった。
問題はその出し方だ。Claude Codeは「コミット&プッシュします」と宣言して、そのまま実行した。俺はpushしていいとは一言も言っていない。「dotfilesにいれて」と頼んだだけだ。
これは前回 mistakes.md に刻んだはずの失敗だった。
2026-06-21: ユーザーの明示許可なくリモートへ git push した
Correct Action: push は依頼の延長でも、実行していいと明示確認を取ってから
Trigger: git push、または定期 push の仕組みを作るとき記録した次のセッションから、この一行を読んでから動く。そう書いた。なのに翌日、別のリポジトリで同じことが起きた。記憶に変えたはずの失敗が、記憶を素通りして再発した。
鍵は止まった。でも別の穴が空いていた
おもしろいのは、前回入れたガードはちゃんと機能していたことだ。
鍵漏洩を止めるために、git層のpre-commitフックを入れた。sa.json や *.pem のような危険なファイル名で弾き、中身も実トークンの形でスキャンする。このフックは今も生きている。今回の作業に鍵は絡まなかったので出番はなかったけれど、形の決まった失敗を物理で止める仕組みは、そのまま残って働ける状態にある。
止められなかったのは、確認なしpushのほうだ。
ここで気づく。前回俺が立てた防御は、二種類の失敗のうち一種類しか塞いでいなかった。鍵を漏らすという「形の決まった失敗」はgit層のフックで止まる。BEGIN PRIVATE KEY という文字列が混ざる、という具体的で機械判定できる事象だからだ。でも「確認を取らずにpushする」は形が決まっていない。どのリポジトリでも、どんな依頼の文脈でも起こりうる。ファイル名でも中身でも判定できない。
だから前者は機械のガードに、後者は mistakes.md という記憶に守らせた。設計としては正しい。なのに記憶のほうが破られた。
なぜ記憶は効かなかったか
破られた理由を掘ると、失敗が形を変えていた。
前回の失敗は「pushしていいか聞き忘れた」だった。だから mistakes.md には「push前に明示確認を取れ」と書いた。ところが今回の失敗はそれと微妙に違う。Claude Codeはおそらく、自分には許可があると思っていた。直前に別のリポジトリで「プッシュして」と言われていたからだ。
その許可を、別のリポジトリの別の作業にまで勝手に引き継いだ。「さっきpushしていいと言われた」→「だからこのpushもしていい」。聞き忘れたんじゃない。一度もらった許可を、勝手に延長した。
記録した教訓は「聞き忘れるな」だった。でも実際に起きたのは「許可を持ち越すな」だった。同じ「確認なしpush」という結果でも、原因の粒度が違う。記憶は、記録したときの粒度を超える変異までは捕まえられなかった。
許可には射程がある
ここでようやく、本当の論点にたどり着く。許可には射程がある、ということだ。
人間どうしなら暗黙に共有している。「このファイル、pushしといて」と言ったとき、その許可が効くのは、そのリポジトリの、その変更の、その場限りだ。10分後の別の作業や、隣のリポジトリには及ばない。射程を口に出す必要すらない。
AIはこの射程を平気で踏み越える。一度「していい」と言われた操作を、文脈をまたいで再利用する。しかもAIの側では筋が通っている。さっき許可されたんだから、と。悪意でやっているわけじゃないぶん、止めにくい。
依頼の言葉そのものにも、射程がある。「いれて」「保存して」「管理して」は、変更を作れという指示であって、リモートに送れという許可ではない。ローカルで完結する作業と、外に出す作業のあいだには境界がある。その境界を、依頼の勢いで飛び越える。
整理するとこうなる。許可の射程は、少なくとも三つの軸で区切られている。
- リポジトリ … repo Aの許可は repo Bに及ばない
- 作業 … この変更への許可は、次の変更に及ばない
- 操作の種類 … 「作る」許可は「送る」許可ではない
前回俺が守れたのは三つめだけだった。鍵を「送る」ことはフックで止めた。でもリポジトリと作業をまたいだ持ち越しは、まったく想定していなかった。
二段構えに作り直す
対策は、破られた側の粒度を上げることだった。
mistakes.md の記述を「push前に確認しろ」から一段具体的にした。
Correct Action: 「いれて」「保存して」「管理して」等はコミット/push の許可ではない。
変更を staged 状態で見せ、「コミットして push していいか」を都度確認してから実行する。
ある操作への許可は、そのrepo・その作業限りで、次に持ち越さない。
Trigger: git commit / push する直前。特に直前の別作業で push 許可をもらっている場合ほど注意「次に持ち越さない」を明文化したのがキモだ。前回は結果だけ記録していた。今回は再発のしかた(許可の持ち越し)まで含めて書いた。同じ失敗を二度やられて、ようやく記録の解像度が実態に追いついた。
ただ、記憶の解像度を上げるだけでは弱い。記憶は読まれなければ効かないし、今回みたいに変異すればすり抜ける。だから本当に守りたい操作は、結局git層に落とすのが堅い。このブログのリポジトリには confirm-master-push.sh というフックを入れてあって、master へのpushは機械的に一拍置く。AIが筋を通そうが通すまいが、コミットやpushという操作そのものに割り込むので、必ず発火する。
二種類の失敗には、二種類の守りがいる。形の決まった失敗(鍵混入、特定ブランチへのpush)は機械のガードで物理的に止める。形の定まらない失敗(許可の踏み越え)は記憶で先回りする。ただし記憶は変異に弱いから、刺さるなら機械側へ寄せる。前回はこの仕分けが甘かった。確認なしpushを記憶だけに任せて、git層に落とさなかった。
結び
正直、前回「授業料としては安い」と書いたのは早すぎた。翌日にもう一度払わされたんだから。
ただ、二度目で見えたものはある。AIに自動化を任せるとき、危ないのは派手なやらかしより、一度OKを出した操作を勝手に広げてくる挙動のほうだ。鍵漏洩みたいな分かりやすい事故は、形が決まっているぶん機械で止められる。やっかいなのは「さっき許可されたから」と射程を伸ばしてくるやつで、これは本人の中で筋が通っているから、止めるには射程を先に切っておくしかない。
許可は、出した瞬間に有効期限と適用範囲を持つ。次の作業には持ち越さない。隣のリポジトリには効かない。「作る」と「送る」は別物。当たり前のことなんだけど、AIに任せるなら、この当たり前をいちいち仕組みにしておかないと足をすくわれる。たぶん、まだ何回か払う。