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_leaks or needs_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 null when 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.

Terminal
curl -s https://api.patchlabs.dev/receipts/$CHECK_ID
200 OK
{
  "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.

Terminal
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

Or paste a saved receipt, and add your fix file
Hashed in your browser and compared with fix_sha256. It isn’t uploaded.

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:

Terminal
pip install cryptography
verify_receipt.py
import 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 \uXXXX with lowercase hex (characters outside the Basic Multilingual Plane become a surrogate pair), as Python does by default;
  • true, false and null as 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:

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

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