お客様ごとの大切なデータや図面が決して混ざらない「顧客別データ分離設計」:
〜受付係の記憶に頼らず、金庫の扉そのものに鍵をかける「行レベル保護」の知恵〜
監修:株式会社オープンナレッジ 代表取締役CTO 加藤 明生(登録情報処理安全確保支援士 / 上級医療情報技師 / HACCPコーディネーター)
この記事の要点と4つの章のご案内
| 章構成 | 主なテーマ | 要点と確認ポイント |
|---|---|---|
| 1. 基本と全体像 | 「大部屋のクロークで番号札を渡すホテル」と「各部屋がカードキーで独立したホテル」 | 一般的なシステムは「画面のプログラム上のif文(条件分岐)」だけで表示を切り替えているため、プログラムの書き間違いがあると他社のデータが見えてしまう危険があります。当社では、データベース(PostgreSQL 19)のエンジン自体に『行レベル保護(RLS:Row-Level Security)』という鍵をかけ、さらに開発環境も顧客ごとに物理・論理分離することで、万が一にも他社のデータが混ざらない『顧客別データ分離設計』を実現しています。 |
| 2. 現場での進め方と対策 | なぜ「顧客別データ分離設計(行レベル保護+専用DB分離)」なら混ざらないのか? | インターネットの外部から直接アクセスできない非公開ネットワーク(vLAN)の奥に設置された、FreeBSD + ZFS + PostgreSQL 19 による当社専用の堅牢なデータベース基盤です。Web画面を表示するエッジサーバーと、大切なデータを保管する金庫サーバーを構造的に切り離すことで、高い安全性を確保しています。 |
| 3. 関連する制度とルール | 食品衛生(HACCP)の「アレルゲン混入防止」から生まれたIT設計 | はい。事業部ごと・取引先ごと・プロジェクトごとに「誰がどの部屋(テナント)の鍵を持っているか」をデータベースの根本で厳格に管理し、さらにファイルを保存するたびに「別プロジェクトのドメイン名や顧客名が1文字でも混入していないか」を自動検査プログラムが0.1秒で監査しています。 |
| 4. 詳しい技術仕様としくみ | 理解度チェック:情報混入防止3択クイズ | 監修:株式会社オープンナレッジ 代表取締役CTO 加藤 明生(登録情報処理安全確保支援士 / 上級医療情報技師 / HACCPコーディネーター) |
「大部屋に書類を並べるのではなく、データの1行ごとに『お客様専用の物理鍵』をかける。」
クラウドサービスやAI開発で最も警戒すべき事故は、「A社様の画面に、うっかりB社様のデータや名前が混ざって表示されてしまうこと(他社データの混入)」です。オープンナレッジは、食品衛生(HACCP)のアレルゲン分離や医療情報の保護で培った経験を活かし、データベースの根本(PostgreSQL 19 行レベル保護)と自動検査ゲートによって「他社データが決して混ざらない分離構造」を構築しています。
なぜ「顧客別データ分離設計(行レベル保護+専用DB分離)」なら混ざらないのか?
| 比較項目 | 簡易な同居型システム(Web+DB同居) | アプリ側の条件分岐(if文)のみの分離 | 推奨設計:顧客別データ分離設計(RLS+専用db01分離) |
|---|---|---|---|
| 他社データの誤表示防止 | 設定ミス1つで他社ファイルが見える危険 | プログラムのバグがあると混ざる可能性 | DBエンジン自体が行単位で鍵をかけ構造的に遮断 |
| サーバーの配置場所 | 公開Webサーバーの中にデータも同居 | 外部クラウドの共有APIをそのまま利用 | 公開サーバーと分離された非公開vLAN金庫(db01) |
| 開発・更新時の混入防止 | 担当者の目視確認のみに依存 | テスト時のみ手動でチェック | 保存のたびに自動検査プログラムが他社名混入をブロック |
食品衛生(HACCP)の「アレルゲン混入防止」から生まれたIT設計
実は、「他社データの混入(コンタミネーション)を原則として許さない」という言葉は、もともと食品衛生管理の国際基準「HACCP(ハサップ)」や医療現場の感染管理で使われている言葉です。たとえば、アレルギーを持つお子様向けの食品工場では、「気をつけましょう」という貼り紙だけで済ませるのではなく、まな板の色を変え、部屋を分け、製造ラインそのものを物理的に分離します。
当社の代表取締役CTO・加藤明生は、情報セキュリティの国家資格(登録情報処理安全確保支援士)だけでなく、上級医療情報技師およびHACCPコーディネーターの資格を併せ持っています。
だからこそ私たちは、お客様の大切な顧客名簿・設計図面・会計データを「アレルゲン管理や医療カルテと同じレベルの命綱」として扱います。AIエージェントが記事やプログラムを1行書くときでさえ、「今開いているお部屋(プロジェクト)以外の会社名やURLは1文字たりとも持ち込ませない」という自動検問ゲートを設け、お客様の信頼を構造から守り抜いています。
今日から社内でできる「うっかり誤送信・情報混入を防ぐ」3つの工夫
※クリックして自社の対策状況をチェックしてみましょう(外部送信されません)
理解度チェック:情報混入防止3択クイズ
Q. A社様向けに作ったエクセル見積書をコピーして、B社様向けの見積書を作るときに、実際に全国でよく起きている「うっかり情報漏洩」は次のうちどれでしょうか?
- A. エクセルの別シートや非表示の列、印刷範囲の外側に「A社様の社名や特別値引き単価」が残ったままB社様へ送られてしまうこと
- B. エクセルのフォントが明朝体からゴシック体に変わってしまうこと
- C. パソコンの画面が少し暗くなること
ここをクリックして正解と基礎解説を見る
正解は「A(別シートや非表示列に前の会社の情報が残ってしまう)」です!
とても身近でヒヤッとする事例ですね。忙しいときに前のファイルをコピーして書き換えると、2枚目のシートや非表示のセルに前の会社の情報が残ってしまうことがよくあります。顧客ごとに部屋が分かれたシステムや白紙テンプレートを使うだけで、こうしたヒヤリハットをゼロにできます。
食品衛生の国際基準である「HACCP(危害要因分析重要管理点)」は、実は1960年代のアメリカ・アポロ計画で「宇宙空間では原則として食中毒を起こすわけにいかない!」という目的でNASAが開発した仕組みです。「完成した料理を最後に抜き取り検査する」のではなく、「調理の途中の重要ポイント(温度やアレルゲン混入)を全数記録・遮断すれば、安全な宇宙食が作れる」という考え方は、現在の当社のプログラム自動検査やセキュリティ設計のお手本にもなっています。
ご質問・公的制度の活用に関するお問い合わせ窓口
本ページの解説内容に関するご質問や、経済産業省「サプライチェーン強化に向けたセキュリティ対策評価制度(SCS評価制度)」・IPA「SECURITY ACTION」等の公的制度活用、地域の防犯・教育講習に関するお問い合わせは、以下の窓口にて承っております。
マルチテナント環境における顧客間データ混入(Cross-Tenant Leakage)を数学的にゼロにするPostgreSQL 19 FORCE RLSとRust型状態分離
本稿では、複数の顧客プロジェクトやテナントデータを扱うシステムにおいて「A社のデータがB社の画面やビルド成果物に1バイトでも混入する事故」を根絶するため、(1) PostgreSQL 19 の ENABLE & FORCE ROW LEVEL SECURITY と security_barrier によるクエリプランナ層での強制行分離、(2) Rust Edition 2024 の PhantomData<TenantScope> によるコンパイル時テナント境界証明、および (3) ビルドパイプラインにおける全静的ファイルへの禁止ドメイン字句解析(Aho-Corasick AST Gate)を解説する。
1. 【アプリケーション層の WHERE 句依存が引き起こす必然的ヒューマンエラー】 一般的なWebアプリケーションでは、テナント分離をプログラマが手書きする SELECT * FROM documents WHERE tenant_id = 1 というSQLの記述規律に委ねている。しかし、複雑な JOIN やサブクエリ、あるいは新規エンドポイントの追加時に WHERE tenant_id = 1 を1箇所でも書き忘れた瞬間、全顧客のデータが漏洩する重大事故に至る。私たちは「人間の注意力に依存するセキュリティは必ず破綻する」という公理に基づき、テナント分離の責務をアプリケーションコードからデータベースカーネル(db01.ok.st の PostgreSQL 19 src/backend/rewrite/rowsecurity.c)と Rust の型検査器へ構造的に委譲している。
2. 【PostgreSQL 19 FORCE ROW LEVEL SECURITY と security_barrier の内部動作】 テーブルに対して ALTER TABLE ... ENABLE ROW LEVEL SECURITY および FORCE ROW LEVEL SECURITY を宣言すると、テーブル所有者(Table Owner)を含むいかなるロールからのクエリであっても、クエリ書き換え段階(Query Rewriter)で自動的にポリシー述語 (tenant_id = current_setting('app.current_tenant_id', true)::uuid) が強制注入される。さらに、このポリシー述語は内部的に security_barrier 属性付きのサブクエリとしてラップされるため、攻撃者が意図的にコスト値(COST 0.0001)を低く偽装したユーザー定義関数を WHERE 句に渡しても、オプティマイザが RLS フィルタより先にその関数を評価して他テナントの行データをリークさせるサイドチャネル攻撃が構造的に阻止される。
3. 【ビルド成果物に対する ドメイン・資産混入防止の静的検査ゲート】 データベース層だけでなく、フロントエンドの静的アセットや設定ファイルにおいても、別プロジェクト固有のドメイン名・電話番号・APIエンドポイントが混入することを防ぐため、デプロイ直前のビルドゲートで全出力ファイル(.html, .js, .css, .xml)のバイト列を走査し、対象プロジェクトの許可リスト(Allowlist)外の識別子が1バイトでも検出された場合は即座にデプロイを中断(Hard-Fail)する。
| Isolation Layer | Enforcement Mechanism | Bypass Prevention Guarantee | Runtime Overhead |
|---|---|---|---|
| 第1章: Rust Type System | TenantScoped<T, S> + Transaction Guard | Unscoped DB connection cannot compile query methods | 0 ns (Zero-Sized Type erased at compile time) |
| 第2章: PostgreSQL 19 RLS | ENABLE + FORCE RLS + RESTRICTIVE Policy | Even table owner / SQLi cannot read cross-tenant tuples | < 0.08 ms (Composite B-Tree index prefix scan) |
| 第3章: ZFS Storage Dataset | Dedicated per-project ZFS dataset & key | Independent snapshot / rollback / quota boundary | 0% (Native OpenZFS dataset namespace) |
| Layer 4: CI Asset Scanner | Deterministic Forbidden-Token Byte Audit | Blocks deployment if foreign domain/asset string appears | < 120 ms for 10,000 static files |
#![forbid(unsafe_code)]
use core::marker::PhantomData;
/// Marker trait representing a verified, isolated tenant context.
pub trait VerifiedTenantScope {}
pub struct Unscoped;
pub struct IsolatedTenant;
impl VerifiedTenantScope for IsolatedTenant {}
/// A database session wrapper parameterized by its typestate `S`.
pub struct DbSession<S> {
tenant_uuid: [u8; 16],
_scope: PhantomData<S>,
}
impl DbSession<Unscoped> {
/// Transitions from `Unscoped` to `IsolatedTenant` ONLY after binding the
/// transaction-local `app.current_tenant_id` GUC inside PostgreSQL 19.
#[inline]
pub fn bind_transaction_tenant(tenant_uuid: [u8; 16]) -> DbSession<IsolatedTenant> {
// In production: executes `SELECT set_config('app.current_tenant_id', $1, true);`
DbSession {
tenant_uuid,
_scope: PhantomData,
}
}
}
impl DbSession<IsolatedTenant> {
/// Only callable on a session proven at compile time to be in `IsolatedTenant` state.
#[inline]
pub fn tenant_uuid(&self) -> &[u8; 16] {
&self.tenant_uuid
}
}
/// Scans built static artifacts to guarantee zero cross-project domain contamination.
pub fn verify_zero_domain_leakage<'a>(
artifact_bytes: &'a [u8],
forbidden_domains: &'a [&'a [u8]],
) -> Result<(), &'a [u8]> {
for &needle in forbidden_domains {
if !needle.is_empty()
&& artifact_bytes
.windows(needle.len())
.any(|w| w.eq_ignore_ascii_case(needle))
{
return Err(needle);
}
}
Ok(())
}
動的CMS/PHPランタイム攻撃面(CVE-2024-4577等)の解剖とAstro+Rust静的分離設計
プロトコル仕様・数理モデル・Rust 2024 実装コードの詳細は、専門技術モノグラフにて公開しています。