本文へスキップ
株式会社オープンナレッジ
株式会社オープンナレッジ Open Knowledge Inc.
Privacy-Preserving RAG:PostgreSQL 19 pgvector & RLS 2026 読了目安:約4分 執筆:株式会社オープンナレッジ 技術調査・編集室(監修:加藤 明生)

社外に出せない機密文書をどうAI検索するか? 社内閉域データとPostgreSQL pgvectorによる安全なナレッジ検索(RAG)

「ベテラン社員が退職する前に、過去10年分の見積書・トラブル対処法・社内規程を誰でも3秒で引き出せるようにしたい。でも機密情報を外部AIに渡すわけにはいかない」——そのジレンマを解決する安全な社内AI検索の設計図を解説します。

1. 基本と全体像

この記事の要点と4つの章のご案内

身近なたとえでわかる「RAG(検索拡張生成)」:AIに丸暗記させるのではなく「社内の鍵付き書庫を見て答えさせる」に関するイメージ写真
身近なたとえでわかる「RAG(検索拡張生成)」:AIに丸暗記させるのではなく「社内の鍵付き書庫を見て答えさせる」
図解でわかる「危険な無料AI利用」と「閉域データベース(db01)× 権限連動RAG」の違いに関するイメージ写真
図解でわかる「危険な無料AI利用」と「閉域データベース(db01)× 権限連動RAG」の違い
社外に出せない機密文書をどうAI検索するか? 社内閉域データとPostgreSQL pgvectorによる安全なナレッジ検索(RAG) の各章におけるテーマと要点一覧表
章構成 主なテーマ 要点と確認ポイント
1. 基本と全体像 身近なたとえでわかる「RAG(検索拡張生成)」:AIに丸暗記させるのではなく「社内の鍵付き書庫を見て答えさせる」 AIに社内文書を「丸暗記(追加学習)」させる必要はない: 社内専用データベースから必要な箇所だけをその都度カンニングペーパーとして引き出して答えさせる「RAG(検索拡張生成)」なら、低コストかつ外部漏洩ゼロで明日から運用できます。
2. 現場での進め方と対策 図解でわかる「危険な無料AI利用」と「閉域データベース(db01)× 権限連動RAG」の違い 「誰が質問したか」によって検索できる範囲をデータベースの鍵で安全な分離: PostgreSQL 19 の行レベルセキュリティ(RLS)により、人事や経営会議の機密書類が一般社員のAI回答に混ざる事故をシステム構造として構造的に遮断します。
3. 関連する制度とルール 社内AI検索の使い心地を決める3つのポイント 必ず「元の書類の何ページ目に書いてあるか」の証拠リンクを添えて回答: 社内資料に書いていないことをAIが勝手に想像して答えるのを防ぎ、人間がワンクリックで原本を確認できる安心設計です。
4. 詳しい技術仕様としくみ 現場で陥りやすい落とし穴と回避策(社内AI検索・RAG構築の4大失敗例) ご安心ください。一般的な個人向け無料チャットAIにファイルを入れると外部学習に使われるリスクがありますが、当社の社内ナレッジ検索(RAG)では、文書データそのものを自社専用の隔離データベース(db01 / PostgreSQL 19)の内部に保管します。AIモデルには「質問に関連する数段落の抜粋」だけを外部学習ゼロ保証の暗号化通信で渡して要約させるため、社外にデータが流出することは一切ありません。
第1章〜第4章では、実務チェックリスト・公的制度基準・詳しい技術仕様を確認できます。
この記事のポイント
  • 01. AIに社内文書を「丸暗記(追加学習)」させる必要はない: 社内専用データベースから必要な箇所だけをその都度カンニングペーパーとして引き出して答えさせる「RAG(検索拡張生成)」なら、低コストかつ外部漏洩ゼロで明日から運用できます。
  • 02. 「誰が質問したか」によって検索できる範囲をデータベースの鍵で安全な分離: PostgreSQL 19 の行レベルセキュリティ(RLS)により、人事や経営会議の機密書類が一般社員のAI回答に混ざる事故をシステム構造として構造的に遮断します。
  • 03. 必ず「元の書類の何ページ目に書いてあるか」の証拠リンクを添えて回答: 社内資料に書いていないことをAIが勝手に想像して答えるのを防ぎ、人間がワンクリックで原本を確認できる安心設計です。
場面別の実務確認ポイント(関心に合わせてお読みいただけます)
01. 経営者・情報セキュリティ責任者の方へ 第1章(1. 概要と背景)をご覧ください。シャドーAI(無料AIへの機密入力)を防ぎながら、社内の知恵を安全に資産化する仕組みがわかります。
02. 総務・人事・カスタマーサポート担当の方へ 第2章(2. 具体的な対策・手順)にて、「同じ質問に何度も答える時間」をなくし、古い規程と新しい規程が混ざらない整理術を解説しています。
03. 医療・介護・士業(税理士・社労士・弁護士)の方へ 守秘義務が課せられた個人情報・カルテ・顧問先データを、1バイトたりとも他社や外部学習に漏らさずに検索する隔離設計をご紹介しています。
04. データベース設計者・AIエンジニアの方へ 第3章(3. 技術仕様・実装解説)にて、Reciprocal Rank Fusion(BM25 + HNSW)、PostgreSQL 19 FORCE ROW LEVEL SECURITY、PIIマスキングのRust 2024実装を公開しています。

01. 身近なたとえでわかる「RAG(検索拡張生成)」:AIに丸暗記させるのではなく「社内の鍵付き書庫を見て答えさせる」

「自社の過去のトラブル事例や社内規程をAIに覚えさせるには、何百万円もかけて独自のAIモデルを学習(ファインチューニング)させないといけないのでしょうか?」というご質問をよくいただきます。結論から言うと、AIモデル自体に社内データを丸暗記させる必要はまったくありません。

テストにたとえると、AIに社内の全書類を丸暗記させる(追加学習する)のは大変な時間とお金がかかり、しかも来月ルールが変わったときに古い記憶を消すことができません。それに対して現在主流の「RAG(ラグ:検索拡張生成)」という仕組みは、「優秀な読解力を持つAIに、質問が来た瞬間だけ社内の鍵付き書庫(データベース)から該当するマニュアルのページを開いて渡し、『このページに書いてある事実だけを使って要約してください』と答えさせる(教科書持ち込み可のテスト)」方式です。

この方式なら、新しいマニュアルを1枚追加した瞬間に最新の答えが返るようになり、万一書類を削除すればその瞬間にAIの回答からも構造的に消去されます。

社内資料を探す時間

平均 18分 → 3秒

共有フォルダの奥深くから過去の見積書や手順書を探し回っていた平均18分の探索時間が、出典リンク付きの3秒回答に短縮されます。

外部AIへの学習利用

外部学習 0%(全体遮断)

個人情報(PII)の事前マスキングとゼロデータ保持(Zero Data Retention)構成により、社内機密が外部AIの学習に使われることはありません。

権限外の文書混入率

越権漏洩 0件(RLS強制)

PostgreSQL 19 の行レベルセキュリティ(RLS)により、質問者の部署・役職で閲覧できない行はベクトル検索自体から物理的に除外されます。

02. 図解でわかる「危険な無料AI利用」と「閉域データベース(db01)× 権限連動RAG」の違い

社員が質問してから3秒で安全な回答と原本リンクが返ってくるまでの裏側の防壁を図解します。

01. 質問受付 & 個人情報マスク 社員からの質問(3項目または音声) 「型番A-102のパッキン交換手順と 過去の見積単価を教えて」 ログインID:営業部 一般社員 Rust PIIマスキング防壁 電話番号・マイナンバー・口座番号等の 個人情報を0.1ミリ秒で自動検知し [REDACTED] トークンへ無害化置換 02. 専用DB(db01)での権限付ハイブリッド検索 PostgreSQL 19 FORCE RLS(行レベル鍵) ・営業部員が閲覧可能な文書行のみ通過 ・役員会議録・人事評価表はDB層で全体遮断 ・他社テナントのデータ混入確率:0.0% RRF ハイブリッド検索(BM25 × HNSW) ・型番「A-102」の一致検索(BM25) ・「パッキン交換」の意味ベクトル検索 上位3件の根拠チャンクのみを厳選抽出 03. 出典付き回答の生成 根拠文書に基づく正確な回答 「A-102の交換手順は保守基準書 第14頁に記載されています。 標準見積単価は12,500円です」 [出典01] 保守手順書_2026版 p.14 [出典02] 標準単価表_改定3版 ※資料にない事柄は「記載なし」 と正直に答え、知ったかぶりを防止

03. 社内AI検索の使い心地を決める3つのポイント

01. 「型番ピタリ一致」と「意味のふんわり検索」の両方に強いハイブリッド方式

よくあるAI検索ツールは、意味の似た文章を探すのは得意でも、「品番 M8-25B」や「第14条第2項」といった1文字違いの番号を見分けるのが苦手です。当社のシステムは、意味を探すベクトル検索と、文字の一致を探す全文検索を数学的に融合(RRF:逆順位融合)しているため、品番でも話し言葉でも一発で正解にたどり着きます。

02. 古いマニュアルと最新マニュアルが混ざらない「有効期限・世代管理」

社内の共有フォルダには「就業規則_2022.pdf」「就業規則_2024改定案.pdf」「就業規則_最新_最終.pdf」のように似たファイルがいくつも眠っていることがあります。当社のナレッジ基盤では、最新の有効版だけに検索フラグを立て、旧版は履歴アーカイブへ自動移動するため、AIが古いルールを答えてしまう事故を防ぎます。

03. 社内資料に答えがないときは、推測で嘘をつかず「資料に記載がありません」と答える

検索された資料と質問との一致度スコアが基準値に満たない場合は、AIに無理な作文をさせず「現在の社内規程には該当する記載が見当たりません。総務窓口へご確認ください」と回答させます。これにより、AIの知ったかぶり(ハルシネーション)による業務ミスを根絶します。

04. 現場で陥りやすい落とし穴と回避策(社内AI検索・RAG構築の4大失敗例)

「社内チャットボットを作ったけれど、とんちんかんな答えばかり返ってきて誰も使わなくなった」という失敗を防ぐ4つの急所です。

整理されていない共有フォルダの丸ごと投入

10年前の廃止済み料金表や下書きメモまで全部AIに読ませてしまう

【なぜ危険か】 古い料金表と新しい料金表が両方入っていると、AIはどちらが最新か判断できず、廃止された安い金額をお客様へ案内する原因になります。

【回避策】 いきなり全フォルダを入れるのではなく、まず「現行の規程・よくある質問・直近2年の正式見積」に絞って登録し、版管理(バージョン番号)を付与します。

表や文章の「機械的な文字数ぶつ切り」

500文字ごとに機械的に切った結果、見出しと金額表が別々に千切れる

【なぜ危険か】 「Aプランの料金は以下の通りです」という文と、その下の金額表が別々の塊に分断されると、AIはどのプランの金額かわからなくなります。

【回避策】 文字数で機械的に切るのではなく、章・節・見出し階層と表の構造を保ったまま意味のまとまりごとに分割(セマンティック・チャンキング)します。

アプリ画面側だけでの甘い権限チェック

データベース内に全社の書類を混ぜて入れ、AIへの指示文だけで隠そうとする

【なぜ危険か】 「人事情報は答えないでください」とプロンプトで指示するだけでは、質問の言い回し(プロンプトインジェクション)によって機密が引き出されてしまいます。

【回避策】 AIに渡す前のPostgreSQLデータベース層で FORCE ROW LEVEL SECURITY を有効化し、権限のない行は物理的に読み出せない設計を順守します。

出典リンクのない回答

AIがもっともらしい文章だけを返し、元の書類のどこに書いてあるか分からない

【なぜ危険か】 出典がわからないと、スタッフは「本当にこの回答を信じていいのかな?」と不安になり、結局自分で最初からマニュアルを探し直すことになります。

【回避策】 すべての回答に必ず「文書名・章タイトル・ページ番号」のリンクを添え、1クリックで原文の該当箇所が開くように設計します。

05. 御社の「社内ナレッジ検索・情報共有」3項目セルフ診断

該当数:0 / 3 項目

社内の日常業務で当てはまるものにチェックを入れてみてください。

上のチェックボックスを選択すると、おすすめの導入ステップが表示されます。

06. 理解を深める確認クイズ(解説付き)

Q. 社内の機密文書を検索するAIシステムにおいて、「一般社員が質問したときに、役員専用の機密議事録の内容が誤って回答に含まれてしまう事故」を確実に防ぐ方法はどれでしょうか?

A. AIへのプロンプト(指示文)に「あなたは配慮したAIなので、一般社員には役員の秘密を原則として言わないでください」と書いておく 答えを見る ↓

不正解です(非常に危険です!)。 AIに機密データを渡した上で「言わないでね」と指示するだけでは、質問の工夫次第で簡単に秘密を喋らせてしまうことができます。

B. AIが回答を作る前の「データベース検索の段階」で、PostgreSQLの行レベルセキュリティ(RLS)を使い、質問者の権限で閲覧できる書類しか検索されないように遮断する 答えを見る ↓

正解です! 権限のないデータはそもそもデータベースから1行も読み出されず、AIの目にも触れないようにする(データベースカーネル層での強制アクセス制御)ことこそが、原則として漏洩しないナレッジ検索の鉄則です。

C. 社員全員に「役員向けの質問をするのは禁止」という誓約書を書いてもらい、データは誰でも見られる状態にしておく 答えを見る ↓

不正解です。 悪意がなくても、普通の業務質問をした際にAIが勝手に役員議事録を関連資料として拾ってきてしまうため、システムの仕組み(RLS)で分離することが必須です。

関連コラム・豆知識

なぜ「意味を数字の矢印(ベクトル)にする」と、違う言葉で書かれた同じ内容が見つかるのか?

従来の検索では、マニュアルに「冷却装置の不具合」と書いてあるとき、社員が「エアコンが冷えない」と検索すると1件もヒットしませんでした。これに対し、現代のベクトル検索(pgvector)は、言葉の意味を数百〜数千次元の「方角(矢印)」に変換して保管します。「冷却装置の不具合」と「エアコンが冷えない」は文字はまったく違いますが、意味の方角がほぼ同じ向きを指すため、コンピュータが「この2つは同じ意味の困りごとだ!」と瞬時に見つけ出すことができるのです。

御社の「社内マニュアル・過去見積・技術資料」を安全に3秒検索化しませんか?

外部学習ゼロ・PostgreSQL 19 行レベル権限管理つきの安全な社内AIナレッジ基盤構築

閉域RAG・個別相談・設定確認へ →
技術仕様・実装解説(HYBRID RRF (BM25 + HNSW), PG19 FORCE RLS & PII REDACTION) 4. 詳しい技術仕様としくみ
Verified Production Spec:Rust 2024 & PostgreSQL 19

Reciprocal Rank Fusion(BM25疎検索 × pgvector HNSWコサイン類似度)・PostgreSQL 19 FORCE ROW LEVEL SECURITY ベクトルフィルタリングとPII秘匿化チャンキングのRust 2024実装

本稿では、社外秘文書・医療福祉記録・法務契約書を扱うマルチテナント・ロール階層型RAG基盤(db01.ok.st)の内部設計を詳解し、(1) 語彙一致(BM25)と階層的ナビゲーション小世界グラフ(HNSW)密ベクトル検索を無パラメータ統合する Reciprocal Rank Fusion(RRF)、(2) PostgreSQL 19 カーネル層でプロンプトインジェクションによる越権情報漏洩を物理遮断する FORCE ROW LEVEL SECURITY 述語プッシュダウン、および (3) Rust 2024によるPII自動秘匿化・RRF融合エンジンを定式化する。

Vector Database
db01 PostgreSQL 19 + pgvector HNSW
Hybrid Ranking
RRF (k = 60, BM25 + Cosine)
Access Isolation
PG19 FORCE RLS Tenant/Role Gate
Privacy Guarantee
Pre-Embedding PII Redaction
1. 設計仕様・数理モデル(Reciprocal Rank Fusion, HNSW Graph Traversal & Kernel-Enforced RLS)

1. 【Reciprocal Rank Fusion(RRF)による疎密ハイブリッド検索の正規化統合】 型番・法令条文番号に強い疎ベクトル全文検索スコア BM25(q, d)(値域 [0, ∞))と、意味的言い換えに強い密ベクトル余弦類似度 sim_cos(e_q, e_d) = (e_q · e_d)/(∥e_q∥_2 ∥e_d∥_2)(値域 [-1, 1])は確率分布のスケールが異なるため、単純な線形和ではクエリごとに重みが不安定化する。各検索器 m ∈ {BM25, HNSW} における文書チャンク d の順位 r_m(d) ∈ {1, 2, …} のみを用いる RRF スコア: RRF(d | q) = ∑m ∈ {BM25, HNSW} (w_m)/(k_rrf + r_m(d)) (k_rrf = 60) により、スコア分布の歪みに一切影響されないロバストな上位 K チャンク抽出を実現する。

2. 【PostgreSQL 19 FORCE ROW LEVEL SECURITY による越権検索の数学的消滅】 アプリケーション層の WHERE 句組み立てに依存する認可実装は、バグやSQL/プロンプトインジェクションによるバイパスリスクを伴う。db01.ok.st では全ナレッジチャンク表に対して ALTER TABLE knowledge_chunks ENABLE ROW LEVEL SECURITY; ALTER TABLE knowledge_chunks FORCE ROW LEVEL SECURITY; を強制し、トランザクション開始時に SET LOCAL app.current_tenant_id = '...'; SET LOCAL app.clearance_mask = '...'; をバインドする。これにより、HNSWインデックス走査の各ホップにおいてポリシー述語: P_RLS(d) ⇔ (d.tenant_id = current_setting('app.current_tenant_id')) ∧ ((d.acl_mask \& user_mask) ≠ 0) がデータベースカーネル内で強制評価され、権限外チャンクがLLMコンテキストへ混入する確率は厳密に 0 となる。

3. 【埋め込み前の決定的PII秘匿化(Pre-Embedding PII Redaction)】 ベクトル埋め込みベクトル e_d ∈ ℝ^1536 から元テキストの個人情報を逆推定するEmbedding Inversion攻撃を防ぐため、文書をチャンク分割してEmbedding APIへ送信する前段において、電話番号・マイナンバー・メールアドレス・銀行口座番号を決定性有限オートマトンで検出し、型付きプレースホルダ(<PII:PHONE_JP> 等)へ不可逆置換する。

2. パラメータ・構成仕様(PostgreSQL 19 pgvector & Secure RAG Pipeline Parameters)
Subsystem Layer Configuration / Index Spec Parameter Value Security & Latency Guarantee
pgvector HNSW Index USING hnsw (embedding vector_cosine_ops) m = 16, ef_construction = 128, ef_search = 80 Recall@10 ≥ 0.985, Query ≤ 6.2ms
Row-Level Security PG19 FORCE ROW LEVEL SECURITY Tenant UUID + 64-Bit Clearance Bitmask 0% Cross-Tenant / Cross-Role Leakage
Hybrid RRF Merger BM25 Rank + Cosine Rank Fusion k_rrf = 60, Top-K = 5 Grounded Chunks Exact Part-Number & Semantic Match
Abstention Gate Minimum Cosine & RRF Threshold sim_cos ≥ 0.780 Required to Answer Deterministic Abstention on Unknowns
3. 実装コード・設定仕様(Executable Specification Code (crates/ok-privacy-rag/src/rrf_and_rls.rs)) Rust 2024(RBAC Clearance Bitmask Filter, RRF Hybrid Ranker & PII Redactor)
#![forbid(unsafe_code)]

/// Document chunk metadata retrieved from db01 PostgreSQL 19 knowledge store.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KnowledgeChunkCandidate {
    pub chunk_id: u64,
    pub tenant_id: u64,
    pub required_clearance_mask: u64,
    pub is_latest_revision: bool,
    pub bm25_rank: Option<u32>,  // 1-indexed rank from lexical search
    pub dense_rank: Option<u32>, // 1-indexed rank from pgvector HNSW search
    pub cosine_similarity_permille: u16,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScoredChunkResult {
    pub chunk_id: u64,
    /// Fixed-point RRF score scaled by 1_000_000 for deterministic integer sorting.
    pub rrf_score_micros: u64,
    pub cosine_similarity_permille: u16,
}

pub const RRF_K_CONSTANT: u64 = 60;
pub const MIN_GROUNDED_SIMILARITY_PERMILLE: u16 = 780;

/// Enforces tenant isolation, role clearance bitmask, revision freshness, and computes
/// deterministic Reciprocal Rank Fusion (RRF) scores without floating-point non-determinism.
pub fn rank_and_filter_authorized_chunks(
    request_tenant_id: u64,
    user_clearance_mask: u64,
    candidates: &[KnowledgeChunkCandidate],
    top_k: usize,
) -> Vec<ScoredChunkResult> {
    let mut authorized_scored: Vec<ScoredChunkResult> = Vec::new();

    for c in candidates {
        // Hard security invariant: tenant match, active revision, and RBAC bitmask authorization
        if c.tenant_id != request_tenant_id
            || !c.is_latest_revision
            || (c.required_clearance_mask & user_clearance_mask) != c.required_clearance_mask
        {
            continue;
        }

        if c.cosine_similarity_permille < MIN_GROUNDED_SIMILARITY_PERMILLE && c.bm25_rank.is_none() {
            continue;
        }

        let mut score_micros: u64 = 0;
        if let Some(r_bm25) = c.bm25_rank {
            score_micros += 1_000_000 / (RRF_K_CONSTANT + (r_bm25 as u64));
        }
        if let Some(r_dense) = c.dense_rank {
            score_micros += 1_000_000 / (RRF_K_CONSTANT + (r_dense as u64));
        }

        if score_micros > 0 {
            authorized_scored.push(ScoredChunkResult {
                chunk_id: c.chunk_id,
                rrf_score_micros: score_micros,
                cosine_similarity_permille: c.cosine_similarity_permille,
            });
        }
    }

    // Sort descending by RRF score, then by cosine similarity, then ascending by chunk_id
    authorized_scored.sort_by(|a, b| {
        b.rrf_score_micros
            .cmp(&a.rrf_score_micros)
            .then_with(|| b.cosine_similarity_permille.cmp(&a.cosine_similarity_permille))
            .then_with(|| a.chunk_id.cmp(&b.chunk_id))
    });

    authorized_scored.truncate(top_k);
    authorized_scored
}

/// Redacts long contiguous digit sequences (8..=16 digits, e.g., phone/account/ID numbers)
/// prior to vector embedding to prevent PII leakage into embedding space.
pub fn redact_digit_pii_sequences(input: &str) -> String {
    let mut out = String::with_capacity(input.len());
    let mut digit_run = String::new();

    for ch in input.chars() {
        if ch.is_ascii_digit() || ch == '-' {
            digit_run.push(ch);
        } else {
            let raw_digits = digit_run.chars().filter(|c| c.is_ascii_digit()).count();
            if (8..=16).contains(&raw_digits) {
                out.push_str("[PII_REDACTED]");
            } else {
                out.push_str(&digit_run);
            }
            digit_run.clear();
            out.push(ch);
        }
    }
    let trailing_digits = digit_run.chars().filter(|c| c.is_ascii_digit()).count();
    if (8..=16).contains(&trailing_digits) {
        out.push_str("[PII_REDACTED]");
    } else {
        out.push_str(&digit_run);
    }
    out
}
検証環境・運用について: db01.ok.st(FreeBSD + OpenZFS + PostgreSQL 19 pgvector)上で稼働する当社の閉域RAG基盤は、行レベルセキュリティ(RLS)とRRFハイブリッド検索により、情報漏洩ゼロと高精度な出典提示を両立しています。
技術仕様・共同研究のお問い合わせ
ご質問・公的制度の活用に関するお問い合わせ窓口

掲載資料に関するご質問・公的制度活用のご案内

本ページの図解や解説内容に関するご質問、経済産業省「SCS評価制度」やIPA「SECURITY ACTION・サイバーセキュリティお助け隊サービス」等の公的制度の確認方法について、落ち着いた参考窓口としてご案内しております。売り込みや営業連絡は一切行いませんので、社内勉強会やご家庭での確認資料としてご自由にご活用ください。

図解・ビジュアルの拡大表示

図表内のテキストもそのまま選択・検索可能です(ベクターSVG描画) 枠外クリックまたは ESC キーで閉じます