What I measured — 3 findings from the scan
Observed on the live app. Nothing was logged into, nothing was queried, and no value of anything secret was recorded — the file name and the count, never the contents.
HighMeasured
Source maps are published with your production bundle
Your original, readable source code is downloadable next to the minified build. Comments, internal endpoints, feature flags and the exact logic behind every permission check are laid out for anyone who wants to read them.
Seen in /assets/index-9f3c2a.js.map · 3 matches
First step Turn off source-map output for production builds (or upload them only to your error tracker) and redeploy.
Fixed in Core 2 · What the Browser Sees
Worth verifyingMeasured
No Content-Security-Policy
With no policy, any script that gets injected — through a compromised dependency, a stored comment, a bad redirect — runs with the full trust of your page. A CSP is the one control that limits the damage after something else has already gone wrong.
First step Start with a report-only policy that lists only the script and connection origins your app really uses, watch the reports for a week, then enforce it.
Fixed in Core 2 · What the Browser Sees
NoteMeasured
Supabase anon key found — expected
The anon key is designed to be public, so seeing it here is fine. What it means is that Row Level Security is the only thing standing between a visitor and your tables — every table without a policy is readable by anyone who has this key, which is everyone.
Seen in /assets/index-9f3c2a.js
First step Open the Supabase dashboard, confirm RLS is enabled on every table with real data, and read each policy as if you were a stranger with the anon key.
Fixed in Supabase 1 · RLS That Actually Restricts
What the owner told me — 9 findings from the check
Two questions aren't asked once you've scanned: whether anyone has looked at what the browser sees, and which Supabase key the frontend uses. The scan settled both, so they don't appear here. How that works →
CriticalSelf-checked
No real access control on user data
Logging in and being allowed are the same thing in this app. Anybody with an account is one guessed identifier away from somebody else’s records.
First step Before anything else this weekend, add an ownership check to the one endpoint or table holding your users' most personal data; the rest of the list can wait a week.
Fixed in Core 3 · Authn vs Authz
HighSelf-checked
No recovery path if the database is lost
There is no rehearsed way back if the database goes. Backups you have never restored are a belief, not a plan.
First step Turn on automated backups today, and take one manual snapshot before you touch anything else this weekend.
Fixed in Core 6 · Backups & Recovery
Worth verifyingSelf-checked
RLS is on, but the policies are unread AI output
Row-level security is switched on and the policies were written by an assistant that nobody reviewed. Switched on and actually restricting are different states.
First step Open the Supabase dashboard and read each policy on your main table out loud — every one should name a user, an action, and whose rows it applies to.
Fixed in Supabase 1 · RLS That Actually Restricts
Worth verifyingSelf-checked
You can't say which secrets reach the browser
Some of what this app calls configuration reaches the browser and some does not, and nobody has drawn the line. Until that line exists, every new key is a coin flip.
First step This weekend, open your deployed site's page source and search it for each key you use — anything that turns up there is already public, and knowing which ones is the whole job.
Fixed in Core 1 · Secrets & Keys
Worth verifyingSelf-checked
The change-the-ID attack has never been tested
Nobody has changed a number in a URL and pressed enter. It is a two-minute test, and it is the one that tells you whether the paragraph above is theoretical.
First step Create a second test account, sign in as it, and confirm a record belonging to your first account refuses to load — if it loads, that is your Saturday.
Fixed in Core 3 · Authn vs Authz
Worth verifyingSelf-checked
Payment amounts may not be server-enforced
The price the browser sends is the price that gets charged. A determined customer can decide what your product costs.
First step Trace exactly what flips a user to paid, and confirm the flip happens on your server after your payment provider tells it to — not after the browser does.
Fixed in Core 7 · Payments That Hold
Worth verifyingSelf-checked
Rate limiting is partial or assumed
Nothing counts how often a stranger can call this app. Bills and login attempts both scale with whoever is calling, not with whoever is paying.
First step List every endpoint that costs you money when it runs — anything calling a paid API — and put a limit on the busiest one first.
Fixed in Core 4 · Abuse & Rate Limits
Worth verifyingSelf-checked
Fuzzy inventory of the personal data you hold
The owner can describe roughly what personal data is stored, not exactly. Roughly is not a position you can defend to anyone who asks, and people do ask.
First step Walk your tables for twenty minutes and write down what personal data each one holds and why — the columns you cannot justify are the ones to delete.
Fixed in Core 5 · Your Data Liability
Worth verifyingSelf-checked
Storage bucket policies never reviewed
The storage bucket rules are whatever the setup wizard chose. Uploaded files may be readable by anybody holding a link, or by anybody at all.
First step Open Storage in the Supabase dashboard and check each bucket's public flag and its policies — your table policies do nothing for files.
Fixed in Supabase 3 · Files & Storage