db-multitenancy について
db-multitenancyは、マルチテナントのデータベーススキーマを評価するスキルです。テナント分離とテナント単位の性能を確認し、読み取り専用のPostgreSQLクエリで状態を検証できます。
主な機能
- 01テナントモデルと要件の適合性を評価
- 02テナント列の欠落やNULL許可を検出
- 03行レベルの分離強制を確認
- 04テナント列が先頭のキーとインデックスを検証
- 05テナント間の外部キーと一意制約を検出
活用シーン
- 01デプロイ前のマルチテナントスキーマ審査
- 02テナント分離が弱いテーブルの発見
- 03テナント単位で絞り込めないインデックスの特定
- 04PostgreSQLのRLSとインデックス状態の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ hainrixz/ claude-db --skill db-multitenancyインストール後、Claude Code で/db-multitenancyを入力して読み込まれたか確認します。
詳細
db-multitenancy (M9)
マルチテナントシステムでは、テナント境界がスキーマにおける最も重要な制約です。これを忘れたクエリが1つでもあると、ある顧客のデータが別の顧客に漏えいします。また、これはパフォーマンスにも関わります。すべてのアクセスでテナントごとに絞り込めるよう、tenant_id はキーとインデックスの先頭に配置する必要があります。このモジュールは両方の軸を扱います。設計面では分離(セキュリティ)、パフォーマンス面ではインデックスやパーティションの局所性(スケーラビリティ)です。シングルテナントシステムでは、完全にスキップします(not_applicable)。
チェック項目
- テナントモデルの適合性:共有テーブル(
tenant_id列)、テナントごとのスキーマ、テナントごとのデータベースのいずれを採用しているかを確認します。分離要件に合わないモデル(例:RLS なしで、厳格な分離が必要な規制対象のワークロードに共有行を使用)にフラグを立てます。 - テナントスコープの欠落:テナント所有テーブルに
tenant_id/org_id/account_id列がない場合、またはテナント列が NULL 許容で、スコープされない行が許される場合を検出します。 - 分離の強制不足:テナントスコープをアプリケーションコードのみに依存し、RLS ポリシー(Postgres)も制約もない場合を検出します。
WHERE tenant_id = ?の記述が1か所でも欠けると、データが漏えいします。M10 に関連します(依存対象のテナントテーブルで RLS が無効なら重大度5)。 tenant_idを先頭にしたインデックス/キー:tenant_idが先頭にないインデックスや PK/クラスタリングキーを検出します。その場合、エンジンが単一テナントに絞り込めず、テナントをまたぐ全テーブルスキャン(パフォーマンス低下)が発生し、分離も弱まります。M11 の ESR 順序と M16 のパーティショニングに関連します。- テナントをまたぐ FK/UNIQUE:テナント列を含まない一意制約や FK を検出します。これにより、テナント間での重複や結合が許されます。
軸と重大度
- 依存対象の共有テナントテーブルで RLS が無効、または分離が強制されていない:重大度5、
fail、軸both、信頼度established(上限値。上限値自体は M10 の RLS ルールが所有・判定します。二重計上しないよう調整してください)。 - テナント所有テーブルに
tenant_idがない:重大度4、warn/fail、軸both。 tenant_idが先頭にないインデックス/PK:重大度3、warn、軸performance。- テナント列を含まない UNIQUE/FK:重大度3~4、
warn、軸both。
Tier-0 静的チェック
scripts/parse-schema.mjs で DDL/スナップショットを解析し、テナント所有テーブル(ヒューリスティック:マルチテナントと判定されたシステムのビジネステーブル)を特定します。各テーブルに NULL 不可のテナント列があるか確認し、PK/UNIQUE/インデックスでその列が先頭にあることを検証し、宣言済みの RLS ポリシーを検出します。プログラムソースの解析結果は directional のままとし、重大度5の上限値を引き上げることはありません。
Tier-1 検証クエリ
RLS の状態と、tenant_id を先頭にしたインデックスを確認します。
-- $DATABASE_URL, read-only
SELECT relname, relrowsecurity, relforcerowsecurity
FROM pg_class WHERE relkind = 'r' AND relnamespace = 'public'::regnamespace;
-- is tenant_id the leading index column?
SELECT i.indexrelid::regclass AS index, a.attname AS first_col
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = i.indkey[0]
WHERE i.indrelid = '<table>'::regclass;
ライブ DB がない場合、RLS/強制状況に関する検出結果は needs_api のままとし、暗黙に合格とはしません。
検出結果
schema/finding.schema.json に従って出力します。例:
M9.documents.rls_off_shared_tenant— 分離のために依存している共有テナントテーブルで RLS が無効(重大度5、fail、軸both、信頼度established)。M9.invoices.no_tenant_id— テナント所有テーブルにtenant_id列がない(重大度4、warn、軸both)。M9.events.index_not_tenant_leading— インデックスの先頭がtenant_idではなくcreated_at。テナントごとの絞り込みができない(重大度3、warn、軸performance)。 各検出結果では、evidence.observedに DDL/インデックス/ポリシーを一字一句そのまま引用します。verification.reproduceには、上記の実行可能なクエリを指定します(method: ddl_parse/schema_introspect/index_check)。expected_impactは区分と信頼度タグを付けます。
誠実性
- 分離に関する重大度5の上限値を適用するには、そのテーブルがテナント分離のために実際に依存されている必要があります。RLS に関するライブの証拠または宣言済みの証拠がなければ、結果は
needs_apiまたはdirectionalとし、暗黙に上限値を適用しないでください。 - アプリケーションレベルの
WHERE tenant_id = ?は実際の緩和策ですが、データベースによる保証ではありません。条件を1つでも忘れるとデータが漏えいします。その点を明記し、RLS と同等のものとして扱わないでください。 - シングルテナントシステムは
not_applicable(重大度0、スコアリング対象外)とします。テナント要件を捏造してはなりません。