Anyone with the public key can read personal data
supabase/migrations/20260101000000_init.sql:12
CREATE POLICY "Profiles are viewable by everyone" ON public.profiles FOR SELECT USING (true)
Personal columns: email, phone
Free tool · no sign-up
Paste a public GitHub repo. We read the SQL in its supabase/migrations folder and flag the rules that let the wrong people read or change your data. No account, no email.
Example result · real output for our demo app
Result
supabase/migrations/20260101000000_init.sql:12
CREATE POLICY "Profiles are viewable by everyone" ON public.profiles FOR SELECT USING (true)
Personal columns: email, phone
supabase/migrations/20260101000000_init.sql:13
CREATE POLICY "Users can update own profile" ON public.profiles FOR UPDATE USING (auth.uid() = user_id)
Without WITH CHECK the USING rule decides what a row may be changed into.
| Table | Row level security | Policies | Personal columns | Defined at |
|---|---|---|---|---|
profiles | on | 3 | email phone | …_init.sql:3 |
bookings | on | 2 | none found | …_init.sql:16 |
Static analysis only; the full check proves it in the lab. This reads the SQL as text. It can’t tell a table you meant to be public from a leak, and it can’t see what actually happens when a visitor queries the database. In the full check of this same app, the lab confirmed PL-001: an anonymous visitor read both test users’ rows. PL-004 is harmless here: without WITH CHECK, Postgres applies the USING rule to the changed row too, so a user still can’t hand their profile to someone else. See the lab proof.
What it looks for
Each finding links to a page that explains it in plain English, shows the fix and says when the pattern is intended. Only tables in the public schema are analysed.
| ID | What we flag | Severity |
|---|---|---|
| PL-001 | A read policy open to anonymous visitors, not limited to the signed-in user, on a table with email, phone, address, date-of-birth or ID-number columns. | critical |
| PL-002 | Any signed-in user can read every row, so signing up unlocks other people’s data. | high medium |
| PL-003 | Row level security is on but the table has no policies, so nobody can use it through the API (the app may be broken). | low |
| PL-004 | An UPDATE policy without WITH CHECK. | medium |
| PL-005 | A policy that is always true, such as USING (true). | high to low |
| PL-006 | A SECURITY DEFINER function with no fixed search_path, or callable by anonymous visitors. | high medium |
| PL-007 | A storage bucket created or updated with public = true. | medium |
| PL-008 | A table where row level security is never enabled: anyone with the public key can read and change every row. | critical high |
Full taxonomy, with the lab test for each pattern: all findings.
Static vs lab
This tool reads your SQL. The full check rebuilds your database and tries to read it as three different people.
Free RLS check
Full lab check
supabase/migrations in a disposable Postgres with Supabase’s roles, auth functions and grantsFor engineers
The same check is a public endpoint. It returns JSON with every table, finding, file and line, so you can fail a build on a critical finding. It’s also a tool in our MCP server, so your coding agent can call it.
supabase/migrations/*.sql on the default branch, pinned to its latest commit, in filename ordercurl -s https://api.patchlabs.dev/tools/rls-check \
-H 'Content-Type: application/json' \
-d '{"repo": "manra399/patchlabs-demo-app"}'
{
"repo": "manra399/patchlabs-demo-app",
"branch": "main",
"commit": "9e2482e1fa0ab23ae952445540f8445d0bd67e84",
"files_scanned": 1,
"truncated": false,
"cached": false,
"tables": [
{ "name": "profiles", "rls_enabled": true, "policies": 3,
"personal_columns": ["email", "phone"],
"file": "supabase/migrations/20260101000000_init.sql", "line": 3 }
],
"findings": [
{ "id": "PL-001", "severity": "critical", "table": "profiles",
"title": "Anyone with the public key can read personal data",
"file": "supabase/migrations/20260101000000_init.sql", "line": 12,
"snippet": "CREATE POLICY \"Profiles are viewable by everyone\" …",
"detail": "Personal columns: email, phone" }
],
"limits": "static analysis only; the full check proves it in the lab"
}
Not necessarily. No findings means none of the eight patterns appear in your migration files. Access can still leak through grants, views, functions or settings made outside the repo. The full check rebuilds the database and tests what each identity can actually read, which is the only way to know.
We keep the repo name and a hashed network address to enforce the hourly limit, and we cache the result for 10 minutes so repeat checks are instant. We don’t clone the repo; we fetch the migration files from GitHub and analyse them in memory.
Not yet. Private repos need our GitHub App, which is coming. Until then, both this tool and the full check work on public repos only.
The tool only reads supabase/migrations at the root of the default branch, which is where Lovable, Bolt and the Supabase CLI put them. If yours live elsewhere, tell us and we’ll look at supporting your layout.
No. A product catalogue or a public blog is meant to be readable by anyone, and the tool can’t know your intent. That’s why each finding page explains when the pattern is fine, and why the full check asks you plain-English questions before calling something a leak.
Prove it
The full check rebuilds your database in our lab, tests it as three identities, writes the fix and proves it works. Free for your first app.