Receipt
Signed receipts
Every finished check comes with a receipt signed with Ed25519. Anyone can confirm it came from PatchLabs and hasn’t been edited since: a client, an investor, an auditor, or you in a year’s time.
What a receipt records
A receipt is issued for every check that finishes as done, no_leaks or needs_review. It records:
check_id,repo- Which check, of which repo.
status- The result:
done,no_leaksorneeds_review. tables_leaking- Every table the check found readable by the wrong people.
lab_proof_passed- Whether the fix passed all three tests: anonymous visitor denied, stranger denied, owner still reads their own rows.
fix_sha256- The SHA-256 of the exact fix file we proved, or
nullwhen there is no fix. finished_at- When the check finished (ISO 8601, UTC).
decisions- Every approve or reject:
{action, tables, reason, by, at}. answers- Every answer to the report’s questions:
{question, answer, by, at}.
Because decisions and answers are included, a receipt fetched after your team approves a fix is different from one fetched before, and each is signed. Save the one you want to keep.
Fetch a receipt
Receipts are public to anyone with the check ID, like the report itself. Before the check finishes, the API answers 409.
curl -s https://api.patchlabs.dev/receipts/$CHECK_ID{
"receipt": {
"check_id": "7c1e…", "repo": "manra399/patchlabs-demo-app", "status": "done",
"tables_leaking": ["profiles"], "lab_proof_passed": true,
"fix_sha256": "9b0f…", "finished_at": "2026-11-09T16:56:41.204512+00:00",
"decisions": [], "answers": []
},
"signature": "base64…",
"key_id": "ed25519:4be1c0a29f7d3e18",
"algorithm": "Ed25519"
}The public key
Our current public key is published by the API. key_id is ed25519: followed by the first 16 hex characters of the SHA-256 of the raw 32-byte public key, so you can recompute it yourself.
curl -s https://api.patchlabs.dev/.well-known/patchlabs-receipt-key
# {"key_id": "ed25519:…", "algorithm": "Ed25519", "public_key": "<base64 raw 32 bytes>", "pem": "-----BEGIN PUBLIC KEY-----…"}Verify a receipt
In your browser
This runs entirely in your browser with the Web Crypto API. It fetches the receipt and our public key, rebuilds the signed bytes, and checks the signature. Add the fix file you applied to check it matches too.
Verifier
The verifier needs JavaScript. The Python script below does the same job.
The same verifier is on every app’s public proof page, and each finished report shows its receipt.
In Python
Install the cryptography package, put a check ID in CHECK, and run:
pip install cryptographyimport base64, json, urllib.request
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
API, CHECK = "https://api.patchlabs.dev", "<check id>"
r = json.load(urllib.request.urlopen(f"{API}/receipts/{CHECK}"))
k = json.load(urllib.request.urlopen(f"{API}/.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("receipt is genuine:", r["receipt"]["status"], r["receipt"]["tables_leaking"])To verify a receipt you saved earlier, change the r = … line to r = json.load(open("receipt.json")).
Exactly what is signed
The signature covers the receipt object only, serialised as Python’s json.dumps(receipt, sort_keys=True, separators=(",", ":")). If you verify in another language, reproduce those bytes exactly:
- keys sorted at every level, no spaces after
,or:; - pure ASCII: every character outside printable ASCII escaped as
\uXXXXwith lowercase hex (characters outside the Basic Multilingual Plane become a surrogate pair), as Python does by default; true,falseandnullas in JSON.
The browser verifier above does this in about ten lines of JavaScript (the pyjson function in this file); feel free to borrow it.
Check a fix file against its receipt
fix_sha256 is the SHA-256 of the fix exactly as we served it. Hash the downloaded file and compare:
shasum -a 256 20261109165600_patchlabs_leak_fix.sql # or: sha256sum …A match means the migration you are about to apply is byte for byte the one the lab proved. An editor that adds a trailing newline will change the hash, so compare the file as downloaded.
Key changes
If we ever replace the signing key, receipts signed with the old key will no longer verify against the published one (their key_id won’t match). We will announce any change on the changelog, with the retired public key.
What a receipt proves
A receipt proves that PatchLabs issued these results for this check and that nobody has edited them since. It doesn’t re-run the lab, and it says nothing about commits made after finished_at. For the current state of an app, run a re-check.