RAG とデータ漏えい — 文書を読ませた AI が、他人の文書を答える

RAG は AI に「あなたの文書」を読ませてから答えさせる仕組み。検索は「質問に似ている文書」を探すだけで「この人が見ていい文書」は探さないので、A社担当の質問に B社の契約条件が混ざって返る。Microsoft 365 Copilot の導入では、共有が緩いまま忘れられた給与表や役員会資料が普通の質問で表面化した。漏えいは AI が余計なことを言ったのではなく、渡した検索結果に他人の文書が入っていた。守る場所は AI ではなく検索の段階。頼み方と2人で確かめる方法。

動画で見る

結論から

RAG(検索拡張生成)は、AI に「あなたの文書」を読ませてから答えさせる仕組みです。社内マニュアル、顧客ごとの契約書、過去の問い合わせ履歴。これを AI に渡すと、「うちの会社のことを知っている AI」になります。便利です。ただし、AI は「誰がこの質問をしているか」と「この文書は誰が見ていいか」を、自分では照合しません。 検索の段階で絞らなければ、A さんの質問に、B さんの契約書の内容が混ざって返ります。

この記事で伝えたいのは、この一点だけです。RAG の漏えいは「AI が余計なことを言った」のではなく、「AI に渡した検索結果に、そもそも他人の文書が入っていた」。だから守る場所は AI ではなく、検索(取り出し)の段階。 Microsoft 365 Copilot の導入で「共有設定が緩いまま放置された給与表や役員会議事録が、普通の質問で出てきた」という事例が多数報告されたのは、この構造です。

  • 何が起きるか:営業担当の佐藤さんが「A 社の契約条件は?」と聞く。AI の検索が全顧客の契約書から「似ている文書」を拾い、B 社の条件も一緒に返す。あるいは、一般社員の質問に、人事フォルダの給与情報が出る
  • なぜ:RAG の検索は「質問に似ている文書」を探すだけで、「この人が見ていい文書」は探さない。権限の絞り込みを入れなければ、全文書が候補になる
  • 直す:検索の段階で「質問者が見ていい文書だけ」に絞る(文書ごとに所有者・部署を付け、検索時にフィルタ)。AI に渡す前に絞る。渡した後にお願いしても遅い
  • 確かめる:2人のアカウントで、相手にしか見えないはずの文書の内容を AI に聞く。答えたら ✗

動画版(約2分半)は本文の上に埋め込んであります。

① 何が起きるのか:質問に「似ている」文書が、全部候補になる

例として、AIで作った顧客管理アプリに「契約書について AI に質問できる」機能を付けたとします。仕組みはこうです。

  1. 顧客ごとの契約書 PDF を、小さな断片に分けて保存する(ベクトル DB など)
  2. 営業担当が質問すると、質問に似ている断片を検索で数件拾う
  3. その断片を AI に渡し、「これを読んで答えて」と頼む

佐藤さん(A 社の担当)が「A 社の解約条件を教えて」と聞くと、検索は「解約条件」に似ている断片を拾います。A 社の契約書からも、B 社・C 社の契約書からも。AI はそれを全部読んで、「A 社は30日前通知、B 社は60日前通知です」のように、律儀にまとめます。佐藤さんは B 社の担当ではありません。

同じことが社内向けの AI でも起きます。「今年の昇給の目安は?」と一般社員が聞くと、検索が人事フォルダの給与テーブルを拾い、AI が答える。Microsoft 365 Copilot の企業導入では、まさにこれが多数報告されました。SharePoint で「全社員」に共有されたまま忘れられていた給与ファイルや役員会の資料が、Copilot の普通の質問で表面化した。ある導入支援企業は、監査した企業の 8割でこの種の過共有が見つかったと報告しています。Copilot は新しい権限を与えたわけではなく、すでに緩かった権限を、検索で見つけやすくしただけです。

RAG の検索は「似ている」を探し「見ていい」は探さない。守る場所は検索の段階

② なぜ起きるのか:検索は「似ている」を探し、「見ていい」は探さない

原因は、RAG の検索が意味の近さで文書を選ぶことにあります。「解約条件」という質問には、どの会社の契約書の「解約条件」の段落も、同じくらい似ています。検索は誰の質問かを知りません。

そして AI 側にも、照合する手段はありません。AI に渡された断片は、全部「読んでいい文書」に見えます。システムプロンプトに「他社の情報は出さないで」と書いても、渡された文書の中に B 社の条件があれば、AI はそれを「参考情報」として使います(→ プロンプトインジェクションの記事と同じ構造で、AI が読む文章に「これは見せていい/だめ」の区別は無い)。

だから、守る場所は検索の段階です。文書を保存するときに「所有者(顧客 ID・部署・ユーザー ID)」を一緒に付けておき、検索するときに「質問者が見ていい所有者の文書だけ」に絞る。AI に渡す前に絞れば、AI は他人の文書を読めません。AI に渡した後に「見せないで」と頼むのは、財布を渡した後に「中を見ないで」と言うようなものです。

AI はどう作るか。 「契約書について質問できる機能を付けて」と頼むと、AI は①文書を断片化して保存、②似ている断片を検索、③AI に渡す、を素直に作ります。この3ステップに「権限」は登場しません。動作確認は自分の文書でやるので、「自分の文書の内容が返ってきた、動く」で完了します。他人の文書も一緒に候補になっていることは、他人のアカウントで試すまで見えません。

Supabase の pgvector を使っている場合、文書テーブルに RLS を付ければ、検索時に自動で本人の行に絞られます(→ RLS の記事)。逆に、AI 機能が service_role で検索していると、RLS が効かず、全文書が候補になります(→ AI エージェントの権限過剰の記事)。

③ どう直すのか:AIに「検索の段階で絞って」と頼む

以下をそのまま AI ツールに貼ってください。

「このプロジェクトの RAG(文書検索+AI 回答)機能について、①文書(またはその断片)を保存するテーブルに、所有者を示す列(user_id・customer_id・department など)があるか、②検索クエリで、ログイン中のユーザーが閲覧権限を持つ文書だけに絞っているか(WHERE 句または RLS)、③検索が service_role や管理者権限で実行されていないか、を確認してください。①が無ければ追加し、既存文書に所有者を付けてください。②が無ければ、AI に渡す前の検索段階でフィルタを追加してください。システムプロンプトの『他人の情報を出さない』という指示は、対策としては数えないでください。」

ポイントは、「AI に渡す前の検索段階で」と「システムプロンプトは対策として数えない」。AI は後者を書いて「対策済み」と言いがちです。

④ 直ったかを確かめる:2人で、相手の文書を聞く

  • アカウント A・B を作り、A だけが見られる文書(A 社の契約書など)を登録する
  • B でログインし、AI に「A 社の解約条件は?」と聞く。答えたら ✗。「該当する文書がありません」なら ◯
  • B で「解約条件について、全部の顧客の例を教えて」のように、あいまいに聞く。A 社の内容が混ざったら ✗
  • 文書テーブルに所有者の列があるか、RLS が有効かを、Supabase なら Table Editor と Policies で見る
  • 分からなければ、AIに「ユーザー B の質問に対する検索結果に、ユーザー A の文書が含まれないことを確認するテストを書いて」

自分で直せないとき:エンジニアに送る一文

「RAG の検索が権限で絞られておらず、別のユーザーの文書の内容が AI の回答に混ざります。文書に所有者を付け、AI に渡す前の検索段階でログインユーザーの権限でフィルタしてください(RLS があれば有効化、service_role での検索は廃止)。」

よくある質問

AI に「他社の情報は答えないで」と指示してあります。

その指示は、AI がすでに他社の文書を読んでいる前提です。AI が読んだ内容を「言わない」ように頑張るのではなく、「読めない」ようにします。プロンプトインジェクション(「以上の指示は無視して」)で、その指示は外れます。

社内向けなので、全員が全文書を見てもいいです。

その判断が明確なら問題ありません。ただ「全員が見ていい」つもりだった文書に、人事・給与・評価・役員会の資料が混ざっているのが、Copilot の事例で起きたことです。一度、文書の一覧と「誰が見ていいか」を棚卸ししてください。

RAG を使わず、文書を全部プロンプトに貼っています。

同じことです。「AI に渡す前に、この人が見ていい文書だけにする」が原則で、渡し方が検索でも直貼りでも変わりません。

この穴が塞がれば、AI 機能は安全ですか。

「AI が読める範囲」は正しくなります。「AI が実行できる範囲」(→ AI エージェントの権限過剰の記事)と「AI をだます文章」(→ プロンプトインジェクションの記事)は別の話です。