Free Supabase RLS policy generator (copy SQL)
Pick your table, who should reach its rows and which operations. You get the SQL to turn on row level security and add the policies, written the way Supabase's docs recommend.
alter table public.todos enable row level security; create policy "Users can read their own rows" on public.todos for select to authenticated using ( (select auth.uid()) = user_id ); create policy "Users can insert their own rows" on public.todos for insert to authenticated with check ( (select auth.uid()) = user_id ); create policy "Users can update their own rows" on public.todos for update to authenticated using ( (select auth.uid()) = user_id ) with check ( (select auth.uid()) = user_id ); create policy "Users can delete their own rows" on public.todos for delete to authenticated using ( (select auth.uid()) = user_id );
- Index the owner column for speed: create index on public.todos (user_id);
- Set user_id automatically with a default: alter table public.todos alter column user_id set default auth.uid();
Runs in your browser; nothing is sent. Read the SQL before you run it, test it signed out and as a second user, then paste your final policies into the policy explainer for a second look.
What this checks
Nothing on its own: it's a helper that writes SQL. Every policy names its role with to, uses (select auth.uid()), puts with check on inserts and updates, and reads roles from app_metadata. See 16 RLS policy examples for more patterns.
What it can't tell you
- Whether the pattern fits your data. Only you know which tables should be public.
- What's already in your database. Existing policies add up: one permissive
using (true)still opens the table. - Whether it's applied. Test signed out and as a second user, and run the scan below.
Then check the live site
After you run the SQL, the free scan checks which tables a signed-out visitor can still read with your public key.
This is covered by the main scan. There's no separate scanner: one free scan runs every check below on your live URL.
Run the main scan →What the main scan checks
Paste your site's URL and SniffPup checks it the way a visitor's browser would: 15 passive checks. It never logs in, never sends attack traffic and never keeps your data. You get a report sorted by severity, with fixes.
- Leaked API keys in your page and JavaScript: OpenAI, Anthropic, Stripe, AWS, GitHub, Slack, Resend and more, plus public env variables (VITE_, NEXT_PUBLIC_ and similar) with names that sound secret
- Supabase key leaks: a secret or service-role key shipped to the browser
- Exposed .env files and other exposed files: .git, .npmrc, cloud credentials, SQL dumps, backups
- Source map exposure: is your original source code downloadable?
- Security headers (HSTS, CSP, clickjacking), cookie flags and the HTTPS redirect
- Your HTTPS certificate: expiry, issuer, name match and trust
- Supabase and Firebase exposed data: what a stranger can read with your public key (read-only, no row data downloaded)
- CORS: can any website read your pages as your users?
- Email spoofing protection: SPF, DMARC and DKIM
- Outdated JavaScript libraries with known security holes
- AI crawler access: what ChatGPT, Claude and Perplexity's crawlers can see, plus robots.txt and llms.txt
- Speed Doctor: a speed score and what slows your site down
- Crash Catcher: scripts that fail to load, blank pages, missing styles and dead links
- Accessibility basics, domain expiry, outside scripts and privacy and terms links
Open tables are listed as “Anyone can read your Supabase table …”. Free, passive and read-only, no signup. Only scan sites you own or are authorized to test. What the scanner does and never does.
Questions
Is anything sent to SniffPup?
No. The SQL is built in your browser from the choices you make. Nothing is uploaded or stored, and the tool never connects to your database.
Why (select auth.uid()) instead of auth.uid()?
Supabase's docs recommend wrapping auth.uid() and auth.jwt() in a select so Postgres runs it once per query instead of once per row. The result is the same; big tables get faster.
Why does the admin pattern read app_metadata?
Users can edit their own user_metadata, so a role stored there can be changed by anyone. app_metadata can only be set from your server.
Does this replace the scan?
No. It writes policies; it doesn't check what your live site exposes. Run the free scan after you apply them to see what a stranger can still read.
Already have policies? Explain them, or read the Supabase security audit guide.