Harvey / Supabase security checklistInteractive checklist

The Supabase security checklist.

Most Supabase data leaks come down to a handful of things: RLS that checks "logged in" instead of "is this yours," a service_role key that reaches the client, or an app-layer route that trusts an ID from the request. Work through this list — and note which items a scanner can catch versus which need a live test to confirm.
0 / 14
0%

Scannera static check can flag it ·Live testneeds a running stack to confirm

Row-Level Security

Scanner
A table in an API-exposed schema with RLS off is fully readable with the public anon key. Enable RLS on every such table — a static check can confirm the toggle, but not that the policy behind it is correct.
Live test
The most common leak: USING (auth.role() = 'authenticated') lets any logged-in user read every row. Policies must compare to auth.uid() or the tenant column. Whether it actually isolates tenants can only be confirmed by testing across two real users.
Live test
A read policy without a matching WITH CHECK can let a user write rows they can't read, or reassign ownership. Verify both read and write paths.
Scanner
A blanket-true policy disables isolation entirely. Legitimate only on genuinely public tables.

Keys & roles

Scanner
The service_role key bypasses RLS completely. It must live only in server-side code, never in a client component, a public env var, or a module a client component imports. A static scan can flag reachable imports; confirming it's never bundled needs a build-graph check.
Scanner
The anon key is public by design — that's expected. Your security comes from RLS behind it, not from hiding it. The risk is assuming the key is a secret and skipping RLS.
Scanner
Any env var with a client prefix is shipped to the browser. Service keys, webhook secrets, and third-party API keys must not carry NEXT_PUBLIC_ / VITE_.

Functions & RPC

Live test
A SECURITY DEFINER function runs with elevated rights and bypasses RLS. If it's callable over RPC and doesn't constrain by auth.uid(), any user can invoke it against arbitrary data.
Scanner
Audit which RPC functions the public roles can call. Revoke EXECUTE from anything not meant to be client-callable.

App-layer authorization

Live test
RLS protects the database, but a Server Action using the service role, or an API route trusting an ID from the request, can still leak (IDOR). Every mutation should verify the resource belongs to the caller.
Scanner
Over-fetching (select *) can return columns — internal flags, other users' PII — the UI never shows but the response exposes.
Scanner
Wide-open CORS or an unauthenticated endpoint that returns user data is an exposure regardless of RLS.

Data & storage

Scanner
A bucket set public exposes every object by URL. Confirm each public bucket is meant to be public and private buckets have policies.
Scanner
Map sensitive columns and confirm the policies and grants over them match how sensitive the data is.

Want the "live test" items actually checked?

The items marked "Live test" are exactly what a scanner can't confirm. Harvey's free scan covers the static ones on your source; the Full audit proves the live ones on a copy of your stack.