mcpField
D
db-multitenancy

db-multitenancy

github.com/hainrixz数据与数据库2174 次安装4 天前更新
代码仓库

关于 db-multitenancy

db-multitenancy 是用于审查多租户数据库结构的技能,关注租户隔离和面向租户的性能。它检查模式定义,也可通过只读 PostgreSQL 查询验证数据库状态。

主要功能

  1. 01评估租户模型是否匹配需求
  2. 02查找缺失或可为空的租户列
  3. 03检查行级隔离是否得到强制执行
  4. 04验证键和索引是否以租户列开头
  5. 05检测跨租户外键和唯一约束

适用场景

  1. 01部署前审查多租户模式
  2. 02发现租户隔离薄弱的表
  3. 03识别无法按租户裁剪的索引
  4. 04验证 PostgreSQL RLS 和索引状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/hainrixz/claude-db --skill db-multitenancy

安装后在 Claude Code 中输入/db-multitenancy确认已加载。

详细介绍

db-multitenancy (M9)

在多租户系统中,租户边界是架构中最重要的约束:单个查询如果忘记限定租户,就会将一个客户的数据泄露给另一个客户。这也是一个性能维度——tenant_id 必须排在键和索引的首位,确保每次访问都能按租户筛选。本模块同时涉及两个维度:设计层面的隔离(安全性)和性能层面的索引/分区局部性(扩展性)。如果系统是单租户,请完全跳过(not_applicable)。

检查内容

  • 租户模型是否适用:共享表(tenant_id 列)、每租户一个 schema,还是每租户一个数据库——如果模型与隔离要求不匹配,则标记(例如,对于要求严格隔离/受监管的工作负载,使用没有 RLS 的共享行)。
  • 缺少租户范围限定:租户所属的表缺少 tenant_id/org_id/account_id 列,或者租户列可为空(允许出现未限定租户的行)。
  • 隔离未强制执行:仅依赖应用代码限定租户范围,既没有 RLS 策略(Postgres),也没有约束——漏掉一个 WHERE tenant_id = ? 就会泄露数据。与 M10 相关(依赖的租户表关闭 RLS 时 = sev5)。
  • 索引/键以 tenant_id 开头:索引和 PK/聚簇键没有以 tenant_id 开头,因此引擎无法筛选到单个租户——会跨租户执行全表扫描(性能),隔离也更弱。与 M11 的 ESR 排序和 M16 分区相关。
  • 跨租户 FK/UNIQUE:定义唯一约束或 FK 时未包含租户列,可能导致跨租户冲突/联接。

维度与严重性

  • 依赖的共享租户表关闭 RLS/没有强制隔离:严重性 5,fail,维度 both,置信度 established(上限;该上限本身由 M10 的 RLS 规则负责断言/执行——需协调,避免重复计数)。
  • 租户所属的表缺少 tenant_id:严重性 4,warn/fail,维度 both。
  • 索引/PK 未以 tenant_id 开头:严重性 3,warn,维度 performance。
  • UNIQUE/FK 未包含租户列:严重性 3–4,warn,维度 both。

Tier-0 静态检查

通过 scripts/parse-schema.mjs 解析 DDL/快照:识别租户所属的表(启发式方法:被标记为多租户系统中的业务表),检查每个表是否有非空租户列,验证 PK/UNIQUE/索引是否以该列开头,并检测已声明的 RLS 策略。对程序源代码的解析结果仍为 directional,绝不会触发 sev-5 上限。

Tier-1 验证查询

检查 RLS 状态和以租户列开头的索引:

-- $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;

没有实时数据库时,RLS/强制执行相关发现保持为 needs_api,绝不能默认为通过。

发现项

按 schema/finding.schema.json 输出。示例:

  • M9.documents.rls_off_shared_tenant — 依赖 RLS 实现隔离的共享租户表已关闭 RLS(严重性 5,fail,维度 both,置信度 established)。
  • M9.invoices.no_tenant_id — 租户所属的表没有 tenant_id 列(严重性 4,warn,维度 both)。
  • M9.events.index_not_tenant_leading — 索引以 created_at 而非 tenant_id 开头;无法按租户筛选(严重性 3,warn,维度 performance)。 每项发现都应满足:evidence.observed 原样引用 DDL/索引/策略;verification.reproduce 是上文可运行的查询(method: ddl_parse/schema_introspect/index_check);expected_impact 标注影响等级和置信度。

诚实原则

  • sev-5 隔离上限要求该表确实被用于租户隔离;没有实时/已声明的 RLS 证据时,结果应为 needs_api 或 directional,绝不能默默触发上限。
  • 应用层的 WHERE tenant_id = ? 是实际的缓解措施,但并非数据库保证——漏掉一个子句就会泄露数据。应明确指出这一点;不要将其视为等同于 RLS。
  • 单租户系统标记为 not_applicable(严重性 0,不计入评分)——绝不要凭空要求多租户能力。

评论

支持 Markdown