A Supabase publishable key is safe in the browser only because data access is enforced elsewhere. Review Postgres grants and Row Level Security for every object exposed through the Data API, then test each intended role and operation with authorized accounts. Do not treat “RLS enabled” as proof that the policy matches the application’s tenant and ownership rules.
This checklist is for project owners and authorized reviewers. It is not a recipe for testing someone else’s Supabase project with a key found online.
The two layers to review
Supabase’s Data API security guide describes two separate controls:
- Postgres grants decide whether
anon,authenticated, orservice_rolecan reach a table, view, sequence, or function. - RLS policies decide which rows an allowed role can read or modify.
Use both. RLS does not make an unnecessary grant desirable, and a narrow grant does not replace row-level rules.
1. Inventory the exposed surface
Start from the schemas configured as exposed in the project’s API settings. For each schema, inventory:
- tables and partitioned tables;
- views and materialized views;
- functions reachable by Data API roles;
- sequences used by insert paths;
- Storage policies if the application uses Supabase Storage;
- Realtime paths and server functions that depend on the same data.
If the application does not use the Data API, Supabase documents an option to disable it. If it does use the Data API, a dedicated exposed schema can make the intended API surface easier to audit than placing internal and public objects together.
An owner can use a read-only catalog query to list table RLS state:
select
n.nspname as schema_name,
c.relname as table_name,
c.relrowsecurity as rls_enabled,
c.relforcerowsecurity as rls_forced
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname in ('public')
and c.relkind in ('r', 'p')
order by 1, 2;
Change the schema list to match the project’s exposed schemas. Save the output as review evidence; do not assume public is the only exposed schema.
2. Review grants before policies
List the privileges assigned to the client roles:
select
table_schema,
table_name,
grantee,
privilege_type
from information_schema.role_table_grants
where table_schema in ('public')
and grantee in ('anon', 'authenticated', 'service_role')
order by 1, 2, 3, 4;
For each object, ask:
- Does
anonneed any access? - Does
authenticatedneed every granted operation? - Can the client call functions that were intended only for backend jobs?
- Are new tables and functions receiving automatic default privileges?
- Can internal tables move to an unexposed schema?
Supabase’s current guidance is to grant the minimum privilege each role needs and pair grants with RLS in the same migration.
3. Confirm RLS is enabled everywhere it must be
Supabase says RLS must be enabled on tables in an exposed schema. Tables created through the dashboard have RLS enabled by default; tables created through raw SQL or another tool may require an explicit statement:
alter table public.projects enable row level security;
Once RLS is enabled, the publishable key alone cannot access table data until an applicable policy permits it. That fail-closed starting point is useful, but it still needs policies that represent the real application rules.
Record a Confirmed finding when deterministic source or catalog evidence shows an exposed table with applicable client grants but without RLS or an equivalent control. If the live URL only reveals that the app uses Supabase, RLS remains Not tested.
4. Review policies by operation and role
List current policies:
select
schemaname,
tablename,
policyname,
roles,
cmd,
qual as using_expression,
with_check
from pg_policies
where schemaname in ('public')
order by 1, 2, 4, 3;
Build a matrix for every table:
| Role | SELECT | INSERT | UPDATE | DELETE |
|---|---|---|---|---|
anon | Intended rows or denied | Intended rows or denied | Intended rows or denied | Intended rows or denied |
authenticated | Tenant/owner rule | New-row rule | Existing- and new-row rule | Existing-row rule |
Check the policy mechanics:
SELECTusesUSINGto decide which existing rows are visible.INSERTusesWITH CHECKto validate a proposed row.UPDATEcommonly needs bothUSINGfor the existing row andWITH CHECKfor the resulting row. Supabase also notes that an update requires a corresponding select policy.DELETEusesUSINGto decide which existing rows may be removed.- The
TOclause should name the intended Postgres roles rather than evaluating the policy for roles that never need it.
A broad policy such as USING (true) can be correct for deliberately public data. The evidence should include why that table and operation are public, not merely the SQL text.
5. Trace tenant and ownership fields end to end
For a multi-tenant application, a policy is only as reliable as its tenant context.
Check:
- where
tenant_id,organization_id, or the ownership field comes from; - whether the user can set or change that field;
- whether inserts derive tenant context server-side or validate it with
WITH CHECK; - whether updates can move a row into another tenant;
- whether membership removal takes effect with the intended timing;
- whether invitations, role changes, exports, and admin actions use the same boundary;
- whether joins or helper functions accidentally broaden access.
Do not review policies in isolation from the application workflow. A syntactically correct condition can enforce the wrong business rule.
6. Use trusted authorization claims
Supabase warns that raw_user_meta_data is user-editable and should not hold authorization data. raw_app_meta_data is not directly editable by the user and is the more appropriate of the two for authorization context.
Even trusted app metadata can be stale until the user’s JWT refreshes. If removing a user from an organization must take effect immediately, design and test that revocation behavior rather than assuming every existing token contains fresh membership.
Also distinguish:
- the
anonPostgres role, used for unauthenticated requests; and - an anonymous Supabase Auth user, who has a user identity and uses the
authenticatedPostgres role.
Policies that confuse those two cases can grant or deny the wrong behavior.
7. Review views and functions separately
RLS attaches to tables, not every callable database object.
- Supabase notes that views are created with security-definer behavior by default and may bypass underlying RLS. On Postgres 15 and later,
security_invoker = truecan make a view obey the caller’s RLS policies. Otherwise, revoke client access or keep the view in an unexposed schema. - RLS does not apply to functions. Grant
EXECUTEonly to roles that need it. - Review every
SECURITY DEFINERfunction for its owner, fixedsearch_path, input validation, tenant checks, and exposed-schema placement. - Keep privileged helper functions out of exposed schemas unless direct client invocation is deliberate and controlled.
Treat a function that safely performs one narrow server-side action differently from a general-purpose RLS bypass.
8. Test an authorization matrix
Source review should be followed by bounded tests against a project and domain the reviewer is authorized to assess.
For a two-tenant app, a useful minimum includes:
- unauthenticated request;
- user A in tenant A;
- a second user in tenant A with a different role;
- user B in tenant B;
- an invited or suspended user where relevant;
- an approved admin role.
For each high-risk object and workflow, test:
- allowed same-tenant read, create, update, and delete behavior;
- denied cross-tenant access using identifiers the test owner created;
- attempts to change ownership, tenant, price, role, approval, or status fields;
- membership removal and token-refresh behavior;
- negative cases for missing, expired, and lower-privilege sessions.
Use the user’s normal access token and publishable key for user-scoped tests. Do not initialize the test client with a secret or service_role key: those credentials bypass RLS and can make a broken test appear to pass.
Keep test data synthetic, requests bounded, and evidence redacted. “Returned no rows” and “request was denied” are different behaviors; record the actual status, response, role, and policy path.
9. Retest the fix
A complete finding should include:
- result state, severity, and confidence as separate fields;
- the affected object and operation;
- the role and tenant context;
- the policy or grant evidence;
- the authorized request and response evidence when tested;
- the business impact;
- the smallest practical remediation;
- retest status and evidence.
Do not mark an untested policy as passed. If a fix changed only SELECT, keep INSERT, UPDATE, and DELETE in Not tested until their behavior is checked.
Common failure patterns
- A table created in a SQL migration never had RLS enabled.
SELECTis tenant-scoped, butINSERTaccepts a caller-supplied tenant ID.UPDATEchecks the old row but permits changing its ownership in the new row.- A role claim comes from user-editable metadata.
- A view or security-definer function bypasses the policy that appears to protect its base table.
- A test uses the service key and therefore never exercises RLS.
- The UI hides an admin action, but the server or policy accepts the same operation from a normal user.
- A removed member retains access until a stale JWT expires, contrary to the product’s expected revocation behavior.
What the free scan can tell you
A public-surface scan may identify Supabase configuration and correctly classify a publishable key as public. It cannot query a table, inspect private migrations, enumerate policies, or test roles. A visible publishable key makes the RLS question more relevant; it does not answer it.
What requires a Human security review
An owner-run checklist works well for simple ownership rules. A Human security review becomes valuable when the policies encode tenant membership, invitations, delegated administration, approvals, billing entitlements, recovery, exports, or other workflows where the intended rule lives across database and application code.
The sample Human security review shows how cross-tenant evidence, impact, remediation, and retest status are separated.
Limitations
This checklist focuses on the Supabase Data API and Postgres RLS. Authentication configuration, Storage, Realtime, Edge Functions, network restrictions, backups, and application-server authorization may require separate review. The example catalog queries are read-only starting points and must be adapted to the project’s actual exposed schemas.
Revision history
- 2026-07-27: Initial draft based on Supabase’s current RLS and Data API security documentation.