Trust
We prove the fix on a copy. Your database is never touched.
PatchLabs reads your GitHub repo, rebuilds your database from its migrations in a throwaway lab, and tests the fix there. We never ask for your database password, and a check never sends a single request to your app. The one exception is opt-in: Live posture loads your public home page, like any visitor, and only on a domain you verify. This page says exactly what we read, what we keep, who else is involved and where our limits are.
# what a check may touch [READ] github.com/you/your-app code, migrations, git history [VERIFY] patchlabs-verify.txt REQUIRED one-time code: it’s yours # what a check never touches [LIVE] your live app NONE Live posture is separate: opt-in, public home page, verified domain [DB] your Supabase project NONE [SECRETS] database password NEVER [PUSH] your repo NONE # where the testing happens [LAB] disposable Postgres, fake rows dropped when the run ends ✓
Access
What we read, and what we never need.
A check starts from a public GitHub repository and nothing else. Everything we need to find a leak and prove its fix is already in your code.
ReadWhat a check reads
- Your public GitHub repoSource code,
supabase/migrations/*.sqland the full git history (for exposed keys). Read through GitHub, the same way anyone can read a public repo. patchlabs-verify.txtA file you commit with a one-time code, so we know the repo is yours. You can delete it once the check starts.- Your email addressSo we can send the report link and tell you when it’s ready.
NeverWhat we never need
- Your database password or service-role keyThe lab is built from your migrations, so it has its own roles and keys.
- Your production dataThe lab holds fake rows we generate. Your customers’ records never leave your database.
- Access to your live app or Supabase projectNo API calls, no logins to your dashboard, no database connection. Live posture, if you turn it on, loads only your public home page on a domain you verified.
- Write access to your repoYou commit the fix yourself, after your engineer reads it.
Private repos and automatic pull requests need a GitHub App with a read-only grant you can revoke. That is coming; it is not live yet. Today we support public repos only, and fixes arrive as a downloadable migration plus a one-click “commit this file on GitHub” link.
Production
Your database: never touched.
A check never connects to your live app or database. Not to test, not to confirm a leak, not to check a fix.
When a report says “an anonymous visitor can read profiles”, that was observed in our lab, against a database rebuilt from your own migrations, as the lab’s anon role: the role your public key maps to. Your real key and your real users are never part of it.
That is also why a fix can be proven before you ship it: the same lab is rebuilt with the fix applied, and the three identities are tested again.
Because we never look at production, anything changed directly in the Supabase dashboard and not captured in supabase/migrations is invisible to us. The report says what the lab could and couldn’t rebuild.
Live posture is opt-in and separate. PatchLabs never touches your database. Live posture only loads your public home page, like any visitor, and only on domains you verify (a file or meta tag on that domain). Each check is one plain http:// request to see whether it redirects, one https:// page load following at most 5 redirects, and that load’s TLS handshake. It grades HTTPS, the certificate, security headers, cookie flags and mixed content. No other paths, no forms, no API calls, no attack payloads. How Live posture works.
-- every probe runs inside the lab database
set role anon; -- anonymous visitor (public key)
select count(*) from public.profiles;
set role authenticated; -- signed-in stranger
select set_config('request.jwt.claim.sub', '<stranger>', true);
select count(*) from public.profiles;
select set_config('request.jwt.claim.sub', '<owner>', true);
select count(*) from public.profiles; -- owner still reads own row
Lab lifecycle
A fresh lab for every check, gone when it ends.
Each check runs through the same eight steps. One check runs at a time on the worker.
Clone
Read the repo
A copy of your public repo is cloned from GitHub onto the worker machine.
Strip
Remove agent settings
Repo-supplied agent files (
.claude/,CLAUDE.md,AGENTS.md) are deleted, so your repo can’t steer our fix agent.Rebuild
Build the database
Your migrations run in a new, disposable Postgres database that copies Supabase’s roles, auth functions and default grants.
Seed
Add fake rows
Synthetic test users and rows. No real personal data is ever loaded.
Probe
Test who can read what
As an anonymous visitor, a signed-in stranger and the owner. Code and git history are also scanned for exposed keys.
Fix
Write the fix
The smallest Claude model goes first. A bigger one is tried only if the proof fails, within a spend cap per check.
Prove
Rebuild and re-test
A fresh lab with the fix applied: anonymous visitor denied, stranger denied, owner still reads their own rows.
Drop
Delete the lab
The lab database is dropped as soon as the probe and proof finish. Nothing is pushed to your repo.
Fix agent
The fix agent works in a box.
Fixes are written by Claude models from Anthropic, running under tight permissions we set on every run.
- No web access. Web fetch and web search are switched off for the agent.
- Narrow write access. It may only edit the fix folder and the copy of your app’s source on the worker. It cannot touch the original repo.
- One command. The only program it may run is our lab verifier.
- Independent proof. Whatever the agent says, the fix is re-tested by a separate verifier on a fresh lab. If that fails on every model, nothing is offered as proven and a person reviews it.
- Your engineer approves. We never merge or deploy. You get a migration file to read, commit and ship.
- Spend cap. Each check has a model budget. Once it’s reached, we stop escalating and a person takes over.
Retention
What we keep, and for how long.
Short version: the lab is thrown away after each run, reports stay until you delete them, and your account data stays until you delete your account.
| Data | Kept for | Notes |
|---|---|---|
| Lab database (rebuilt schema and fake rows) | Deleted after each check | Dropped as soon as the probe and the proof finish. |
| Live posture results | Until you remove the domain or delete the app | The grade, findings and the response headers and cookie names of your public home page. Removing the domain deletes its history. The free headers check stores nothing. |
| Working copy of your repo and run files on the worker | Up to 30 days | A clone of public code plus logs, kept briefly so we can investigate a failed check, then deleted. |
| Reports, fixes and receipts | Until you delete them | So your report link keeps working. Delete them from your account, or email us for a check made without an account. |
| Email address, name and account data | Until you delete your account | DELETE /me (or the button in Settings) removes your user, sessions, solely owned workspaces, apps, checks and keys, plus earlier checks made with your email. |
| Sign-in links | 20 minutes, single use | Stored only as a hash. |
| Sessions | 30 days, or until you sign out | Stored only as a hash on our side; the token lives in your browser’s localStorage. |
| API keys | Until you revoke them | Shown once, stored only as a hash. |
| IP address | Never stored in full | A salted, shortened hash is kept with each free check for rate limiting. |
| Emails we send you | Your copy, plus our mail provider’s logs | Report links, sign-in links and alerts only. No marketing emails. |
Signed-in users can download everything we hold about them as JSON (GET /me/export, or Settings → Export). Full details in the privacy policy.
Where it runs
Three places, each with one job.
patchlabs.dev
A static website served by GitHub Pages. No server code, no cookies, no analytics. Pages that show your data call the API from your browser.
Microsoft Azure
The API (api.patchlabs.dev) and its Postgres database run in our Microsoft Azure account. It stores accounts, checks, reports and receipts.
The worker machine
Checks run on a worker machine we operate in London, UK. It asks the API for the next verified check, runs the lab and the fix agent, and posts the report back.
Subprocessors
Who else is involved.
Every company that processes data on our behalf. We’ll update this list before adding anyone new.
| Company | What for | Data involved |
|---|---|---|
| Anthropic | AI models that write fixes and reports | relevant code, migrations, lab results |
| Microsoft Azure | Hosting for the API and its database | account data, checks, reports, receipts |
| GitHub | Website hosting; reading public repos | repo name; website visitors’ request logs |
| Spacemail by Spaceship | Sending email | email address, message content |
The website also loads fonts from Google Fonts, and the report page loads two open-source libraries from cdnjs (Cloudflare). Those services see your IP address and browser type when the files load, as with any website that uses them.
Accounts
How accounts and keys are protected.
No passwords
Sign-in is by a one-time link sent to your email. Links are 32 random bytes, stored hashed, single use, valid for 20 minutes, and limited to 5 per email per hour. We give the same answer whether or not an account exists.
Sessions you control
A session is 32 random bytes, stored hashed on our side, valid for 30 days. Signing out revokes it on the server. It travels as a Bearer header, never as a cookie.
Keys shown once
API keys (pl_live_…) are shown once and stored only as a hash. Revoke one and it stops working immediately.
Webhooks
Webhook URLs must use HTTPS and a public host. Private and loopback addresses are refused, so a webhook can’t be pointed inside our network.
Rate limits
Free checks and the free RLS tool are rate limited per IP (using a hash, never the raw address) and per email.
Unguessable links
Report links use random UUIDs. Anyone with the link can read the report, so share it like a password.
Signed receipts
Every result is signed. Anyone can check it.
When a check finishes, we sign a receipt with Ed25519: the repo, the result, the tables that leaked, whether the lab proof passed, a SHA-256 of the fix, and every approval and answer from your team.
- Receipt
GET https://api.patchlabs.dev/receipts/<check id>- Public key
api.patchlabs.dev/.well-known/patchlabs-receipt-key- Signed bytes
- The
receiptobject as JSON, keys sorted, no spaces
Change one character of a receipt and the signature no longer verifies. That lets a client, auditor or investor confirm a result came from us, without trusting a screenshot.
import base64, json, urllib.request
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
API, CHECK = "https://api.patchlabs.dev", "<check id>"
get = lambda p: json.load(urllib.request.urlopen(API + p))
r = get(f"/receipts/{CHECK}")
k = get("/.well-known/patchlabs-receipt-key")
assert r["key_id"] == k["key_id"], "signed with a different key"
msg = json.dumps(r["receipt"], sort_keys=True,
separators=(",", ":")).encode()
pub = Ed25519PublicKey.from_public_bytes(
base64.b64decode(k["public_key"]))
pub.verify(base64.b64decode(r["signature"]), msg)
# raises InvalidSignature if anything was changed
print("genuine:", r["receipt"]["status"])
Scanning policy
Only repos you own or are allowed to test.
A full check can’t start until the ownership file is on the repo’s default branch. Only someone with write access to the repo can put it there.
How ownership is checked
When you start a check we give you a one-time code. You commit it to patchlabs-verify.txt on the default branch (one click opens GitHub with the file filled in). We read that file; if the code matches, the check is queued. For monitored apps, verification happens once.
The free RLS check
Our free tool reads a public repo’s migration files and reports patterns, without a lab, without a fix and without verification. It sees exactly what anyone browsing the repo on GitHub sees. It is rate limited.
Live posture and the free headers check
Daily Live posture checks run only on a domain the app’s team has verified, with a code in /patchlabs-verify.txt or a patchlabs-verify meta tag on the home page. The free headers check loads one public page you name, once, exactly as a browser would, and keeps no result. It is rate limited and refuses private addresses.
Opting out
If you own a public repo and don’t want it in PatchLabs research samples or free-tool results, email manraj@patchlabs.dev with the subject “Opt out” and the repo URL. We’ll confirm within two working days and delete anything we hold about it. Our published research is aggregate only and never names an app.
Full rules in the acceptable use policy.
Responsible disclosure
Found a problem with PatchLabs? Tell us.
Email manraj@patchlabs.dev with the subject “Security”. You’ll reach the founder directly.
- We reply within two working days.
- We won’t take legal action against good-faith research that follows the policy.
- We credit you on the changelog if you’d like.
Contact: mailto:manraj@patchlabs.dev
Expires: 2027-11-09T23:59:59.000Z
Preferred-Languages: en
Canonical: https://patchlabs.dev/.well-known/security.txt
Policy: https://patchlabs.dev/legal/disclosure-policy
Known limitations
What PatchLabs does not do.
We would rather you know these now than find out later.
- Database access rules onlyWe test who can read personal data in Supabase/Postgres tables, and scan for exposed keys. We don’t test the rest of your app.
- As complete as your migrationsThe lab is rebuilt from
supabase/migrations. Changes made only in the Supabase dashboard are invisible to us. - Live posture is not a vulnerability testIt reads the headers, certificate and cookies of your public home page. It doesn’t test your app for data leaks, log in, or try other URLs.
- Not a penetration testWe don’t attack your app or test business logic. Dependencies are checked against known advisories (PL-023) from your lockfiles, not by probing the running app.
- Proof covers the tables we testA passing proof means the three identities behave correctly for the tables checked, not that your whole app is secure.
- Public repos only, for nowPrivate repos and automatic pull requests need our GitHub App, which is coming.
- One worker machineChecks run one at a time, so a busy queue means a longer wait. The status page shows the queue live.
- Some fixes need a personIn our lab study, 18 of 20 fixes passed the strict proof with no human (n=20, 9 Nov 2026). The other 2 went to a person.
- No certificationsWe hold no SOC 2, ISO 27001 or similar certification and don’t claim to. We won’t show a badge until one is real.
Questions
Need something for your security review?
Ask the founder directly. We can answer questionnaires, walk through the pipeline, or sign a data processing agreement.