Sample report

This is the whole thing.

Below is a complete report for a demo app I built for this — a small Supabase note-taking app on notes.example, which is a reserved address and belongs to nobody. Real scores, real findings, real first steps. Nothing here is trimmed or blurred, because on a real report nothing is either.

About this example

I made the app and I answered the 11 questions as its owner. The grade came from scanning it; the score came from those answers. It is not a customer, not a competitor, and not anybody's live site.

The exposure report

Several doors are open. Most take under an hour to close.

Do these first

Start with these two.

  1. No real access control on user dataSelf-checked

    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.

  2. Source maps are published with your production bundleMeasured

    Turn off source-map output for production builds (or upload them only to your error tracker) and redeploy.

Free, like everything else on this page.

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

And then what

These 12 findings live in 9 modules — all of them in the Sprint, $39.

  • Core 2 · What the Browser Sees
  • Supabase 1 · RLS That Actually Restricts
  • Core 3 · Authn vs Authz
  • Core 6 · Backups & Recovery
  • Core 1 · Secrets & Keys
  • Core 7 · Payments That Hold
  • Core 4 · Abuse & Rate Limits
  • Core 5 · Your Data Liability
  • Supabase 3 · Files & Storage

Everything above stays free on a real report too: the grade, the score, every finding, and the first steps. The $39 is for closing them, not for seeing them.