Securealy
Check · Security · high · passive

Supabase REST metadata not readable with the public key

If the page ships a Supabase URL and anon key, the REST root does not list tables to anonymous callers.

Severity
high
Tier
Passive: runs on any URL
Category
Security
Fix
Summary below; full runbook in the Fix Kit, Module 03: Lock it down
Id
supabase-rest-exposed

What this means

Passing means that when your page ships a Supabase URL and anon key, the REST root at that URL does not describe your tables to an anonymous caller. Failing means anyone who reads your page source can ask the database API for a list of your tables and columns with the key you gave them. That list is the map an attacker reads before the first row is pulled.

How we test it

We look for a Supabase project URL and anon key in your home page and its bundles. If both are present we make one request to the REST root with that key, reading metadata only; we do not read rows and we store nothing. Pass is a response with no table metadata, or no Supabase credentials in the page at all. Fail is a response that lists tables. The verified check for anonymous row reads goes on to test whether those tables return rows.

Why it matters

Table names are the map; the verified check reads the rows. The anon key is public by design: it ships in every page's source and talks to the database API directly, never touching your endpoints, so endpoint gating cannot help on this path. In the Fix Kit's own audit, four tables created without row level security let that public key read 493 rows and 17 business addresses straight through the database API. A table list with the public key tells a stranger exactly where to point the next request.

The fix

Run the Module 3 RLS baseline so row level security is on for every table in the public schema and the anon and authenticated roles lose their direct grants, which leaves the public key nothing it may read. Where a page must read a table from the browser on purpose, add the narrowest select policy from rls-baseline.sql and record the table in public-tables.txt. The full runbook is in the Fix Kit, Module 03.

Prompt for your AI

Paste this into the coding agent that built the app. It reproduces the finding and stops before changing anything; the fix steps are in the kit.

Read the built front-end bundle and find the Supabase project URL and the anon key it ships. Print the URL and the first eight characters of the key only.
Decode the anon key's JWT payload and print its role claim; expected: anon. If it says service_role, stop and tell me immediately.
Load SUPABASE_ANON_KEY from .env.local and make one GET request to https://YOUR_PROJECT_REF.supabase.co/rest/v1/ with the apikey header and Authorization: Bearer set to it. Do not request any table.
Report whether the response describes tables, and list the table names it mentions.
Do not change any policy, grant or setting; list what it would take to close this and stop.
Show me the first 400 characters of that response body; expected for a passing project: no table names.

Next

Grade your own URL

Every grade lists this check with its evidence for your site. Two free grades a day.