Supabase security check
Your anon key is public by design. Whether your data is safe depends on Row Level Security, and on nothing privileged leaking into the browser. HatTest tests both from the outside, the way a stranger would.
What goes wrong
Every Supabase app ships its project URL and anon key to the browser. That is how Supabase is meant to work, but it means anyone can send the same requests your app sends. What stands between them and your data is Row Level Security (RLS).
- RLS switched off on a table. With RLS off, the anon role can typically read every row.
- A policy that lets everyone in. A policy written as
using (true)turns RLS on and still lets anyone read every row. It looks protected in the dashboard; it is not. - The service_role key in your frontend. That key bypasses RLS entirely. If it ends up in your JavaScript bundle, your whole database is open.
- Storage buckets that list their files. A bucket that answers an anonymous list request shows everything inside it.
A presence check ("is a key exposed?") catches the third one. The other three need someone to actually ask your API for the data, which is what HatTest does.
What HatTest checks
The free scan asks as the anonymous public: what can a stranger read or reach? The deep scan signs in as two test accounts you create in your own app and checks whether one user can reach the other’s data. These are the checks that matter most here; the full catalog runs 156.
- high Anonymous read exposes sensitive data
- info Table is anon-readable but holds no sensitive columns
- high Anonymous writes accepted (RLS does not block unauthenticated writes) Off by default
- medium Anonymous writes accepted on an intake table (confirm intended) Off by default
- high Supabase service_role key exposed
- high Supabase secret key exposed (sb_secret_)
- high Supabase Storage bucket is world-listable and holds private-looking files
- medium Supabase Storage bucket list is anonymously readable
- info Supabase RPC (stored-procedure) surface referenced by the app
- high Authenticated user can read another user's sensitive rows Deep scan
- high Authenticated user can read another user's object by ID (BOLA / IDOR) Deep scan Supabase only
- medium Authenticated user can write another user's row (writes not owner-scoped — confirm intended) Deep scan Off by default
What’s free, and what it never does
Running a scan is free (up to 5 a day), and so are the severity scoreboard, the informational findings and your site profile. The negative findings, each with its evidence and a plain-English fix, are a $50 unlock per report.
- It never runs your code. It looks at what your live app already serves and answers.
- It only scans a site you have verified you own. The no-signup check above is the exception, and it only reads what any browser sees.
- It does not write to your data. Every check that would write is switched off on this deployment, marked above.
- It never tells you a site is “secure.” It reports what it found, and names anything it could not check.
Questions
Is it a problem that my Supabase anon key is visible in my site's code?
No. The anon key is meant to be public, and HatTest does not flag it. It only matters what that key is allowed to do, which is decided by your Row Level Security policies. That is what the scan tests.
How do you know a table is exposed without guessing?
The scan reads your table the way an anonymous visitor could, using your app's own public key, and reports a table only when rows actually come back. Sensitive-looking columns raise the severity; a public table with nothing sensitive is reported as informational, not as a problem.
Can it test whether one logged-in user can read another user's rows?
Yes, in the deep scan. You create two test accounts in your own app; HatTest signs in as each and checks whether one can reach the other's rows. The free scan tests the anonymous public.