Catch cross-tenant Row-Level Security leaks in Postgres and Supabase before your users do.
Point it at your database. rlsgrid reads the live schema and:
- Maps every
role × table × operationand labels itallow/deny/conditional/unrestricted. - Fuzzes for real cross-tenant leaks — it seeds synthetic tenants and actively tries to read, insert, update, and delete one tenant's rows from another tenant's session.
- Emits a pgTAP suite you can run in CI.
Postgres RLS is powerful and easy to get subtly wrong: a missing
WITH CHECK, a FOR ALL where you meant FOR SELECT, a forgotten
ENABLE ROW LEVEL SECURITY, a service_role bypass leaking client-side.
Your application unit tests will not catch any of these — they test your code,
not the policies. rlsgrid tests the policies, against a real database.
sudo apt install python3-venv -y
python3 -m venv ~/venvs/rlsgrid-env
source ~/venvs/rlsgrid-env/bin/activate
pip install rlsgrid
# if ipv6
export DATABASE_URL=postgresql://user:pw@host/db # use staging, never prod
# if ipv4
getent ahosts db-host | grep STREAM | grep 4
export DATABASE_URL=postgresql://user:pw@<response>/db
rlsgrid init --from-db # read the schema, write an annotated config
rlsgrid check --tenants 5 # seed → fuzz → teardown. exit 1 on any leak.check is the whole loop: it leaves nothing behind and returns non-zero on a
breach, so it drops straight into CI. A leak looks like:
✗ 1 cross-tenant breach detected
LEAK role=authenticated actor_tenant=a1b2 → target_tenant=c3d4
on public.documents UPDATE: target-owned row visible across tenants
- uses: matte97p/rlsgrid@v1
with:
command: check
database-url: ${{ secrets.STAGING_DB_URL }}rlsgrid introspect # tables, RLS state, policies
rlsgrid plan --explain # the full matrix, with a "why" column
rlsgrid gen pgtap --out tests/rls/generated.sql # emit a pgTAP suite
rlsgrid fuzz --tenants 5 # fuzz only (auto-cleans up)
rlsgrid seed --dry-run # show the seed plan without writing
rlsgrid check --sarif-out rls.sarif # SARIF for GitHub code scanningInstalling rlsgrid registers a rlsgrid fixture, so you can gate your
existing suite:
def test_no_cross_tenant_leaks(rlsgrid):
report = rlsgrid.check()
assert report.ok, [b.detail for b in report.breaches]Point it with --rlsgrid-config path/to/rlsgrid.toml.
Config for your stack — Supabase, Prisma, Drizzle, SQLAlchemy, Rails, function-based access checks — is in docs/RECIPES.md.
- allow — a permissive policy applies and gates nothing.
- deny — RLS is on and no policy matches the role/op.
- conditional — a policy applies but a
USING/WITH CHECKexpression gates which rows. This is where the fuzz earns its keep. - unrestricted — RLS is off, or the role has
BYPASSRLS. Surfaced explicitly so you notice when you did not mean it.
- RLS at the database (the Supabase default): the fuzz finds leaks
directly. Set
tenancy.mode = "jwt". - Access enforced by a SQL function (e.g.
check_user_has_access_to_store(user_id, store_id)): settenancy.mode = "function"and rlsgrid calls the helper with cross-tenant arguments, asserting it returns false.
| hand-written pgTAP | static linters | rlsgrid | |
|---|---|---|---|
| New table lands without a test | silent | maybe | shows up in plan |
| Cross-tenant write leaks | only if you wrote that test | no | probed automatically |
| Function-based access | no | no | first-class |
| Setup | per-test | low | one config |
It composes with supabase-test-helpers:
keep your bespoke business-rule pgTAP, let rlsgrid watch the floor.
seed, fuzz, and check write to the database, so they refuse any URL
matching [safety].forbid_url_patterns (default ["prod", "production"]).
Point DATABASE_URL at staging or a disposable database.
Alpha, but exercised end to end in CI against a rich multi-tenant schema and
run through pg_prove. The config shape may still shift before 1.0. Issues
and PRs welcome — see CONTRIBUTING.
Built by Matteo Perino while shipping GeoSuite, a multi-tenant Supabase app.
MIT — see LICENSE.
Part of my open-source toolkit — github.com/matte97p:
- pentest-framework — low-noise pentest orchestration, normalized to one schema and rendered to a PDF
- demowright — record polished product-demo videos from a script that lives in your repo
- GeoSuite CLIs — zero-dep Generative Engine Optimization toolkit
⭐ If rlsgrid caught a leak (or proved you safe), give it a star — it helps other Supabase teams find it.