1. What "zero-knowledge" means at Relative Health Report
When we say Relative Health Report is zero-knowledge for sensitive data, we mean exactly the following:
- Our servers store only ciphertext for every piece of clinical or family-history information you enter. Condition names, symptoms, treatments, codes, member names, dates of birth, causes of death, gene results, attachments, and AI-generated insight content are all end-to-end encrypted before they leave your device.
- We do not hold the keys that decrypt that ciphertext. The only keys that can decrypt your family's data are derived from your passphrase (which we never receive) and held briefly in your device while you are signed in.
- A Relative Health Report employee with full admin access cannot read your data. They can read the ciphertext blobs in the database and the metadata we list explicitly below, and that is all.
- A court order compelling us to disclose your data produces ciphertext. We cannot decrypt what we do not have the keys for, so we cannot be compelled to produce decrypted content.
What zero-knowledge does not mean. It does not mean we are anonymous to you — you have an account, we know your email's cryptographic hash, your subscription state, and your sign-in times. It does not mean your data is safe if your passphrase is leaked — the key that protects your data is derived from the passphrase. And it does not mean we promise to recover your data if you lose your passphrase. Recovery depends on a one-time code we show you at signup. Lose both the passphrase and the code and the data is unrecoverable. We will not have a backdoor we can use, because the same backdoor would be available to anyone who compromised our servers.
2. What Relative Health Report can see
In a true zero-knowledge system the server still sees some information by necessity. The exhaustive list of what we can see about you:
| What we see | Why |
|---|---|
| Email cryptographic hash (HMAC-SHA-256 under a server-side pepper) | Required to find your account at login |
| Account creation time, sign-in times, IP, user agent | Session management, anti-abuse, security monitoring |
| Family membership topology (which user IDs are in which family IDs) | Server enforces who can write to your family's encrypted rows |
| Per-user role in each family (owner / member / observer) | Server enforces write permissions |
| Sizes and counts of encrypted blobs | Inherent to ciphertext storage; surfaced only as storage metrics |
| Subscription tier, billing state | Required by payment processor |
| AI feature usage (token consumption per user) | Quota enforcement and billing |
| Public X25519 keys per user | Required for family-sharing invites |
| Wrapped family-key blobs per (user, family) | Required to deliver the family key to legitimate members |
Explicitly absent from this list: condition names, AI prompt content, AI response content, attachment bytes, family names, member names, dates of birth or death, ethnicities, gene results, descriptions, symptoms, treatments, screening recommendations, physician summaries, and reproductive event details. All clinical content is stored as ciphertext only.
3. What each adversary can learn
We model eight adversary classes, plus one self-inflicted failure mode. For each, here is what they would learn under this architecture.
A Relative Health Report insider with full admin access
Reads ciphertext blobs, email hashes, family membership topology, sign-in times, and AI token usage. Cannot read any clinical content — the keys are derived from passphrases that never reach our servers. The irreducible residual risk is a malicious client-side build that exfiltrates plaintext from the browser; we plan to harden this with reproducible builds and external attestation.
Infrastructure provider (Railway, Google Cloud, Cloudflare)
Same as our insiders for stored data — ciphertext and metadata only. There is one important exception: during AI inference, your decrypted family data is sent directly from your device to Google Cloud Vertex AI (which hosts Anthropic's Claude). Our servers are not on this call path. Under Vertex AI's no-train / no-log configuration, that content is not logged, not retained, and not used for training. See section 9.
Court-compelled disclosure
We can be compelled to produce ciphertext blobs, email hash, sign-in times and IPs, and family-membership topology. We cannot decrypt data you have already stored — we do not hold the keys, so no court order can compel us to produce its plaintext. One honest limit: like any software vendor, we could in principle be compelled to ship a compromised future client build (the malicious-client-build risk in Limitations). That risk is forward-looking — it cannot retroactively decrypt data already stored, and it only reaches you if you keep using a compromised build. A gag order may also prevent us from notifying you.
Database breach
An attacker who steals a copy of our PostgreSQL database gets ciphertext blobs (useless without keys), email hashes (useful only if the pepper is also exfiltrated), and membership topology. No clinical content. The pepper is stored separately from the database credentials so a single-environment compromise does not enable email enumeration.
Compromised AI provider
A compromise of Google Cloud Vertex AI or Anthropic during your inference window would expose the input and output of that one call — family member names, conditions, dates, and gene results in the prompt. It would not expose families that did not invoke AI features during the window, any historical content (no retention), or any keys. This is the intentional, contractually-bounded tradeoff. Customers who prefer to avoid it can use the deterministic insight engine path, which never invokes the LLM.
Network attacker
Nothing of substance. TLS 1.3 protects both the device-to-server and the device-to-Vertex-AI connections. Traffic analysis could reveal that you used an AI feature, not which family or what content.
Malicious family member
Someone you invited into a family has access to everything the family has shared with them, by design. The cryptography cannot prevent this — they hold the family key. Mitigations: roles (observers cannot write), and revocation with key rotation (a removed member loses access to anything created or modified after removal). What revocation cannot do: reach into the revoked member's device and erase data they already cached locally. We surface this limit in the revocation confirmation dialog so it is not a surprise.
Stolen device or hijacked session
The attacker sees everything you can see while signed in, for the lifetime of that session. They do not get the passphrase (which is not stored after session unlock) or the ability to sign in again after the session expires. Once the session ends, the keychain is wiped. Mobile keychains are hardware-backed by iOS Secure Enclave and Android Keystore, and the mobile app's local copy of your family data is itself an encrypted database whose key never leaves that hardware — extracting the app's files, or a backup of them, yields ciphertext. On web, keys that persist across page reloads are encrypted before they reach IndexedDB, under a browser-held key that script cannot export — copying the stored data out yields ciphertext. Browsers have no hardware enclave, so this protects stored data from theft; it cannot protect a page that is itself compromised.
Self-inflicted loss: lose passphrase and recovery code
Your data is unrecoverable. We cannot help. This is the unavoidable cost of true zero-knowledge: there is no master key we can use to unlock your data, so there is no path to recovery when both client-held secrets are lost. We mitigate this with strong UX at signup — the recovery code is shown once with copy + download + a checkbox + a three-word re-type confirmation that proves you read it.
4. Cryptographic primitives
We use a single, well-reviewed cryptography library (libsodium, via
libsodium-wrappers-sumo on web and
react-native-libsodium on mobile). No hand-rolled
cryptography anywhere in the codebase.
| Purpose | Algorithm |
|---|---|
| Stretching your passphrase / recovery code | Argon2id (memory-hard), MODERATE parameters: 3 ops, 256 MiB |
| Deriving subkeys from a stretched key | BLAKE2b-based KDF |
| Encrypting your data and wrapping keys | XChaCha20-Poly1305 with 24-byte random nonces |
| Delivering the family key to a new member | X25519 sealed box (anonymous-sender encryption) |
| Hashing your email for server lookup | HMAC-SHA-256 with a server-side pepper |
| Random bytes (nonces, salts, keys, recovery codes) | OS cryptographic random source via libsodium |
Every encrypted row stores a crypto_version integer so
primitives can be upgraded in the future without breaking existing
data. Decrypt failure throws — we do not have a silent fallback path
that returns garbage on a tag mismatch. Each row's associated data
(AAD) binds the ciphertext to its entity type, ID, and version, so
spliced ciphertext fails the integrity check.
5. Key hierarchy
Reading top to bottom: your passphrase decrypts your User Encryption Key, your User Encryption Key decrypts your X25519 private key, and your X25519 private key decrypts the wrapped Family Data Key that the family owner sealed to you when you joined. The Family Data Key decrypts the family's entries, members, and records.
Passphrase --Argon2id--> stretched key --KDF--> Master Key (32 bytes)
\-KDF--> Auth Tag (server-side
peppered + hashed;
plaintext never stored)
Recovery code --Argon2id--> stretched key --KDF--> Recovery Key
User Encryption Key (UEK, 32 bytes, generated once at signup):
- stored as AEAD(UEK, key = Master Key)
- stored as AEAD(UEK, key = Recovery Key) <- parallel recovery path
X25519 keypair:
- public key: plaintext on server
- secret key: AEAD(secret key, key = UEK)
Family Data Key (FDK, generated when a family is created):
- For each member:
wrapped FDK = crypto_box_seal(FDK, member's X25519 public key) The recovery code path is independent. If you use it, you unlock the same User Encryption Key as the passphrase path, and you are immediately prompted to set a new passphrase — which re-wraps your User Encryption Key under the new key.
6. Authentication
Login is a two-round protocol designed so the server never sees your passphrase or any value from which it could derive your keys.
At signup, your device stretches the passphrase with Argon2id, splits the result into a Master Key and an Auth Tag, generates your User Encryption Key and X25519 keypair on-device, wraps the User Encryption Key under the Master Key and again under the Recovery Key (independent paths), and POSTs the wrapped blobs and the Auth Tag to the server. The server peppers the Auth Tag, hashes it with Argon2id, and stores only the hash. The plaintext Auth Tag is never persisted.
At login, the client fetches your KDF salt from the server, re-derives the Master Key and Auth Tag on your device, sends the Auth Tag to the server, and on a successful peppered-hash match the server returns your wrapped User Encryption Key and wrapped X25519 secret key. The client unwraps them and stores them in a per-session keychain. On iOS and Android that keychain is hardware-backed (Secure Enclave / Android Keystore). On web it is IndexedDB, with every entry encrypted under a key the browser holds as non-extractable — what reaches disk is ciphertext, not raw keys. On logout or session expiry, the keychain is wiped.
7. Recovery model
We use a one-time recovery code shown at signup — the same pattern used by Signal, Bitwarden, and 1Password's emergency-access feature.
- 256 bits of cryptographic randomness encoded as 24 words from a curated 2048-word list. We deliberately do not use the BIP39 wordlist (the Bitcoin wallet standard) to avoid confusion with crypto wallet seed phrases.
- Shown once at signup in monospace, 4 rows × 6 words. Copy + download buttons. To proceed, you must (a) tick "I have saved this code" and (b) re-type three randomly selected words from your code — enough friction that the screen cannot be cleared accidentally.
- Lose the passphrase, keep the recovery code: you use the recovery code to unlock, then set a new passphrase. Existing data remains readable.
- Lose the recovery code, keep the passphrase: sign in normally; regenerate the recovery code from the settings page (requires passphrase re-entry).
- Lose both: your data is unrecoverable. We cannot help.
9. AI insight architecture
AI-generated family insights and physician summaries use Anthropic's Claude — a current-generation Sonnet-class model — hosted by Google Cloud Vertex AI under its no-train / no-log data-governance configuration. (We are establishing a HIPAA Business Associate Agreement with Google Cloud before serving regulated customers — see section 10.)
Our servers are not on the AI call path. When you request an insight:
- Your client requests a short-lived Google Cloud IAM token from our backend. The token asserts a dedicated service account whose only permission is Vertex AI model invocation in our project — it cannot read or modify any other cloud resource — and it expires within ten minutes. Our backend logs only the user ID, project, region, model, and remaining quota — no input content, no output content.
-
Your client decrypts the family data locally and sends the decrypted
data directly to
*-aiplatform.googleapis.comwith the IAM token. Our servers do not see this request. - Vertex AI runs the inference. Under Vertex AI's no-retention posture, the input and output are not logged, not retained, and not used for training.
- Your client receives the result, encrypts it under the appropriate key, and stores the encrypted result in our database. Your client reports token usage back for quota accounting — token counts and operation type only, no content.
Generic, anonymised condition lookups (e.g., looking up typical symptoms of "type 2 diabetes" to auto-populate an entry form) use a public, anonymous, condition-name-keyed cache that retains no user or family identifier. The privacy posture is equivalent to a MedlinePlus search.
Customers who reject any inference-time provider exposure can use the deterministic insight engine, which runs entirely on your device — it implements USPSTF, NCCN, and ACMG guideline matching without invoking the LLM, and produces a useful subset of what the LLM path produces.
For the product-level account of which guidelines feed these insights, see how we build our insights; the research behind them is on the science page.
10. Limitations and what is out of scope
We document these explicitly so customers can understand what we do not claim:
- Hardware-attested inference (TEE). Today we rely on Vertex AI's zero-data-retention contractual posture rather than on cryptographic guarantees that Google cannot see inference inputs. We may revisit this with a confidential-computing approach (e.g. Google Cloud Confidential Space) if customer demand justifies the operational cost.
- HIPAA Business Associate Agreement. Our AI inference posture today rests on Vertex AI's contractual no-train / no-log data-governance configuration, not on an executed BAA. We are establishing a Business Associate Agreement with Google Cloud covering Vertex AI before we serve HIPAA-regulated customers.
- Social recovery (Shamir secret sharing). We currently use a single recovery code. We may add a social-recovery option if usability research finds the single-code path too high-friction.
- Bring-your-own LLM provider. Standard customers use the issued-token path. Enterprise tier may offer BYO-provider.
- Federated insight generation across families. Would require additional cryptography (private set intersection or homomorphic primitives) that we do not yet support.
- WebAuthn / passkey-based key derivation. We currently use passphrase + recovery code. We may add WebAuthn PRF as an alternative key-derivation source.
- Resistance to a malicious client-side build. If we pushed a compromised client bundle, the protections in this document would not hold. We mitigate with code review and a strict Content-Security-Policy, and plan to add reproducible builds with external attestation.
- Anonymity from us. You have an account with an email, a billing identity, and an IP. Zero-knowledge protects the content of what you store, not the fact that you have an account.
- Resistance to a coerced user. If you are compelled to reveal your passphrase, the cryptography does not protect you. You hold the key.
Third-party review. We plan an independent cryptographic design review by a recognised firm (Trail of Bits, NCC Group, or Cure53) before any enterprise-tier or regulated-customer launch. We will publish the review's findings here when it completes.
11. Reporting a vulnerability
Email security@relativehealthreport.com. We acknowledge within 72 hours and disclose findings publicly after a fix has shipped, with attribution if the reporter wants it. We do not currently run a paid bug bounty.