Security guarantees: overview
The short answer, threat model, destinations, and handshake.
Published from docs/security-guarantees/overview.md (GitHub)
Part of the security guarantees.
Short Answer
Given an honest, uncompromised client, the system can detect forged or tampered signed group and organization policy state when the signer identity key is authentic and the client receives the referenced policy bundle.
The system does not provide a universal guarantee that an honest client can detect every compromised-server behavior. A compromised server can still deny service, omit state, replay older valid state when the client has no checkpoint, show split views to different clients without gossip or witnessing, or substitute identity keys on first contact with a user whose identity link the client has not verified.
The confidentiality boundary is:
- The server cannot decrypt encrypted content without recipient private keys or unwrapped DEKs.
- The server cannot forge a valid signed group or organization policy state unless it controls an authorized policy signer key, can substitute the signer identity key trusted by the client, or can make the client accept invalid policy state.
- The server cannot make an honest client accept object grants or recipient targets that are not justified by signed access manifests and verified principal policy heads.
Threat Model
The security properties assume:
- The client code is honest and runs the implemented validation paths.
- User signing and encapsulation private keys are not compromised.
- Cryptographic primitives hold.
- Clients do not intentionally ignore policy validation failures.
- Server-side API and database code may be malicious, compromised, stale, or inconsistent.
The properties are relative to the authenticity of registered
user identity keys. The client fetches
each complete user identity through one strict gateway, checks both key
fingerprints, and durably pins the
exact bundle by trust domain
and userId. The reverse signing-fingerprint-to-user binding is also unique
within that trust domain and checked in the same durable transaction. A new
session without restored acknowledgments cannot bind a known signing identity to
a different user. Later substitutions hard-fail, and policy verification also
checks the signing fingerprint in signed state. Verifying signed state pins a
referenced user on first sight (observed); only an independently exchanged
identity link (link) or the device’s own keys (self) admit a user as a
contact, roster or group member, or share recipient. Neither mechanism proves
global currentness.
Root And System Destinations
Directory listing fields cannot establish a Trash, Contacts, or root role.
The SDK verifies the container projection before
adopting those destinations, then takes the slot and parent edge from its signed
manifest. Local pre-login content merges only into the session’s root id in the
expected organization, and only if
that root’s epoch-1 container.create was signed by the session user (the login
response naming it is unsigned; every acknowledged organization was created by
the user). That creator check holds at the reconciliation boundary itself, from
the role remote hydration verified and cached (a local refresh never fetches; an
uncached role leaves the merge pending), so a root this device hydrated earlier
as another user’s shared container is refused as a signer_mismatch incident and
never merged, whichever path reaches it. A later login may repeat an
acknowledged root or report it purged; a different root id is refused as an
incident. The default organization is not bound, since a purged personal one is
legitimately replaced. Only a root administrator may create a signed system
slot. Additional grantees are allowed: sharing a legitimate Trash must not
disable deletion. A create acknowledgement persists the client-signed slot and
refuses a differing echo. The app selects a foreign organization’s Trash only by
a slot the viewer can derive; another identity’s Trash is reported unavailable,
never matched by name.
Every folder’s metadata document id comes from its signed manifest, never from
the listing. A held folder keeps its organization and metadata document unless
the session user’s own signed container.create re-homed it, as purged
organization recovery does under existing ids, or the held organization’s
pinned founder created the destination and signed a continuous
replacement chain binding its
organization and root genesis. An organization a folder left is recorded and
never binds it again. Other conflicts are refused as incidents before pins or
role caching, keeping the row, queued edits and checkpoints; an unbound folder
binds only to its own user’s create. Transactions recheck the durable binding.
Recovery Key Disclosure
Identity Manager’s recovery QR contains the plaintext recovery phrase. It is generated and decoded locally, with the same acknowledgement as revealing the phrase. Scanning stages a validated phrase for the existing restore/login flow. Validation proves the phrase’s format and checksum, not its ownership; users must scan their own backup, since restoring another person’s QR selects that person’s identity. Revealed keys are hidden and staged restore phrases are cleared on page hiding or backgrounding, but this cannot prevent screenshots or photographs. A native camera capture uses a temporary file that the adapter deletes before returning the photo for decoding; deletion failures are reported and the capture is rejected. Any retained image of the QR grants the same identity access as the recovery phrase itself.
Protocol Handshake
The access and policy handshake has these layers:
- User registration stores
the user’s signing key, encapsulation key, personal organization,
reserved
Adminsgroup, reservedMembersgroup, root container, root metadata state, optional roster-profile bootstrap material, and initial signed principal policies inside one registration transaction. - The initial
Adminspolicy must project the registering user as the sole admin. The initialMemberspolicy must project the registering user as an admin. The initial organization policy must be version1, must target the new organization, must be signed by the registering user, must project only the registering user as admin, and its version-2 authority descriptor must commit the exact initialAdminsandMemberspolicy heads. - Later group and organization policy states are signed principal states. The server verifies the signature, state hash, projection and envelope roots, encrypted payload hash, member count, previous-state link, and admin-signer rule before storage.
- The signed state commits the exact direct-member envelope set. State, payload, projection, and envelopes are accepted atomically; the API rejects missing, extra, altered, or fingerprint-mismatched envelopes.
- Access changes are represented as signed access events and derived access manifests. Manifests bind the object, organization, epoch, predecessor hash, event hash, structural hash, grant root, referenced principal heads, and key-target hash.
- Clients verify historical policy proofs, including retained membership/grant metadata for authorized object readers, and fetch and cache verified full current policies for key unwrapping.
- App clients unwrap group or organization addressed object envelopes only through verified cached principal policies, valid member envelopes, and signed access manifests.
- Writes commit to the verified access manifest hash and derived recipient target hash so stale or forged access views fail verification.
- Clients persist exact full-bundle identity pins and monotonic checkpoints for principal policy heads and access manifest heads.
- Terminal trust-boundary verification failures are appended to a local security-incident ledger before the workflow rethrows them. The ledger is not part of remote-state reset and contains typed codes and object references, not exception text or decrypted data.