Finding index
Every problem we test for, with the fix and the proof.
31 live checks with stable IDs: 8 that PatchLabs Lab proves in a database rebuilt from your migrations, 13 that PatchLabs Code finds by reading your repo, and 10 that Live posture reads from your public home page on a domain you verify. 5 more are written up but not tested yet, and are marked as coming. Each page shows vulnerable code, a way to check your own project and the fix.
All findings
36 of 36 shown
Live · PatchLabs Lab · who can read what in your database
Anonymous visitor can read personal data
Anyone with your app's public key can read emails, phone numbers or addresses.
Any signed-in user can read every row
Signing up unlocks other people's rows. If sign-up is open, that means anyone.
RLS enabled but no policies (owner locked out)
Not a leak, yet. The table is locked to everyone, and the quick fix people reach for is the leak.
UPDATE policy missing WITH CHECK
Users may edit their own row, and nothing stops them changing its role, plan or owner.
Always-true policy
A policy that says yes to everyone. On writes, anyone can change or delete your data.
SECURITY DEFINER function exposed
A function that runs with the owner's powers, callable by anyone through the API.
Public storage bucket
Files in this bucket are served to anyone with the link. ID scans and invoices don't belong there.
Row level security not enabled
No rules at all. With API access granted, anyone with the public key can read and change every row.
Live · PatchLabs Code · keys and risky code in your repo
AI, payment or database key in the code
A secret key for an AI, payment or database service is sitting in your current code, readable by anyone who can read the repo.
Service role key in client code
The key that ignores every access rule, shipped to every visitor's browser.
Key still in git history
You deleted the key. Every old commit still has it, and so does every clone and fork.
Edge function open without sign-in while using the service key
A server function holding the key that ignores every rule, with nothing checking who is calling it.
Sign-ups need no email confirmation
Anyone can create a working account with any email address, including one they don't own.
Access decided by user_metadata, which users can edit
Roles stored where users can rewrite them. One call from the browser and they're an admin.
Admin check only in the browser
The admin page is hidden in the app, but nothing stops a normal user asking the database for the same data.
Payment webhook doesn't verify its signature
Anyone can send your app a fake 'payment succeeded' event and get the paid plan for free.
Edge function trusts a user id sent in the request
The function acts on whichever user id the request names, so any user can act as any other.
Dependency with a known vulnerability
A package version in your lockfile matches a published advisory in the OSV database.
User content rendered as raw HTML
Text one user wrote goes into the page as HTML, so it can run scripts in other users' browsers.
Firebase rules allow anyone
Your Firestore, Realtime Database or Storage rules say yes to everyone, or do until a test-mode date.
Any website can call a signed-in function
The endpoint tells browsers that any website may send your users' cookies to it and read the answer.
Live · PatchLabs Live posture · HTTPS and headers on your verified domain
Site doesn’t force HTTPS
The plain http:// address of your site serves the page instead of redirecting to https://.
TLS certificate is invalid, expired or expires within 21 days
Browsers will block your site, or soon will, because its HTTPS certificate doesn’t check out.
No HSTS header, or max-age under 180 days
Browsers aren’t told to use HTTPS only, so a visitor’s first request can still go out over plain HTTP.
No Content-Security-Policy
Your page sends no Content-Security-Policy, so any script that gets onto it can run and send data anywhere.
Any site can frame the page
Neither frame-ancestors nor X-Frame-Options is set, so another site can load your app invisibly inside its own.
No X-Content-Type-Options: nosniff
Browsers may guess a file’s type from its contents, so an uploaded “image” can be run as a script or page.
Cookie set without Secure, or a session cookie without HttpOnly
Your home page sets a cookie that can travel over plain HTTP, or a session cookie that any script on the page can read.
Page loads scripts or styles over plain HTTP
Your HTTPS page pulls a script or stylesheet from an http:// address, which anyone on the network can replace.
Server headers reveal software versions
A Server or X-Powered-By header names the software and its version, which tells attackers which known bugs to try.
No Referrer-Policy
Your page sends no Referrer-Policy, so the browser’s default decides how much of your URLs other sites see.
Coming · not tested yet
Explained, with self-checks and fixes, but PatchLabs does not test for these yet. They won’t appear in a report until they ship.
Strangers can change or delete other people's rows
A write policy checks that someone is signed in, not that the row is theirs. Anyone who signs up can edit or wipe it.
Users can make themselves admin
A role column or roles table that users can write to. One request turns a normal account into an admin.
View skips row level security
Your table's policies are right, but a view on top of it runs as its owner and hands every row to the API.
Stored files readable or writable by anyone
The bucket is private, but its storage policies let anyone read, upload, replace or delete other people's files.
Realtime sends private rows to subscribers
A table is published to Realtime and its read policy is too broad, so new rows stream to people who shouldn't see them.
No findings match those filters.
IDs never change meaning and are never reused, so you can link to them from tickets and docs. Last reviewed 10 Nov 2026.
Severity
Ranked by who can reach the data.
Severity follows one question: who, outside the people meant to see it, can read or change the data? Dependency findings take the severity of their published advisory.
| Level | Meaning |
|---|---|
| critical | Anyone on the internet, or anyone who signs up, can read personal data, change a whole table or make themselves admin. |
| high | Anyone who signs up can read personal data, a privileged function or webhook is open, or a user can act on another user's data. |
| medium | A real weakness that needs a specific situation to matter, such as a public bucket, unsanitised HTML or a loose CORS rule. |
| low | Nothing exposed by itself, but the app is likely broken or other findings get easier to exploit. |
| info | Nothing exposed and nothing to exploit: a one-line hardening step. Costs no points in the Live posture grade. |
How a finding reaches you
Data you watched leak, code you can point at, or a header you can read.
Every finding in a report uses these IDs and the same shape: severity, the object, the file and line or the address, short evidence, and how it was found.
PatchLabs Lab
Rebuilds the database from your migrations in a disposable Postgres with Supabase's roles and grants, seeds fake rows, and reads every table as an anonymous visitor, a signed-in stranger and the owner. Then writes the fix and proves it the same way.
PatchLabs Code
Reads the repo without running it: keys in code and the full git history, Edge Functions and their config, webhooks, CORS, admin checks in the browser, raw HTML, Firebase rules, and dependency versions checked against OSV.
Live posture
On a domain you verify, loads your public home page like any visitor and reads the response: HTTPS, the certificate, security headers, cookie flags and mixed content. Never your database.
Static RLS check
Reads the SQL in your supabase/migrations and flags PL-001 to PL-008 from the rules as written. The free headers check does the same for PL-027 to PL-036 on any address.
Map
How our database IDs line up with Supabase’s linter.
Use Supabase's Security Advisor as well: it covers health and performance checks we don't. The difference is in the rows marked "none": public reads, which the linter skips on purpose because only the app's intent can say whether they are a leak. Code findings without a linter equivalent are left out.
| PatchLabs ID | Finding | Related Supabase lint |
|---|---|---|
| PL-001 | Anonymous visitor can read personal data | none (0024 skips SELECT) |
| PL-002 | Any signed-in user can read every row | none (0024 skips SELECT) |
| PL-003 | RLS enabled but no policies (owner locked out) | 0008 |
| PL-004 | UPDATE policy missing WITH CHECK | – |
| PL-005 | Always-true policy | 0024 (writes only) |
| PL-006 | SECURITY DEFINER function exposed | 0011, 0028, 0029 |
| PL-007 | Public storage bucket | 0025 |
| PL-008 | Row level security not enabled | 0013, 0007 |
| PL-010 | Service role key in client code | – |
| PL-011 | Key still in git history | – |
| PL-012 | Strangers can change or delete other people's rows coming | 0024 (only literal true) |
| PL-013 | Users can make themselves admin coming | – |
| PL-014 | View skips row level security coming | 0010, 0002, 0016 |
| PL-015 | Stored files readable or writable by anyone coming | – |
| PL-016 | Realtime sends private rows to subscribers coming | – |
| PL-019 | Access decided by user_metadata, which users can edit | 0015 (policies only) |
Lint numbers from Supabase's database linter (splinter), checked against its source in Nov 2026. Independent: PatchLabs is not affiliated with Supabase.
Free check
Which of these does your app have?
Paste your public repo. You get every live finding, the fix and the proof on one page.