Skip to content

エンタープライズRAGにおける「検索スコープ迂回(Document Set Bypass)」とDBセッション早期クローズの罠

1. 概要

社内の機密ドキュメントを横断検索するエンタープライズRAG(Retrieval-Augmented Generation)において、ユーザーごとに「閲覧できるドキュメントの範囲」を厳密に制御することはシステム上の最優先命題である。 しかし、APIレイヤーにおける入力検証の漏れと、バックエンドにおけるパフォーマンス最適化(DB接続プールの枯渇を防ぐためのセッション早期クローズ)が噛み合わさったとき、セキュリティのチェックゲートが機能しなくなる脆弱性が発生することがある。 本稿では、企業向けRAGシステムであるOnyx(旧Danswer)で発生した、検索フィルターの権限バイパス脆弱性(onyx-dot-app/onyx#10602)を事例に、RAGにおける権限境界の設計論について論じる。


2. インシデントの分析:APIパラメータの上書きによる情報リーク

2.1. 現象:Vespa ACLの網の目をすり抜ける公開ドキュメント

Onyxでは、NotionやSlackから同期したドキュメントを「Document Set(ドキュメントセット)」というグループ単位にまとめ、エージェント(アシスタント)ごとに検索対象のセットを制限することで情報のスコープを管理していた。 しかし、以下のAPIレイヤーのチェック漏れにより、ユーザーが意図しないドキュメントを検索できてしまう脆弱性が報告された。

  • 攻撃手法: ユーザーがチャットメッセージを送信する際、リクエストAPIの内部検索フィルターパラメータ(internal_search_filters.document_set)を手動で書き換え、本来そのユーザーに権限がない(あるいはそのエージェントに割り当てられていない)任意のドキュメントセット名を指定して送信する。
  • 結果: 検索エンジン(Vespa)自体が持つ個々のドキュメントのACL(アクセス制御)は機能していたものの、**「本来特定のドキュメントセット(特定プロジェクト等)内に限定されるべき公開(PUBLIC)ドキュメント」**が、ドキュメントセットのフィルタリング制限を上書きされたことにより、そのスコープ外から自由に検索・引き出されてしまう状態となっていた。

2.2. 根本原因:パフォーマンス最適化と「セキュリティチェックの無効化」

この脆弱性の修正にあたり、当初開発者は検索クエリ構築関数である _build_index_filters の内部に「ユーザーが指定したドキュメントセットの閲覧権限チェック」を実装した。 しかし、ここにはパフォーマンス最適化に起因する重大なバグが潜んでいた。

Onyxの検索機能は、並行検索ワーカーが動く際、並行処理中にデータベース接続(Connection)を占有し続けないよう、「検索を実行する手前で、一時的にDBセッションを開いてすべてのACLフィルタ情報をメモリ上にプリフェッチ(先行取得)し、即座にDBセッションをクローズする」 という設計になっていた。 これにより、メインの検索クエリ構築時にはすでにDB接続は閉じられており、db_session 引数は None で呼び出されていた。

結果として、

  • if db_session is not None: というチェックガードを設けていたため、メインの検索パスにおいて**セキュリティチェックがサイレントにスキップ(無効化)**され、パラメータの改ざんが防げていなかった。

3. 実体験:パフォーマンスと安全性のトレードオフが生む「セキュリティホール」

データベースの接続数制限や高速化のための「早期セッションクローズ」は、多くのWebアプリケーションやRAGのバックエンドで用いられる典型的な最適化パターンであるが、セキュリティのタイミングとずれると容易に地雷と化す。

  • 実体験エピソード: あるマルチテナント型のSaaS製品で、ユーザーが自分の所属するテナント以外のデータにアクセスできないよう、APIの入口でテナントID의チェックをかけていた。 システム全体の応答速度を高めるために、「DBセッションをできるだけ短く保ち、並行処理の前に必要なコンテキストデータをすべてオブジェクトに詰めて、セッションを即座に閉じる」というリファクタリングを施した。 しかし、このリファクタリングの過程で、あるサブモジュールが「遅延ロード(Lazy Loading)」で追加データを引っ張る際、すでにセッションが閉じられていたためテナントIDの検証メソッドがエラー(またはスキップ)になり、結果としてテナントをまたいだデータの上書きや読み込みができてしまうバグが本番環境で発生した。 「いつDBとの接続が生きているか(チェックが実行可能か)」というライフサイクルと、「どのタイミングで権限チェックを完了すべきか」の不一致は、最適化に熱心な熟練開発者ほど踏み抜きやすい「死角」である。

4. 解決策とRAG의 セキュリティ・ガードレール

Onyxのチームは、この問題に対して、DBセッションがまだ生きている 「SearchTool.run() のプリフェッチフェーズ(DBセッションが閉じられる直前)」 にドキュメントセットの権限チェックを前倒し(シフトレフト)して行うように設計を修正した。

python
with get_session_with_current_tenant() as db_session:
    # 1. 検索実行前のDBセッションがアクティブな間にチェックを強制する
    if not self.bypass_acl and self.user_selected_filters.document_set is not None:
        accessible_names = filter_document_set_names_by_user_access(
            db_session=db_session,
            document_set_names=self.user_selected_filters.document_set,
            user=self.user,
        )
        unauthorized = [name for name in self.user_selected_filters.document_set if name not in accessible_names]
        
        # 2. 権限がない場合は、ValueErrorではなくLLM/ツール呼び出し例外として即座に中断
        if unauthorized:
            raise ToolCallException(
                message=f"User does not have access to document sets: {unauthorized}",
                llm_facing_message="You do not have access to one or more requested document sets."
            )

4.1. 存在確認攻撃(Oracle Attack)への対策

また、この修正では「存在しない(実在しない)ドキュメントセット名」が指定された場合も、サイレントに無視して通すのではなく、一律で「権限不足」としてエラーを返すようにした。 もし無効な名前をサイレントに無視してしまうと、攻撃者が適当な名前を送り続け、エラーが出るか出ないかで「他テナントのドキュメントセットの名前が実在するかどうか」を推測(オラクル攻撃)できてしまうためである。


5. 結論

RAGシステムにおける権限管理は、検索エンジンのACL(行レベルのセキュリティ)と、アプリケーション側のセッションスコープの二重の防衛線によって成り立つ。 パフォーマンスを優先したデータベース接続の早期クローズは、検証ロジックが実行される「文脈(コンテキスト)」を破壊し、セキュリティチェックを骨抜きにするリスクを孕んでいる。 セキュリティ検証は、**「DB接続が存在し、最も安全な最上流のゲートウェイ」**において完了させ、下流のステートレスな処理には検証済みの安全なデータ(クリーンリスト)のみを引き渡す。この設計の徹底こそが、機密ドキュメントを扱うAI検索インフラにおける絶対的な設計原則である。


6. 参考文献 / 一次情報

Shiori pointer-RAG automation experiment.