You want to know whether strangers can read your app's data. You can find out yourself tonight, in about ten minutes, without paying anyone. Here is the method, in the order to run it.
1. Find the public key and the project URL
They are in your frontend bundle on purpose, so this is easy. Open the developer tools, the Network tab, reload the page, and look for the request that goes to *.supabase.co. Or grep the build output:
grep -ro "https://[a-z0-9]*\.supabase\.co" dist/ | head
The key next to it is the "anon" key, sometimes labelled VITE_SUPABASE_ANON_KEY or NEXT_PUBLIC_SUPABASE_ANON_KEY. It is public by design. It is not the thing that protects your data, and the rule that does protect your data lives in Postgres, not in the key. If you cannot find either one, look in the environment files of the project, or in the settings of the tool you built the app with.
2. Send one read request
This is the single most informative test you can run. Use a table name that really exists in your project.
curl -s -i "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=1" \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"
Three possible answers.
- 200 with a row of data. Anybody on the internet can read this table. This is the one that matters.
- 200 with an empty array. The read was allowed, but the table has no rows. Nothing has leaked yet. The door is open.
- 401 or 403. This table is protected. That rule is working.
Send it once, then stop. Do not loop it across tables or pages. That is how a check turns into an incident, and also how you end up holding a copy of your customers' data. The live check in the scanner behaves the same way on purpose: one read request, one table name that already appeared in your own SQL files, no sign-in attempt, nothing written.
3. Check your migrations for the rule that should have stopped it
grep -rn "enable row level security" supabase/migrations/
grep -rn "create policy" supabase/migrations/
Every table that holds user data should appear in the first list. Every table in the first list should have at least one policy in the second list. If a table has RLS on and no policy, it has locked itself, including your own signed-in users, and it usually shows up as "the app stopped working". If a table has neither, it is the case from step two. The full fix, with the policy for each operation, is in the RLS guide linked below.
4. Look for the other four leaks
The committed environment file.
git ls-files | grep -i '^\.env'
If it prints a file, the secrets in it are in your git history and in every clone of the repository. 18.5% of 400 public repositories built with Lovable, Bolt and v0 had a committed .env (VibeSafe). Rotating the keys is the part that actually protects you. Removing the file does not.
The admin key in the browser bundle.
grep -rl "service_role" dist/ build/ .next/ out/ 2>/dev/null
The service_role key bypasses every row-level rule. If it is in the JavaScript that every visitor downloads, then every visitor has it. Moving it server-side is the fix, and so is rotating it afterwards.
The debug route.
router.get('/api/debug', (req, res) => {
res.json({ ok: true, env: process.env });
});
And express.static('.') serves the whole project folder, which means a request for /.env returns your secrets.
The wildcard cross-origin rule.
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
That combination tells every browser on the internet that any website may call your API using the visitor's session, and read the response.
5. Write down what you found and what you did not check
This is the part most people skip, and it is the part that matters. A check you did not run is not a pass. "I looked at two tables out of nine" is a fact about your coverage, and it is worth writing down next to the results.
If you do this for one app, the habit is worth more than the answer, because the same five checks catch the same classes of problem in the next project too.
If you would rather have a person read the results than a list, that is a different product, and it is worth comparing the two before you decide.
What to do next
Run the free scanner. It reads your project folder in your browser and covers steps 3 and 4 in one pass, plus a check on token verification. Nothing is uploaded, there is no server, no account and no email address. You get a ship-readiness score out of 100, a paste-ready fix for every finding, and a list of the checks that passed and the ones that were skipped and why. It does not make the live request from step two, because that needs a live URL and your explicit permission, so run that one yourself. It is not proof that your app is secure. It is a record of one pass over your files, and it is worth running again after every build.
The scanner that finds this is open source: github.com/rafikulislammollaq-dev/vibeaudit
It also runs in the browser, where your project is read on your own machine and never uploaded: rafikulislammollaq-dev.github.io/vibeaudit/scanner
If you would rather have a person read the findings and explain them, that is a $199 review: rafikulislammollaq-dev.github.io/vibeaudit
Top comments (0)