マルチテナントRAGの認可チェックリスト|テナント間の情報漏えいを防ぐ(2026)
マルチテナントRAGの認可を実装・監査するための実務チェックリスト。サーバー側でテナント境界を確定し、検索時に強制し、チャンクACL・キャッシュ・禁止取得テストまで整えます。
目次
- 1分要約
- どんな人向けか
- 結論
- 解説
- 認証できても、文書の認可は終わっていない
- top-kの後ではなく、similarity searchの前に絞る
- tenant隔離と文書ACLは別の問題
- 実務ガイド
- Step 1: 認可contractを先に決める
- Step 2: 機密度に合わせて隔離境界を選ぶ
- Step 3: 取り込み時にACLを保存する
- Step 4: 信頼できるアプリコードでscopeを組み立てる
- Step 5: rerankと生成までscopeを切らさない
- Step 6: cache・trace・派生データも隔離する
- Step 7: 「取得してはいけない」をテストする
- Step 8: 監視と事故対応を用意する
- ありがちな落とし穴
- チェックリスト
- FAQ
- 1. 1 tenant = 1 namespaceなら十分?
- 2. 1つの共有indexでmetadata filterを使ってもよい?
- 3. 最後に未認可chunkを除外すれば、本文は漏れないのでは?
- 4. embeddingも機密データとして扱うべき?
- 参考資料
- 内部リンク
- 免責事項
マルチテナントRAGで、他社データの誤取得をどう防ぐ?
1分要約
- テナント・ユーザー・所属グループは、検証済みセッションからサーバー側で確定する。ブラウザから届く
tenantId、namespace、ACLは認可に使わない。 - 認可はベクトル検索や全文検索のクエリ内部で強制する。全テナントを検索した後に結果を除外する方式は安全ではない。
- ACLをチャンク単位で保持し、semantic cacheやログにも境界を引き継ぐ。取得禁止文書が一件も混ざらないことをリリース条件にする。
どんな人向けか
- 複数顧客の文書を1つのRAG基盤で扱うSaaSチーム
- 既存の認可モデルへベクトル検索、ハイブリッド検索、reranker、semantic cacheを追加するエンジニア
- テナント間漏えいを監査可能なテストへ落としたいセキュリティ・基盤担当者
結論
retrievalを「ログイン後に使える検索機能」ではなく、認可境界として扱う。
安全な処理順は次の通り。
- 呼び出し元を認証する
- テナント、ユーザー、グループ、ロールをサーバー側で確定する
- 可能ならindex、collection、namespaceなどの隔離境界を選ぶ
- retrievalリクエスト内部で文書・チャンクACLを適用する
- 認可済み候補だけをrerankする
- 生成前に最終contextを再検証し、違反時はfail closedにする
検索後のフィルタは主制御にならない。その時点で、他テナントのvectorが件数、処理時間、score、候補選択、ログ、rerankerへ影響している可能性がある。OWASP GenAI LLM Top 10 2026もembedding層をアプリケーションのtrust boundaryに含め、index queryの内部でtenant scopeを強制するよう勧めている。
機密性が高いテナントやtrust zoneは、物理的に分けたindexや強いnamespace隔離を優先する。共有indexとmetadata filterを使う場合も、すべてのread/write経路でサーバー由来のscopeを必須にし、迂回経路を負のテストで塞ぐ。
解説
認証できても、文書の認可は終わっていない
認証は「誰がアプリを呼んだか」を確かめる。retrievalが返した各チャンクを、その人が読めることまでは保証しない。
RAGのリクエストは、複数の保存先と変換処理を通る。
- 元文書と元システムのACL
- 抽出テキストとチャンク
- embeddingとvector metadata
- keyword・vector検索の候補
- rerankerへの入力と出力
- 最終prompt contextと引用
- semantic cache、trace、サポート用ログ
どこか1段でもtenantやACLのscopeが消えると、通常の製品画面では見えない情報が回答へ混ざる。
top-kの後ではなく、similarity searchの前に絞る
次の順序は危険。
全テナントを検索 -> 上位20件を取得 -> 権限のない結果を削除 -> 残りを生成へ渡す
他テナントのチャンクが上位を占有すると、正しいテナントの回答が空になる。文書本文を返さなくても、結果件数、score分布、latency、trace、後段処理から「何かが存在する」と推測される余地も残る。
必要な順序はこちら。
検証済み認可scope -> scope付き検索 -> 認可済み候補 -> rerank -> 最終context
Azure AI Searchはtenant filterやdocument-level security filterをquery-time controlとして説明している。Pineconeはserverlessのマルチテナント構成で1 tenant = 1 namespaceを推奨し、data-plane操作は必ず1つのnamespaceを対象にする。他製品ではtenant partition、collection、shard、prefilterなど名称が違うが、必要な性質は同じ。権限のないvectorを候補集合へ入れないことだ。
tenant隔離と文書ACLは別の問題
tenant境界は「このデータはどの顧客に属するか」を決める。ACLは「その顧客の中で、誰がこのチャンクを読めるか」を決める。
1 tenant = 1 namespaceでも、社内に人事・法務・経理・案件別の限定文書があるなら不十分。元データに細かい権限がある場合は、次の2層を保つ。
- tenantまたはtrust zoneの隔離
- 文書・チャンク単位のuser、group、role、classification
1つの文書に公開部分と機密部分が混在するなら、チャンク単位で認可する。ACL metadataが欠けた、または同期期限を過ぎた場合は公開扱いにせず、取得を拒否する。
実務ガイド
Step 1: 認可contractを先に決める
vector databaseを選ぶ前に、アクセスを決める属性を明文化する。
- 組織境界となる
tenant_id - 認証済み
user_id - 有効なgroup IDとrole ID
- project、region、classificationなどのscope
- ACL versionまたは最終同期時刻
これらは検証済みsessionか信頼できるidentity providerから作る。request body、query parameter、モデル出力、検索済みテキストを認可根拠にしない。
Step 2: 機密度に合わせて隔離境界を選ぶ
次の順で判断しやすい。
- 専用serviceまたはindex: 規制対象、契約上の分離が必要、特に機密性が高いtenant
- namespace・tenant partition・collection: 共有基盤上で行う一般的なSaaSのtenant隔離
- 共有index + サーバー強制metadata filter: 機密度が低い共有データ、または複数tenantの横断検索が本当に必要な場合
embedding dimensionが同じという理由だけで、外部Web、社内文書、顧客限定データを同じtrust zoneへ入れない。
Step 3: 取り込み時にACLを保存する
すべてのチャンクへ、元データの認可判断を再現できるmetadataを持たせる。
{
"chunk_id": "refund-policy#annual-plans-02",
"tenant_id": "tenant_7",
"source_id": "refund-policy",
"allowed_group_ids": ["billing", "support-leads"],
"classification": "internal",
"acl_version": "2026-09-05T02:10:00Z"
}
取り込み処理は必須scopeの存在と形式を検証する。認可metadataが欠けたレコードは隔離し、indexへ入れない。元システムの権限が変わったら、決めたSLO内で該当チャンクを更新または再indexする。
Step 4: 信頼できるアプリコードでscopeを組み立てる
retrieval関数へ渡す値を絞る。次のTypeScriptは製品非依存の疑似コードなので、filter構文は利用中の検索製品へ合わせる。
type AuthzContext = {
tenantId: string;
userId: string;
groupIds: string[];
};
async function retrieve(query: string, sessionToken: string) {
const authz: AuthzContext = await authzFromVerifiedSession(sessionToken);
const results = await vectorStore.query({
namespace: `tenant:${authz.tenantId}`,
query,
topK: 12,
filter: {
tenant_id: authz.tenantId,
allowed_group_ids: { anyOf: authz.groupIds },
},
});
if (results.some((r) => !isAuthorized(r.metadata, authz))) {
throw new Error('retrieval_scope_violation');
}
return results;
}
最後の検証はdefense in depthと異常検知のために置く。全体検索してから弾いてよい、という意味ではない。
Step 5: rerankと生成までscopeを切らさない
rerankerへ渡せるのは認可済み候補だけ。promptを組み立てる直前にもchunk IDを検証し、ユーザーが開けない引用リンクは表示しない。
「このtenantの文書だけを使って」とLLMへ頼んでも認可にはならない。system promptはモデルへの行動指示であり、決定的なaccess controlではない。
Step 6: cache・trace・派生データも隔離する
retrievalが正しくても、派生システムがscopeを落とすと漏えいする。
- semantic cache keyへtenantと認可scope versionを含める
- source setが明示的にpublicでない限り、生成済み回答をtenant間で共有しない
- trace、eval、サポートツールは同等以上の境界で保護する
- embeddingとvector backupを元文書と同じ機密区分で扱う
- 元文書を削除したらchunk、vector、cacheも削除する
信頼できないclientへraw similarity scoreを返さない。特定文書の存在を探るoracleになり得る。
Step 7: 「取得してはいけない」をテストする
少なくとも2 tenant、各tenant内に複数のpermission groupを用意する。質問ごとに、期待するchunk IDだけでなく禁止chunk IDも記録する。
release gateへ次を入れる。
- candidate retrievalに禁止IDが0件
- rerank後とcontext assembly後も禁止IDが0件
- 互換性のないscope間でcache hitが発生しない
- tenantまたはACL metadata欠落時にdeny by defaultとなる
- 権限取り消しが定義済みの同期期限内に反映される
- 直接的なID、言い換え、攻撃的なprobe queryで同じ境界を守る
productionと同じAPI経路で試す。filter builderだけのunit testでは、scopeなしfallback queryを見つけられない。
Step 8: 監視と事故対応を用意する
tenant scope、principalまたはgroup ID、index version、返したchunk ID、policy判定を残す。質問文に機密情報が入り得るなら、retention policyに従ってredactまたはhash化する。
次をalert対象にする。
- tenantまたはACLが一致しないchunkを1件でも返した
- scopeなしの検索request
- 拒否やtenant横断probeの反復
- embedding・similarity search APIへの異常なアクセス
- 合意した期限を超えるACL同期遅延
vector storeやembedding backupが漏えいしたとき、「vectorだけだから影響は小さい」と決めつけない。機密元データと同じincident processへ上げる。
ありがちな落とし穴
- ブラウザから届く
tenantId、namespace、group ID、filter JSONを信頼する - search APIを認証しただけで、各chunkを認可しない
- 全体top-kを取得してからtenant filterをかける
- vector searchにはscopeを付けたが、keyword searchやfallback searchで忘れる
- 認可済み・未認可の候補を一緒にrerankする
- tenant namespaceを分けただけで、tenant内の文書ACLを無視する
- ACL metadataが欠けたchunkをpublic扱いする
- 共有semantic cacheから別tenantへ回答を再利用する
- 削除・権限取り消し済みのデータをvector、backup、cacheへ残す
- raw similarity score、件数、詳細traceをclientへ返す
- 正常系だけをテストし、禁止IDが混ざらないことを検証しない
- system promptへaccess controlを任せる
チェックリスト
- [ ] tenant scopeは検証済みのサーバー側sessionから作る
- [ ] client由来のtenant、namespace、ACL値は認可に使わない
- [ ] すべての取り込み経路で必須tenant・ACL metadataを書き込む
- [ ] 認可metadataの欠落・不正はfail closedにする
- [ ] 高機密tenantまたはtrust zoneは強い隔離境界を使う
- [ ] vector・keyword・hybrid・fallbackの全検索でquery内部にtenant scopeを強制する
- [ ] tenant隔離に加えて、文書・chunk ACLも強制する
- [ ] rerankerへは認可済み候補だけを渡す
- [ ] 生成前に最終contextと引用を検証する
- [ ] semantic cache keyへtenantと認可scopeを含める
- [ ] trace、eval dataset、export、サポートツールも同じ境界を守る
- [ ] embeddingとbackupを元文書と同じ機密区分で扱う
- [ ] 元文書削除時にchunk、vector、cache、派生物も消す
- [ ] 権限変更を反映する同期期限を定義する
- [ ] retrieval logへ監査に必要なIDとpolicy判定を残す
- [ ] raw similarity scoreを信頼できないclientへ返さない
- [ ] 2つ以上のtenantと複数permission groupでテストする
- [ ] 禁止文書の取得件数0をrelease gateにする
- [ ] 直接質問・言い換え・攻撃的probe queryをテストする
- [ ] tenant横断のvector露出をdata-security incidentとして扱う
FAQ
1. 1 tenant = 1 namespaceなら十分?
vector基盤が確実なnamespace隔離を提供するなら、強いtenant境界になる。ただし、tenant内の権限までは解決しない。人事・法務・経理・案件文書で閲覧者が異なるなら、文書またはchunk ACLも必要。
2. 1つの共有indexでmetadata filterを使ってもよい?
リスクと製品仕様が合えば可能。ただしfilterは必須で、サーバーが検証したidentityから作り、retrieval内部で適用する。fail closedの初期値と負のテストも必要。高機密用途は、より強い隔離境界を優先する。
3. 最後に未認可chunkを除外すれば、本文は漏れないのでは?
本文を表示しなくても、未認可vectorがtop-k、結果件数、score、処理時間、reranker、trace、cacheへ影響する。未認可chunkが候補枠を使い、正しい回答を落とす問題もある。
4. embeddingも機密データとして扱うべき?
扱うべき。OWASPの現行ガイドはembedding層をtrust boundaryに含め、embeddingやbackupから元データの情報が露出し得ると警告している。元文書と同等のaccess、暗号化、retention、incident controlを適用する。
参考資料
- OWASP GenAI LLM Top 10 2026
- 安全なマルチテナントRAGの設計(Microsoft Azure Architecture Center)
- Azure AI Searchのdocument-level access control
- Azure AI Searchにおけるマルチテナント設計パターン
- namespaceを使ったマルチテナント実装(Pinecone Docs)
内部リンク
- 親記事(Hub):AI開発:まずはここから
- 関連記事:
免責事項
一般的な開発・セキュリティ情報です。利用中のvector製品、identity基盤、データ分類、契約、法規制に合わせて隔離設計を検証してください。