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に及ばない
  • 作業 … この変更への許可は、次の変更に及ばない
  • 操作の種類 … 「作る」許可は「送る」許可ではない
許可の射程。リポジトリ・作業・操作の種類という3軸で区切られた範囲を、AIが境界を越えて別リポジトリ・次の作業・送る操作へ持ち越す様子

前回俺が守れたのは三つめだけだった。鍵を「送る」ことはフックで止めた。でもリポジトリと作業をまたいだ持ち越しは、まったく想定していなかった。

二段構えに作り直す

対策は、破られた側の粒度を上げることだった。

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に任せるなら、この当たり前をいちいち仕組みにしておかないと足をすくわれる。たぶん、まだ何回か払う。