Secure your Supabase app, without being a developer
Contents
Your Supabase anon key sits in your site's JavaScript, and that's fine: it's meant to be public. What protects your data is your database's rules. Here are the six places where they give way most often, how to see it yourself, and what Rustine checks for you.
At a glance
| Risk | Where Rustine checks it |
|---|---|
| A table without RLS | Full audit · Solo, ProSupabase connected · Solo, ProGitHub code · Solo, Pro |
| A using (true) policy | Full audit · Solo, ProSupabase connected · Solo, ProGitHub code · Solo, Pro |
| The service_role key in the browser | Free scanGitHub code · Solo, Pro |
| Public buckets | Full audit · Solo, ProSupabase connected · Solo, ProGitHub code · Solo, Pro |
| Open RPC functions | GitHub code · Solo, Pro |
| Sign-ups left too open | Full audit · Solo, ProSupabase connected · Solo, Pro |
1. A table without RLS
Without Row Level Security, the anon key is enough to read, change and delete the table's rows through the API. Every visitor has that key.
Check it yourself
In your Supabase dashboard, open Advisors, then Security Advisor. The “RLS Disabled in Public” error lists the tables. Turn RLS on for each one, then write its policies.
What Rustine checks
After proof, the full audit counts the rows the anon key lets anyone read, table by table, without reading a single one. With Supabase connected, Rustine reads each table's RLS state. On GitHub, it spots migrations that create a table without RLS.
Full audit · Solo, ProSupabase connected · Solo, ProGitHub code · Solo, Pro
2. A using (true) policy
RLS is on, the padlock shows, but the policy says “yes” to everyone. The table stays open. It's often what an AI agent writes to “make a query work”.
Check it yourself
The Security Advisor shows it as a warning: “Permissive RLS policy”. Then read each table's policies in the dashboard: using (true) or with check (true) with no other condition opens the table to all.
What Rustine checks
With Supabase connected, Rustine reads every policy and reports those set to using (true) or with check (true). The full audit sees the effect, a table readable with the anon key. On GitHub, it spots them in your migrations.
Full audit · Solo, ProSupabase connected · Solo, ProGitHub code · Solo, Pro
3. The service_role key in the browser
The service_role key, like sb_secret_ secret keys, bypasses RLS. In your site's JavaScript, it hands your whole database to anyone who opens the console.
Check it yourself
In your code, search for SERVICE_ROLE and sb_secret_. They must live on the server only (Edge Functions), never in a variable starting with VITE_ or NEXT_PUBLIC_. If one was published, rotate it in Supabase right away.
What Rustine checks
From the free scan, Rustine looks for a service_role or sb_secret_ key in the JavaScript your site serves. On GitHub, it spots a public variable holding it.
Free scanGitHub code · Solo, Pro
4. Public buckets
A file in a public bucket opens with its address alone, with no access control. Invoices, ID scans or exports have no business there.
Check it yourself
In Storage, look for buckets marked Public. The Security Advisor adds “Public bucket allows listing” when their files can also be listed.
What Rustine checks
After proof, the full audit checks whether the bucket list can be read and flags public buckets with a sensitive name, without opening a file. With Supabase connected, it lists all your public buckets. On GitHub, it spots Storage policies that are too broad.
Full audit · Solo, ProSupabase connected · Solo, ProGitHub code · Solo, Pro
5. Open RPC functions
A function of your database can be called through the API, by default even without signing in. As security definer, it runs with its creator's rights and can return rows RLS hides.
Check it yourself
In your database's list of functions, look for security definer functions. For each one, ask whether a signed-out visitor should be able to call it. If not, revoke execute from the anon role: Supabase's documentation gives the command.
What Rustine checks
On GitHub, Rustine spots security definer functions exposed in your migrations. From the site, it isn't checked yet.
GitHub code · Solo, Pro
Sources: [6]
6. Sign-ups left too open
Sign-up without email confirmation, anonymous sign-ins on, passwords too short: these are settings, not code, and they get forgotten.
Check it yourself
In the authentication settings, check that email confirmation is on, that anonymous sign-ins are off if you don't use them, and that leaked password protection is on.
What Rustine checks
After proof, the full audit flags open sign-ups without email confirmation. With Supabase connected, it adds anonymous sign-ins, minimum password length, captcha and leaked passwords.
Full audit · Solo, ProSupabase connected · Solo, Pro
Sources: [1]
Scan your site, it's free
Paste your address: the quick scan reads what a visitor sees, nothing more. The details show once you've proven the site is yours.
Free toolsSupabase checkerSecurity headers test
Questions
Is the Supabase anon key a secret?
No. Supabase designs it to be public: it only opens what your policies allow. The danger is a table without RLS or a policy that's too broad, not the key.
Isn't Supabase's Security Advisor enough?
It's a very good start: it sees your database from the inside. Rustine adds what your site actually exposes, such as a service_role key in your JavaScript or a live .env file, and what a visitor can read with your anon key.
Does Rustine read my data?
No. The audit counts readable rows without downloading them. The Supabase connection asks for read rights on your configuration, never your service_role key.
Sources
- Supabase, Performance and Security Advisors, read on 15/10/2026
- Supabase, Row Level Security, read on 15/10/2026
- Supabase, Understanding API keys, read on 15/10/2026
- Vite, Env Variables and Modes, read on 15/10/2026
- Supabase, Storage Buckets, read on 15/10/2026
- Supabase, Database Functions, read on 15/10/2026