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.

access manifest · every PatchLabs check
# 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 ✓
read-onlyPipeline facts as of 10 Nov 2026
A summary of the pipeline’s permissions, written as a manifest. Each line is explained below.
0requests sent to your live app or database by a Lab or Code check
0passwords: sign-in is by emailed link, and we never ask for your database password
Ed25519signature on every finished check’s receipt, verifiable by anyone
0cookies or third-party analytics on patchlabs.dev

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/*.sql and 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.

who the lab tests as
-- 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.

  1. Clone

    Read the repo

    A copy of your public repo is cloned from GitHub onto the worker machine.

  2. Strip

    Remove agent settings

    Repo-supplied agent files (.claude/, CLAUDE.md, AGENTS.md) are deleted, so your repo can’t steer our fix agent.

  3. Rebuild

    Build the database

    Your migrations run in a new, disposable Postgres database that copies Supabase’s roles, auth functions and default grants.

  4. Seed

    Add fake rows

    Synthetic test users and rows. No real personal data is ever loaded.

  5. 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.

  6. 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.

  7. Prove

    Rebuild and re-test

    A fresh lab with the fix applied: anonymous visitor denied, stranger denied, owner still reads their own rows.

  8. 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 retention
DataKept forNotes
Lab database (rebuilt schema and fake rows)Deleted after each checkDropped as soon as the probe and the proof finish.
Live posture resultsUntil you remove the domain or delete the appThe 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 workerUp to 30 daysA clone of public code plus logs, kept briefly so we can investigate a failed check, then deleted.
Reports, fixes and receiptsUntil you delete themSo 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 dataUntil you delete your accountDELETE /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 links20 minutes, single useStored only as a hash.
Sessions30 days, or until you sign outStored only as a hash on our side; the token lives in your browser’s localStorage.
API keysUntil you revoke themShown once, stored only as a hash.
IP addressNever stored in fullA salted, shortened hash is kept with each free check for rate limiting.
Emails we send youYour copy, plus our mail provider’s logsReport 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.

SITE

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.

API

Microsoft Azure

The API (api.patchlabs.dev) and its Postgres database run in our Microsoft Azure account. It stores accounts, checks, reports and receipts.

LAB

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.

Data processing terms
Subprocessors
CompanyWhat forData involved
AnthropicAI models that write fixes and reportsrelevant code, migrations, lab results
Microsoft AzureHosting for the API and its databaseaccount data, checks, reports, receipts
GitHubWebsite hosting; reading public reposrepo name; website visitors’ request logs
Spacemail by SpaceshipSending emailemail 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 receipt object 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.

verify_receipt.py
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.
/.well-known/security.txt
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.

Manraj Singh Dhillon, technical founder, London