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-001 to PL-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-009 to PL-011 and PL-017 to PL-026) were read from your repo: keys, Edge Functions and their config, webhooks, CORS, admin checks in the browser, roles in user_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:

IdentityWho it stands forShould read
anonAn anonymous visitor using your public anon key, which ships inside your frontendOnly data you meant to publish
strangerA signed-in user who owns nothing: anyone who can sign upNobody else’s private rows
ownerA signed-in user who owns a seeded rowTheir 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:

TableWho could read itVerdictBeforeAfterResult
profilesanyone on the internetbuganon 2, stranger 2anon 0, stranger 0, owner 1closed
  • 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:

supabase/migrations/20261109165600_patchlabs_leak_fix.sql
-- 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.

Updated 10 Nov 2026Something unclear or wrong? Tell the founder