連載『Claude Codeで会社を回す』#5 ── 一人会社が Claude Code で回す AI役員チームの実録。人間(@noracorn)がレビューして公開しています。
AIに作業フォルダを丸ごと見せて、仕事を任せていた。ある朝、参照先の一覧をなんとなく眺めて、背筋が冷えた。いま自分は、金庫の鍵束ごとアシスタントに渡している。 取引先との生々しいやり取りも、検証で貼ったまま消し忘れたキーも、人に見せられない走り書きも、ぜんぶAIの射程に入っていた。
AIは悪意なんて持たない。でも、渡したものは渡したものだ。読めるようにした瞬間、それは「いつか出力に出てくるもの」になる。(この"いつか"が実際に来た話は、別記事「自分のメモを全部RAGに食わせたら、AIが顧客名を喋り出した」に書いた。あれは、線を引かなかった場合の答え合わせだ。)
前回(#4)はAIが書いたコードを別のAIに検品させる「何をさせるか」の話だった。今回はその手前――AIに何を"渡すか"。いや、何を"渡さないか"。
全部見せれば賢くなる、と思っていた
最初の衝動は「全部見せたほうが、賢く動くだろ」だった。作業フォルダごと参照させ、メモも議事録も設計資料も読めるようにする。実際、文脈が増えるほど回答は良くなる。気持ちいい。どんどん渡したくなる。そうやって、いつの間にか鍵束ごと握らせていた。
「危ないものを隠す」は、必ず漏れる
最初にやった線引きは、ブラックリスト方式だった。「危ないファイルだけ、AIから隠す」。
これがダメだった。実際、隠したつもりのリストから、後で書いたメモの社名が一行すり抜けた。理由は単純で、危ないものを全部数え上げるのは無理だから。今日隠しても、明日には新しいメモに社名を書くし、「あとで消す」と書いたキーを消し忘れる。隠し忘れは必ず出る。そして一回でも漏れれば、それで終わりだ。
セキュリティで「全部列挙して塞ぐ」が負けパターンなのは、昔から知られている。塞ぐ側は全部を塞がないと負け、漏らす側は一つ見つければ勝ち。 この非対称に、ブラックリストは勝てない。
だから発想をひっくり返した。隠すんじゃない。最初から"渡さない場所"を作って、そこ以外を渡す。
やったこと: 「渡さないゾーン」を1つ決めて、入口を全部塞ぐ
機密は、あちこちに散らさない。「ここに置いたものはAIにも世界にも出さない」という専用ゾーンを1つ決める。 顧客の情報、金額、生の会議ログ、認証情報。出したくないものは、全部そこに集める。
そのうえで、ゾーンから外に出る入口を、片っ端から塞ぐ。
入口は、最低2つある。
- バージョン管理(commit)の入口: ゾーンをまるごと追跡対象から外す。世界に出さない。
- AI取り込み(検索 / RAG)の入口: ゾーンを読み込み対象から外す。AIに食わせない。
ここで一番刺さった教訓がこれだ。「バージョン管理に上げてない=安全」ではない。 ローカルにしか無いファイルこそ、手元のAIは普通に読みに行く。世界には出てなくても、AIの口からは出る。入口ごとに別々に塞がないと、片方からダダ漏れる。
入口を塞いでも、「本文に紛れた一行」は別腹
ゾーンを決めて入口を塞ぐ。これで9割。でも残り1割が陰湿だ。
機密は、ファイル名で来ない。本文の地の文に、一行だけ紛れて来る。 普通の議事録の途中に、ぽろっと社名と金額。ゾーン分けはファイル単位だから、これは素通りする。
だから最後に、中身をなめるスキャンを足す。出力やコミットの直前に、認証情報・社名・金額っぽいパターンを機械で拾う。
# ゾーン分け(入口)だけだと、本文に紛れた一行は防げない。
# だから「外に出る直前」に中身もスキャンする=二段構え。
DENY_ZONE = "機密ゾーン(バージョン管理からもAI取り込みからも除外済み)"
def before_it_leaves(payload):
# 1) ゾーンの外から来たものか(ファイル単位の線引き)
assert not from_deny_zone(payload), "ゾーンの中身は外に出さない"
# 2) 本文に機密が紛れていないか(中身単位の線引き)
hits = scan_secrets(payload) # APIキー / メール / 電話 / 社名・金額っぽいパターン
if hits:
# ★検出はするが、勝手に無効化しない。現役で使ってるかもしれない。
# やるのは「外に出さない」ことだけ。実体を触るかは人間が決める。
raise BlockedBySecretScan(hits) # 人間に差し戻す
return release(payload)
ポイントは擬似コードのコメントにも書いた通り、検出と無効化は別の判断だということ。見つけたキーを勝手に revoke すると、それが本番で生きてたら今度はこっちが事故る。スキャンの仕事は「外に出さない」まで。実体を触るかどうかは、人間に渡す。
真似する時の最小形
難しい基盤は要らない。順番が全て。オレはこの順番を見事に後ろからやって、社名を一行漏らした。
- 「渡さないゾーン」を1つ決める。機密は散らさず、そこに集約する。出したくないものの住所を一本化する。
- 入口を片っ端から塞ぐ。バージョン管理の入口(commitしない)と、AI取り込みの入口(検索/RAGに載せない)は別物。両方やる。片方だけは穴。
- 「git未上げ=安全」を信じない。世界に出てなくても、手元のAIは読む。AIの取り込み口は独立して塞ぐ。
- 最後に中身スキャン。本文に紛れた一行は、外に出る直前にパターンで拾う。検出止まり、無効化は人間。
順番が逆になりがちなんだ。「何を渡せば賢くなるか」から考えると、際限なく渡したくなる。先に「何を渡さないか」を決める。 渡さないものの輪郭が決まって初めて、安心して残り全部を渡せる。
#4 では「実行(不可逆な操作)をAIに渡さない」線を引いた。#5 はその兄弟で、「データ(機密)をAIに渡さない」線を引く話だ。渡す範囲を決めるのが設計で、渡さない範囲を決めるのは、その設計の一番最初に来る。
まとめ:線は、渡す前に引く
AIエージェントで一番怖いのは、AIが賢すぎることでも、馬鹿すぎることでもなかった。こっちが無邪気に「全部どうぞ」と渡してしまうことだった。AIは渡されたものを、渡された通りに使う。それだけだ。
防ぎ方は、賢い検閲でも、後からの削除でもない(一度AIに渡したものは、インデックスや出力ログから綺麗には消せない)。渡す前に、渡さないものの線を引いておく。 ゾーンを決めて、入口を塞いで、本文を最後になめる。これで事故が「ゼロ」にはならないけど、「隠し忘れ一発で終わり」の構造からは降りられる。
「全部見せたほうが賢く動くだろ」と鍵束ごと渡しかけた過去の自分に言いたい。賢さより先に、線を引け。 渡さないものを決めてからの方が、結局たくさん渡せる。
(前回 #4: Claudeが書いたコードを、別のClaudeに3段階レビューさせる)
(次回 #6(連載最終回): Claudeは言いっぱなし。だから決定を台帳に書かせて後で回収する)
この記事について
arecore.net の中の人が運用する AI 役員チームの実践記録です。受託開発・SES・自社プロダクト開発をやっています。ご相談・フィードバックは arecore.net からどうぞ。