
Rls Doctor
io.github.subhajitluckyv0.5.8更新于 Oct 11, 2026
Read-only Postgres/Supabase RLS audit with a rolled-back probe that proves who can read which rows.
概览
只读的 Postgres 与 Supabase RLS 审计工具,检查目录中的策略并证明应用角色实际能读取哪些行。
- 功能
- 通过 stdio 提供只读 MCP 工具:describe_capabilities、check_rls、probe_access、explain_table、prove_isolation 和 propose_repair。它审计目录中可见的行级安全、授权和角色路径;probe 会在始终回滚的只读事务中模拟应用角色,展示跨属主或跨主体的读取。它还能从 SQL 架构文件离线证明隔离声明,并生成经静态验证的修复建议而不写入文件。
- 适用场景
- 适用于审查或加固 Postgres 与 Supabase 的行级安全,例如检查一个用户能否读取另一个用户的行、在没有数据库的情况下审计迁移文件,或把 RLS 检查加入 CI。它是安全审查辅助工具,不能替代应用层授权测试或完整的生产安全审计。
- 运行要求
- 以 npm 包(rls-doctor)在本地通过 stdio 运行,需要 Node.js 20 或更高版本。数据库审计需要 Postgres 连接字符串,最好通过 DATABASE_URL 或 SUPABASE_DB_URL 提供只读审计凭据;离线架构文件检查不需要数据库。Docker 对 shadow 验证和 demo 是可选的。
安装
在 SourceWeft 中
- 打开 控制台中的 Rls Doctor,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
RLS Doctor
Don't trust your RLS. Prove it.
Can user A read user B's rows? Row Level Security is one of Postgres's strongest isolation tools, and one of the easiest to leave half-configured. You enable RLS, add a policy, and move on — but policies OR-combine, WITH CHECK silently falls back to USING, table owners bypass RLS entirely, and TRUNCATE is never protected at all.
rls-doctor reads your catalog and tells you what's actually reachable. And with probe, it stops analyzing and just shows you, impersonating your app roles in a transaction it always rolls back.
Prove a cross-tenant leak instead of guessing
Most RLS review is reading policies and hoping. probe is behavioral:
It sets request.jwt.claims inside the transaction, so auth.uid() resolves to each real Supabase user, then counts whose rows that identity can read.
It never mutates anything. probe opens begin ... read only and always rolls back — safe against a production read replica.
Install
Requires Node.js 20 or later. Prefer DATABASE_URL or SUPABASE_DB_URL with read-only audit credentials — passing a secret via --connection exposes it in shell history and process listings.
Commands
check options
On generic Postgres (not Supabase), pass your real roles so grants and memberships through them are analyzed at the same severity as authenticated:
probe options
Probe findings: probe-cross-owner-read and probe-cross-subject-read (High), probe-role-unavailable (Medium), probe-error (Low), plus informational probe-reads-rows, probe-subject-reads-own-rows, probe-reads-no-rows, and probe-read-denied.
What it catches
Checks follow PostgreSQL command semantics: SELECT evaluates USING; INSERT evaluates WITH CHECK; DELETE evaluates USING; UPDATE evaluates both. When an UPDATE or ALL policy omits WITH CHECK, Postgres falls back to its USING expression — and rls-doctor analyzes that effective check. ALL is evaluated as each applicable command.
It follows direct, inherited, and SET ROLE membership paths (including PostgreSQL 16 per-membership options; 15 is normalized), and reports current access separately from default privileges affecting future tables.
Ecosystem study
We statically audited the migrations of 60 public Supabase projects: 63% contain at least one high or critical RLS pattern (median score 38/100), with coverage limitations reported honestly in 57% of them. Aggregate, anonymized, reproducible — read the report and the disclosure policy.
GitHub Action
Findings land in the Security tab.
SARIF results carry logicalLocations (schema.table) and stable partialFingerprints, so code scanning tracks findings across runs even though tables aren't files.
Or run it in CI without the Action
Full guide: docs/guides/github-actions.md
Baselines
Adopt on an existing database without failing on day one:
Fingerprints derive from finding id, schema, table, and detail — so a changed predicate or privilege correctly re-reports as new. JSON reports include baseline: { new, unchanged, resolved }.
RLS Score
Every report carries a deterministic score: 100 minus severity penalties —
critical 25, high 10, medium 4, low 1, info 0 — clamped to 0–100. Bands:
green ≥ 80, yellow ≥ 50, red < 50. The score never replaces the report:
inspect the findings before calling a database safe.
--score and --badge print only the score or badge; exit codes still follow
--fail-on. JSON reports always include the score object with its
findingPenalty breakdown.
Audit migrations without a database
check --schema-file reconstructs table, policy, RLS, grant, role, and
default-privilege state from a SQL file — no connection, no credentials.
Unsupported statements become coverage limitations instead of guessed
findings, and the RLS Score carries the offline coverage penalty.
Supported: CREATE SCHEMA/TABLE/ROLE/USER/POLICY, ALTER TABLE RLS and
FORCE ROW LEVEL SECURITY, GRANT/REVOKE on tables, schemas, and roles,
ALTER DEFAULT PRIVILEGES, DROP of the same, and unconditional role
creation inside DO blocks.
Shadow verification
shadow replays your migration files in a disposable PostgreSQL container,
runs the real catalog audit — and probe when seed data is provided — then
compares the offline analysis with the database's actual state:
- Prints how many findings matched between static and live analysis, and lists any disagreement in either direction — a parser-coverage fact, never a guessed finding.
- The container is always removed; the audited database is never touched.
--receiptwrites a receipt markedscope=shadowandenvironment: shadow (disposable world).
Formal proof mode
prove decides row-isolation claims from a schema file within a small,
sound fragment — no database needed:
For each table, command, and role it returns one of:
- proved — every row the policies admit satisfies
owner_col = auth.uid()(or the policies admit no rows at all) - witness — the policies can admit rows outside the ownership boundary,
with the reason stated (
using (true), RLS disabled, mismatched columns) - undecided — outside the decidable fragment (NOT, subqueries, casts, function calls); listed with the reason, never guessed
Exit 1 when any witness exists. --proof <path> writes a canonical JSON
artifact with a SHA-256 digest over the claims. See
docs/proofs.md.
Proof-carrying repairs
fix generates a canonical repair for an exposed table and verifies it in
shadow before writing anything:
The patch is written only if verification proves it resolves the target
witnesses and introduces no new medium+ findings — statically always, and
against a live disposable PostgreSQL when Docker is available. The receipt
binds the finding fingerprints to the exact patch hash and the before/after
verdicts. The tool never applies the patch; a human or authorized agent does,
then reruns check and prove. See docs/repairs.md.
Exit behavior
0— no finding meets the configured threshold1— a finding meets or exceeds--fail-on, or Commander usage/option-parsing error2— caught runtime failure: missing credentials, connection/catalog error, invalid action value, requested table not found
--fail-on none always disables finding-based failure. A clean report uses highestSeverity: "none", so --fail-on info still exits 0 when there are no findings.
Coverage receipts
Every audit can emit a portable coverage receipt — what was checked, what was not, the deterministic score, and finding fingerprints — with a SHA-256 digest over the canonical body and an optional Ed25519 signature:
Receipts never contain policy expressions, connection strings, or credentials. See docs/receipts.md.
MCP server
Read-only tools, each annotated readOnlyHint: true, destructiveHint: false,
idempotentHint: true: describe_capabilities (tool contract, approval rules,
coverage limits), check_rls, probe_access, explain_table,
prove_isolation (offline proofs from SQL files), propose_repair
(statically verified repair proposals; never writes files). Failures return
structured JSON shaped as { error: { code, message, nextAction } }. Setup
for Codex CLI, Gemini CLI, and generic stdio clients is in
docs/agents.md.
Exit codes
0: the command completed and no finding met the--fail-onthreshold.1: the command completed and a finding met the threshold.2: invalid input or an operational failure (connection, parse, IO). Never treat exit2as clean.
Commands document extra codes where they apply (for example
verify-receipt exits 2 on a tampered receipt).
Agent skill
Teaches compatible coding agents when to run the audit, how to use read-only database credentials, and how to avoid leaking connection strings.
Supabase
rls-doctor audits catalog-visible PostgreSQL RLS, grants, and role paths. It does not audit hosted Supabase management settings, views, or functions — review Data API configuration separately. See docs/guides/supabase-rls-patterns.md for unsafe vs. safer policy examples.
Try it in seconds
npx rls-doctor demo starts a disposable PostgreSQL container, applies an
intentionally unsafe schema, runs check and probe, proves a cross-tenant
read, and removes the container. No configuration, no credentials, nothing
written — and when Docker is unavailable it falls back to a recorded run.
demo/ also holds disposable fixtures — unsafe-schema.sql (intentionally risky policies) and safe-schema.sql (a safer reference shape).
Never run demo fixtures against production.
Architecture
Deeper notes: docs/architecture.md
Safety and scope
rls-doctor only queries Postgres catalogs. It sanitizes connection credentials from its own errors. Probe transactions are read-only and always rolled back.
It's a focused catalog audit, not a proof or compliance product. It does not:
- prove arbitrary SQL predicate correctness or simulate
can-style checks - audit views, functions, or hosted Supabase management configuration
- automatically execute suggested SQL — suggestions are review templates
- make a compliance claim
It's a security review aid, not a replacement for application-level authorization tests, grant reviews, or a full production security audit.
Dogfooding
CI checks the safe reference schema with RLS Doctor itself, offline:
The safe fixture must stay clean at high severity. The unsafe fixture is
expected to fail — it powers demo and the test suite.
Development
npm run test:integration starts its own disposable Postgres container, opts into destructive fixtures, runs check/explain, and removes the container. Invoking scripts/run-integration.js directly refuses to run unless RLS_DOCTOR_ALLOW_DESTRUCTIVE_TESTS=1 is set — use that guard only with a disposable database.
Roadmap
- Markdown report output
- Policy diffing between branches
- Optional SQL migration suggestions
License
MIT
来源:README.md,提交 c942d25
工具
0版本历史
1- v0.5.8最新Oct 11, 2026

