Catch the SQL bugs your agent wrote.

SqlProof generates hundreds of valid test datasets for your Postgres or Supabase app, runs your queries as different users, and finds the one that breaks it.

Leaky RLS policies Wrong query results Broken RPCs & triggers Migrations that change results
Install
$ pip install sqlproof
Or ask your coding agent
> test and fix my RLS and queries with sqlproof.com
Claude Code plugins
> /plugin install alialavia/pbt-skills > /plugin install alialavia/sqlproof-skills
Cursor and other agents: add AGENTS.md to your repo, or point them at /llms.txt.
pytest output (trimmed)
$ pytest tests/test_rls_projects.py

AssertionError: user db8a6d46-… can read another
user's row via project_summaries:
  {'id': '00000000-…-000000000001',
   'user_id': 'b51c2c58-…'}

Falsifying example: test_users_only_see_their_own_rows(…)
Draw 1: {'projects': [{'id': '00000000-…-000000000001',
          'user_id': 'b51c2c58-…', 'name': ''}]}

FAILED test_users_only_see_their_own_rows
1 failed in 5.07s

# user db8a6d46 reads a row owned by b51c2c58 through
# the project_summaries view. The projects table was fine.
# Shrunk to the smallest case: 1 project.

Catches all 10 bugs planted in a multi-tenant Supabase app: RLS leaks, RPCs, triggers and queries. See the example →


// the problem

Your agent wrote the SQL.
Did anyone check who can see what?

Coding agents write RLS policies, views, RPCs and migrations fast, and nobody reads them closely. The bugs that matter, like one user reading another user's rows or a report that quietly drops a row, don't show up in happy-path tests with one hand-made user. SqlProof generates hundreds of valid datasets, runs your queries as each test user, and finds the one that breaks your rules.


// using with supabase

Prove your RLS policies don't leak

as_rls_user sets the JWT claims so auth.uid() resolves to the user, and switches to the authenticated role so policies actually apply. This test checks that every row a user can read, through a table and a view, is theirs.

tests/test_rls_projects.py
from hypothesis import given, strategies as st
from sqlproof.contrib.supabase import as_rls_user

@given(data=st.data())
def test_users_only_see_their_own_rows(supabase_proof, data):
    dataset = data.draw(supabase_proof.dataset_strategy(
        sizes={"projects": st.integers(min_value=1, max_value=6)},
    ))
    with supabase_proof.client_for_dataset(dataset) as db:
        users = db.query("SELECT id::text FROM auth.users")
        for user in users:
            for source in ("projects", "project_summaries"):  # a table and a view
                with as_rls_user(db, user["id"]):
                    rows = db.query(f"SELECT id::text, user_id::text FROM {source}")
                leaked = [r for r in rows if r["user_id"] != user["id"]]
                assert not leaked, (
                    f"user {user['id']} can read another user's row via {source}: {leaked[0]}"
                )
pytest output, view created without security_invoker (trimmed)
E   AssertionError: user db8a6d46-6d97-42c0-8251-c65688ff061d can read another user's row via project_summaries: {'id': '00000000-0000-0000-0000-000000000001', 'user_id': 'b51c2c58-9898-4d69-a148-82525e2556dd'}
E   Falsifying example: test_users_only_see_their_own_rows(
E       supabase_proof=<sqlproof.core.SqlProof object at 0x7f6961573490>,
E       data=data(...),
E   )
E   Draw 1: {'projects': [{'id': '00000000-0000-0000-0000-000000000001',
E      'user_id': 'b51c2c58-9898-4d69-a148-82525e2556dd',
E      'name': ''}]}

FAILED tests/test_rls_projects.py::test_users_only_see_their_own_rows - Asser...
1 failed in 5.07s

# After ALTER VIEW project_summaries SET (security_invoker = true): 1 passed

supabase_proof ships with the pytest plugin and seeds a test pool of auth.users. Views must be created WITH (security_invoker = true) or they bypass the table's RLS. More in Testing Supabase apps.

Property-based testing for your database

Instead of one fixture and one expected answer, you state a rule that must always hold. SqlProof generates valid datasets with Hypothesis and tries to break it.

Schema-aware generation

Respects foreign keys, CHECK constraints, UNIQUE, NOT NULL, and enum types automatically. No invalid data, no constraint violations.

Minimal counterexamples

When a rule breaks, Hypothesis shrinks the dataset to the smallest one that still fails, like the single project above, so you fix the bug instead of hunting for it.

No test infrastructure to write

Point it at your local Supabase with one environment variable, or let testcontainers start a throwaway Postgres. Each test runs in a savepoint that rolls back.

Works with your test runner

Drop it into pytest. Decorate a test with @sqlproof(...) or call proof.check() directly. No new runner to learn.

1

Parse schema

Reads your SQL file or introspects a live Postgres DB to extract tables, types, FKs, and constraints.

2

Generate data

Creates random valid rows for every table in FK-dependency order. Respects all constraints.

3

Insert & run

Inserts the rows inside a savepoint, runs your test, then rolls back. Repeats for each generated dataset.

4

Report

On failure, shrinks to the smallest dataset that still fails and shows it to you.

// plain postgres

Not on Supabase? Point it at a schema file

test_orders.py
from sqlproof import SqlProof, sqlproof

proof = SqlProof.from_schema_file('./schema.sql')

@sqlproof(proof, sizes={ 'customers': 10, 'orders': 50 }, runs=50)
def test_order_totals_non_negative(db):
    rows = db.query('SELECT total FROM orders')
    assert all(row['total'] >= 0 for row in rows)

# On failure, SqlProof saves the shrunk counterexample under .sqlproof/failures/.
# Replay it with: sqlproof replay .sqlproof/failures/<test>.json

// get started

Find the bug before your users do.

Install
$ pip install sqlproof
Or ask your coding agent
> test and fix my RLS and queries with sqlproof.com
Read the docs → View on GitHub