// docs

threat model

CozyLabs’s threat model is maintained as a living map from each security objective to the tests that prove it: a meta-test parses the map and fails CI if an objective loses its test. This page is the prose version.

adversaries considered

The relay itself. The defining choice: the server is in the threat model. A compromised or malicious relay can observe routing metadata, reorder or withhold events, and refuse service. It cannot read committed or at-rest content (SO-1), forge attribution (SO-3: clients verify signatures independently), steal keys it never had (SO-2), or grant itself management powers (SO-5: proofs are signed by human keys). One deliberate exception: while a live-chat turn is in flight, the relay process holds the agent’s draft response in plaintext and fans it to authorised collab members (ADR-0030). That draft is bounded in memory, never written to Postgres or logs, and cleared when the turn ends. Only the final commit is sealed.

A database thief. Gets sealed keystores, ciphertext events, wrapped CKs, artifact refs. Everything useful is sealed to keys that were never in the database.

A login-password cracker. Gets a session: metadata-visible, content-blind, mutation-incapable. The sealed keystore can be fetched (that’s the new-device restore path, which by construction can’t demand a management proof), but it’s sealed under the encryption passphrase’s Argon2id-MODERATE derivation. This is why a passphrase strength floor is treated as load-bearing, not cosmetic.

A fellow member. Holds the CK, reads channel content; that’s what membership means. But direct messages are sealed per-pair to the two participants’ X25519 keys, not under the CK: a third member’s decrypt fails at the crypto layer, asserted by test (ADR-0023).

A compromised agent. Has its own capabilities and nothing else. No management surface (SO-5), no credentials: the broker resolves secrets on its behalf and scrubs every credential field value from results and errors (SO-9). Revocation rotates the CK away from it (SO-7).

An internet attacker on a public deployment. Faces Cloudflare Access (identity or service token) before any CozyLabs surface, a single public origin, and a data plane (Postgres, OpenBao) with no published ports at all under the tunnel shape (SO-6, ADR-0014/0015). Per-account lockouts throttle brute force; rotating JWT_SIGNING_KEY is the break-glass that kills every session and agent token at once.

the supply chain

Release artifacts are Ed25519-signed into a RELEASE.json manifest with a verify path, built from a committed, frozen lockfile (SO-8).

accepted residual risk

Named, documented, and deliberate:

  • Routing-metadata leakage. The relay learns {collabId, actorId, seq, kind, ts, opaque refs} per event: the minimum needed to order and fan out. Padding and metadata-hiding are deferred. A security test asserts this is the only plaintext that crosses.
  • Past content after revocation. Crypto cannot retroactively revoke delivered plaintext; SO-7 protects future content only.
  • Key loss is data loss. No passphrase + no recovery code = unrecoverable, because operator-side recovery would mean the operator holds your keys. The product refuses.
  • Beta is single-relay. Live fan-out is in-process (ADR-0016); availability, not confidentiality, is the cost of a relay outage.
  • Server-readable live-chat drafts. While a turn is in flight, the relay process holds the agent’s draft response in plaintext: token-stream deltas, tool-call args, and tool-call output/error. The draft is bounded (capped at 256 KB and 500 blocks per draft, 200 concurrent sessions), never persisted to Postgres or logs, and cleared on turn end or commit. This is a deliberate carve-out from SO-1 (ADR-0030): we considered per-frame sealing to a per-session key and deferred it. Only committed messages are sealed and zero-knowledge at rest. DM drafts are further scoped to the two parties of the pair, not broadcast to all collab members (ADR-0045).