FaceGUID
Security model

What is protected, by what, and what is not.

Biometric systems attract a lot of hand-waving. This page is the opposite: the exact construction, and the exact places it can fail.

1 · From a fuzzy face to an exact key

A face descriptor is never identical twice — lighting, angle and expression move it around. A cryptographic key must be bit-exact. FaceGUID bridges the two with a fuzzy commitment over reliably-selected random projections.

Project.
The 128-D descriptor is projected onto 6,144 fixed pseudo-random gaussian vectors. The matrix is public and identical in every browser; it is part of the format, not a secret.
Select the stable ones.
Across the twelve capture frames, each projection gets a reliability score of |mean| / spread. The top 1,920 — those sitting furthest from their decision boundary — are kept. This is what makes recovery survive a different room and a different day.
Commit.
A fresh random 128-bit secret is repetition-encoded 15× to 1,920 bits and XORed with the sign bits. Only that mask is stored. Without a face that reproduces similar sign bits, the mask is indistinguishable from noise.
Recover.
A later scan XORs its own sign bits back through the mask and majority-decodes each run of 15. Individual bits may flip; the secret comes back whole.
The secret exists only in page memory, only for the seconds it is needed. It is never transmitted, never written to storage, and cannot be recovered from anything the server holds.
2 · What that key unlocks
KeyDerived fromProtects
Face KeyHKDF(secret ‖ optional passphrase, salt)Wraps the two below.
Vault Master KeyRandom 256-bit, wrapped by the Face KeyRecord titles, tags, per-record keys, the search key.
Signing keyECDSA P-256, private half wrapped by the Face KeySigns every record and every reveal. The public half is what the server checks.
Search keyHKDF(vault master key)Blind-index tokens — HMACs the server can match but not invert.
3 · Two locks, not one

Unlocking the vault decrypts metadata only: titles, kinds, tags, field names. Every individual secret stays sealed.

Revealing one takes a second ceremony. The server issues fresh digits bound to that specific record id, burns them on first use, and requires an ECDSA signature over faceguid/reveal/v1|guid|itemId|nonce. A signature for one record proves nothing about another, and a signature from five minutes ago proves nothing now.

Saving records reuses a signing key for up to two minutes so you can add several in a row. Revealing never does — the server enforces freshness by burning the liveness nonce, so the client cannot opt out.
4 · What the server can see
StoredReadable by the operator?
Face template (128 floats)Encrypted at rest with a server key; decrypted in memory only during a 1:N match.
Fuzzy-commitment maskYes, and it is useless without the face.
Record titles, tags, secretsNo. AES-GCM under keys derived in your browser.
Search queriesNo. Only truncated HMACs, which cannot be inverted.
Linked email / phone / URLsYes — these are claims about public identifiers, and the API releases them by scope.
Document ciphertextNo. Chunk-wise AES-GCM under a key wrapped by the vault master key.
Document names and sizesNames no — the metadata is encrypted. Byte counts yes, since storage has to be measured.
Proof receiptsYes — which client asked, when, and whether the proof was spent. Never what it was spent on.
Audit logYes. Event names and timestamps, never contents.
5 · Sealed documents

A document adds one thing the vault does not have: a seal that is meaningful to someone who does not trust us, and stays meaningful if we disappear.

Encrypted before it moves The browser splits the file into 1 MiB chunks and encrypts each under a per-document AES-256-GCM key. That key is wrapped with the vault master key, which is derived from the face. What arrives here is ciphertext with no key attached.
Chunks cannot be rearranged Chunk i is encrypted with iv = baseIv ‖ uint32(i) and additional data naming the document, the index and the total. Drop a chunk, reorder two, or splice one in from another file and decryption fails rather than producing something plausible.
The seal is a signature, not a record The face-bound key signs faceguid/seal/v1|guid|document|sha256|timestamp. We check that signature before storing anything — an unsigned document is deleted, bytes and all — but the proof does not depend on us having done so. It is verifiable from the certificate alone.
Opening is its own ceremony Each document has its own liveness challenge and its own signature over its own id. A signed-in session lists documents; it does not open them. The bytes are released against a token that expires in two minutes and burns on first read.
The hash is of the plaintext Which is what lets a stranger holding the original file verify the seal without ever seeing the ciphertext — and lets you detect, on opening, that stored bytes no longer match what was sealed.
Publishing is opt-in and narrow A published certificate discloses the hash, the timestamp, the signature and the FaceGUID that made it. It never discloses the document, its name, its size in any useful sense, or its contents. Unpublished and non-existent seals answer identically.
“Forever” is about retention, not recovery. Nothing expires and nothing is deleted but by you. That is not the same as being recoverable: the key comes from your face and exists nowhere else, so a face that can no longer be presented is a document that can no longer be opened. Keep independent copies of anything you cannot lose.
6 · The proof-of-human token

A proof is the smallest thing FaceGUID says about a person, and the thing most applications actually consume. Five properties do the work.

Signed, not asserted ES256 over the same key as id_tokens, verifiable against the same JWKS. The header typ is faceguid-proof+jwt, so a token minted for one purpose cannot be spent as the other.
Bound to one client aud is the client_id, and only that client’s credentials can verify it. A proof lifted from one integration is worthless at the next.
Bound to one challenge Your nonce travels inside the signature. Check it and a proof cannot be moved between sessions of your own application.
Bound to one origin The browser can only mint a proof for a web origin registered on that client, and the ceremony posts it to that exact origin — never to *. A leaked client_id cannot be used to harvest proofs from a page you do not control.
Spendable once Every mint writes a receipt. Verification redeems it; the second attempt is a 409. Verify offline if you prefer speed — then redemption is your call to /api/proof/redeem, not something the signature can do for you.
What a proof never carries: no image, no video, no descriptor, no key material. The receiving application holds a UUID and a signature — which is what keeps biometric data out of their breach surface as well as ours.
7 · What a client_id is allowed to do

An identity provider's consent screen is a credibility machine. If anyone can point one at anywhere, the provider becomes phishing infrastructure — a genuine URL, a real camera prompt, and a destination chosen by the attacker.

Destinations must prove themselves Before a client may redirect to a host, or run the SDK on it, the registering identity must publish faceguid-verification=… — derived from its own GUID — at https://host/.well-known/faceguid.txt. Unproven hosts are refused at registration and on every edit, so a client cannot be quietly repointed later.
Loopback is exempt localhost and 127.0.0.1 need no proof. A developer on their own machine cannot publish anything, and cannot phish anyone with an address that only resolves on the victim's own computer.
Nothing may claim to be us A client cannot be named after FaceGUID. The consent screen also shows the exact origin you will be returned to, next to the name the developer chose.
Proofs are origin-bound A proof of human is minted only for a registered origin and posted only to that exact origin — never to *. A leaked client_id cannot harvest proofs from a page its owner does not control.
8 · Erasure

Deleting is one operation, it is available to you without asking us, and it costs a fresh face ceremony — a challenge bound to deletion and a signature by the face key — so a stolen session cannot spend it.

It removes the face template, the helper data, every wrapped key, every vault record and its search tokens, every sealed document including the encrypted bytes in object storage, every linked identifier, every grant and access token, every proof receipt, the activity log, and any developer credentials or organisations created by that identity. Object storage does not cascade from the database, so the bytes are removed first and explicitly — deleting rows before objects would leave the objects unreachable and permanent.

Afterwards the same face enrols as a new GUID with no link to the old one. Nothing is retained, so nothing can be restored. The full inventory →

9 · Where it can fail

Stated plainly, because a security page that only lists strengths is marketing.

  • Liveness is client-side. Analysis runs in the browser and reports a verdict. Server-issued single-use digits defeat replay; they do not defeat a modified client. What a modified client cannot do is produce the face-derived key, which is where the vault’s security actually rests.
  • The descriptor is the crown jewel. Anyone holding both your descriptor and your mask can recover your secret. The descriptor is transmitted for 1:N identification, so it is protected by transport security, rate limits, and the liveness gate — not by cryptography. The optional passphrase closes this gap, and is the right choice for a vault holding anything serious.
  • Faces are not revocable. A leaked password is replaced in seconds; a face is not. This is inherent to every biometric system.
  • Identical twins. A known limitation of face recognition as a class. Two people who look sufficiently alike may resolve to one GUID.
  • Lose the face, lose the vault. There is deliberately no recovery path — a reset mechanism would be a second, weaker way in. Keep independent backups of anything you cannot afford to lose.
  • A proof is evidence, not proof. It says a browser passed the ceremony for your client, within five minutes, once. It cannot say the browser was unmodified. It is enough to gate a signup or rate-limit by person; it is not enough to be the only control on a payment.
10 · Parameters
Descriptor128-D, L2-normalised (FaceNet-style embedding)
Match threshold0.45 L2 · adaptive template update below 0.35
Projections6,144 candidates, 1,920 selected
Secret128 bits, repetition code R=15, majority decode
KDFHKDF-SHA256 · PBKDF2-SHA256 310,000 iterations for the passphrase
Record encryptionAES-256-GCM, unique key per record, 96-bit random IV
SignaturesECDSA P-256 / SHA-256
Liveness challenge3 digits, 120-second TTL, single use, server-issued
SessionsHMAC-SHA256, 30 minutes, HttpOnly + Secure + SameSite=Lax
Document chunks1 MiB plaintext, AES-256-GCM per chunk, IV = 8-byte base ‖ uint32 index, AAD binds document id, index and count
Document sealECDSA P-256 / SHA-256 over the plaintext hash and timestamp
Document download token120-second TTL, single use, bound to one document and one identity
Proof tokenES256, typ: faceguid-proof+jwt, 5-minute default TTL (60–900s), single redemption
Authorization codes120-second TTL, single use, PKCE S256 required for public clients