Report
Reading a report
Every finished check ends on one page: what leaked, who could read it, the fix, and the proof that the fix works. This page walks through each part using the report from our own deliberately leaky demo app.
Open the sample report in another tab to follow along. It is a real run on manra399/patchlabs-demo-app, a public repo with fake data that we keep leaky on purpose.
The status
The pill at the top of the result tells you where things stand.
- fix proven
- We found leaks, wrote a fix, and the fix passed the full lab proof. It is ready for your engineer to review. API status
done. - in review
- We found leaks, but no fix passed the strict proof automatically, so a person is reviewing it before you apply anything. We email you when it’s ready. API status
needs_review. - no leaks found
- None of the tables we tested let the wrong people read personal data. API status
no_leaks. - pending
- Waiting for verification, in the queue, or running:
awaiting_verification,queued,running. - failed
- The check couldn’t finish (for example, the migrations don’t build). We are alerted automatically and will email you. API status
failed.
The summary
Under the headline is a short list of facts:
- Tables leaking
- Every table holding personal data (email, phone, address, date of birth and similar) that the wrong people could read in the lab.
- Readable by anyone
- Tables an anonymous visitor could read with your public anon key. This is the worst case: no account needed.
- Readable by anyone who signs up
- Tables any signed-in user could read, including rows that belong to other people.
- Lab proof
- Passed means the fix denied the anonymous visitor, denied the stranger, and still let the owner read their own rows. Not passed means a person is reviewing.
- Risk of a visible change
- Low, medium or high: how likely the fix is to change something your users notice, with the reason. A fix that removes access your frontend relied on is higher risk.
- Questions for you
- Business questions only you can answer, such as whether sellers’ emails should be public. Each comes with a recommended answer. See questions.
Findings, grouped by line
Below the summary, every problem the check found is listed as a finding with a stable ID, grouped by the product line that found it and sorted most severe first. Each finding has a severity, the object (a table, a function, a file), the file and line in your repo, short redacted evidence, and a one-line fix. Every field explained.
- Lab findings (
PL-001toPL-008) were reproduced in the rebuilt database and are marked proven, with what the lab saw: for example “signed-in stranger read 3 rows owned by another user”. - Code findings (
PL-009toPL-011andPL-017toPL-026) were read from your repo: keys, Edge Functions and their config, webhooks, CORS, admin checks in the browser, roles inuser_metadata, raw HTML, Firebase rules and vulnerable dependencies. They point at a file and line for your engineer to judge, and are not marked proven.
Each ID links to a page with an example, a way to check it yourself and the fix: the finding index. Some IDs there (PL-012 to PL-016) are marked coming: PatchLabs doesn’t test for them yet, so they won’t appear in a report.
Who “the wrong people” are
The lab tests every table as three identities, with fake seeded rows in place:
| Identity | Who it stands for | Should read |
|---|---|---|
| anon | An anonymous visitor using your public anon key, which ships inside your frontend | Only data you meant to publish |
| stranger | A signed-in user who owns nothing: anyone who can sign up | Nobody else’s private rows |
| owner | A signed-in user who owns a seeded row | Their own rows, still, after the fix |
The third test matters as much as the first two. A fix that locks everyone out would “close” the leak and break your app, so the owner must still read their own data for the proof to pass.
The proof table
The report’s proof table shows, for each leaking table, how many seeded rows each identity could read before and after the fix. This is the one from the sample report:
| Table | Who could read it | Verdict | Before | After | Result |
|---|---|---|---|---|---|
profiles | anyone on the internet | bug | anon 2, stranger 2 | anon 0, stranger 0, owner 1 | closed |
- Verdict is our judgement of the table. bug means personal data that shouldn’t be readable; data that looks deliberately public, such as a business’s own contact details, is called out separately and usually becomes a question for you.
- Before and After are row counts from the lab, not from your live database. We never see your real data.
- Result is closed when the after-counts pass all three tests.
The fix
The fix is a SQL migration, written to be added to supabase/migrations/ with a timestamped name so it runs after your existing migrations. It comes with a one-line explanation and the risk note. Here is the sample’s:
-- Only allow users to read their own profile (where user_id = auth.uid())
DROP POLICY "Profiles are viewable by everyone" ON public.profiles;
CREATE POLICY "Users can view own profile" ON public.profiles
FOR SELECT USING (auth.uid() = user_id);If the fix also needs a change in your app’s code (for example, a page that listed every user’s profile), the report says so in plain words. Make that change too. How to apply a fix.
Your decision and the signed receipt
If the check belongs to an app in your workspace and you’re signed in, the report shows a Your decision panel: approve the fix, or mark tables as meant to be readable, and answer any questions. How decisions work. Below it, every finished report carries its signed receipt, which anyone can verify. About receipts.
Exposed keys
The report also lists any AI, payment or database keys found in your code or anywhere in its git history: PL-009 for a secret key in the current code, PL-010 for a Supabase service role key in client code, PL-011 for a key that only survives in history. A key deleted in a later commit is still readable by anyone who clones the repo. Values are always masked.
Rotate first, clean up second. If a key is listed, create a new one at the provider and revoke the old one. Rewriting git history alone doesn’t help: the old key may already have been copied.
Run details
The last line of the report records how long the run took and which models were tried. We start with the smallest Claude model and only move to a larger one if the lab proof fails, with a spending cap on every check. In our lab study, 15 of 20 apps needed only the smallest model (n=20, 9 Nov 2026).
When a report says “in review”
If no fix passes the strict proof on its own, the check goes to a person instead of guessing. In the same study, 18 of 20 fixes passed the lab proof with no human and 2 went to a person (n=20, 9 Nov 2026). You’ll get an email when the reviewed fix is ready; nothing changes in your repo meanwhile.
What a report can’t tell you
The lab is only as complete as the migrations in your repo. Policies or tables created by hand in the Supabase dashboard, and never saved as a migration, are invisible to us. The lab tests read access to tables in the public schema, not every query your app makes. Code findings are pattern checks that point at a line for a person to judge; the dependency check is reported as skipped if the OSV database can’t be reached. A report is not a penetration test. If something looks wrong, email the founder with the check link.