Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.capy.sc/llms.txt

Use this file to discover all available pages before exploring further.

Capy is zero-trust: an attacker who fully compromises Capy’s service still cannot decrypt your secrets. That’s not a marketing claim - it’s a cryptographic property of the system. This page walks through what “zero trust” means here, what each side actually holds, and what the threat model does and doesn’t cover.

The two shares

Zero-trust two-share model: your machine holds the seed phrase, master key, project keys, and an inner wrap key. Capy's service holds identities, memberships, the ciphertext, and an outer wrap. Co-decrypt needs both halves.
Neither side holds a secret the other can use alone. Every meaningful operation - unlocking the master key, redeeming an invite, reconstructing a deploy key - requires the service to cooperate and the client to supply material the service has never seen.

What the client holds

  • The seed phrase - only the org owner ever sees it. Written down at org creation. Never transmitted.
  • key.enc - the master key, double-wrapped. Decrypting it requires either:
    1. Your identity (to ask the service to strip the outer wrap) plus the ability to recompute SHA256(userId || ":" || orgId) - which needs both identifiers the service has, but the hash itself is never sent, or
    2. Your seed phrase - re-derives the master key offline via PBKDF2, bypassing key.enc and the service entirely. This is the owner’s escape hatch and the one artifact that makes seed-phrase compromise require full rotation.
  • Project keys in memory - derived from the master key via HKDF. Live only for the duration of a capy run.
The service never sees plaintext versions of any of these.

What the service holds

  • Identities and memberships - who’s authenticated, who belongs to which org, which role they hold, which protected branches they can access.
  • A service-side key for the outer-wrap layer - org-scoped, so wrap material from one org can’t be used to unwrap another’s. The service uses it to strip the outer wrap of a key.enc blob the client presents during co-decrypt. The service does not store key.enc itself; those live only on developer machines.
  • Encrypted secret blobs - per project, per branch. Opaque to the service.
  • Deploy token metadata - which tokens exist, which have been revoked, which projects they map to. The project-key material is outer-wrapped and inert without the deploy-token half the customer holds.
Notably absent: plaintext secrets, master keys, project keys, invite tokens, or seed phrases.

Why a service breach is survivable

Imagine Capy’s service is fully compromised - attacker reads everything the service can read. What can they do?
  • Read encrypted secret blobs. Yes, but they’re AES-256-GCM under the project key, which we don’t have. Without the project key, ciphertext stays ciphertext.
  • Strip outer wraps on key.enc files. Yes, on any key.enc they obtain. But we don’t store key.enc - those live only on developer machines. The attacker would need to also compromise a developer’s machine to get one. Even then, the inner AES layer uses SHA256(userId || ":" || orgId) - a key the user’s machine derives but never transmits.
  • Read in-memory key material during a request. Project keys are derived on developer machines and never transit the service. During deploy decrypt the service briefly holds a derived serviceKey, but that’s useless without the customer-held PROJECT_KEY.
The only attack path to plaintext requires both a service breach AND a copy of key.enc from one of your developer machines. Compromising just the service yields ciphertext that stays ciphertext. Compromising just the identity provider grants impersonation but no key material - an attacker authenticated as you can read encrypted blobs, but can’t decrypt them without your key.enc.

Why a client breach is contained

If a developer’s laptop is fully compromised, the attacker’s access is limited to that user’s view of the system - and only until you kick them. During the breach window:
  • The attacker has the same view of Capy that the compromised user has, until you kick them. Capy doesn’t try to defend against attackers who fully control an authenticated user’s machine.
  • Treat any recent writes from the user as suspect.
Clean up .env.pre-capy.old. On first run, Capy saves a commented-out backup of your original .env to .env.pre-capy.old (gitignored) so you can recover values during the transition. Delete this file from your machines once you’ve confirmed Capy is working - it sits in plaintext on disk, and any old secrets it contains stay valid until you rotate them.
What the attacker cannot do, even with full laptop control:
  • Continue decrypting secrets after being kicked. key.enc is double-wrapped. Once kicked, the service refuses to strip the outer wrap, and key.enc becomes cryptographically inert on the attacker’s copy of the disk.
  • Derive the master key offline, unless the user was an org owner who stored the seed phrase on that laptop. For non-owners, the on-disk master key is double-wrapped and useless without the service.
Recovery:
  1. capy kick from every org the user belonged to. O(1); effective on the attacker’s next request.
  2. Rotate values the attacker may have already pulled into memory before kick. Kick is prospective, not retrospective - it doesn’t erase what was already decrypted.
  3. Rotate values the attacker may have pushed under the user’s identity.
  4. If the user was an org owner and the seed phrase was on that laptop, full rotation: new seed, new master key, re-encrypt every secret, re-invite every member.

Revocation as a first-class operation

Because decryption requires live cooperation from the service, revocation is O(1): the service simply stops cooperating with the kicked user. No re-encryption. No new master key. See Cryptography → Revocation. The only scenario where revocation alone isn’t enough is seed phrase compromise. A user with the seed phrase can derive the master key offline and never needs to ask the service for anything. If you suspect seed-phrase exfiltration, you have to rotate: new seed, new master key, re-encrypt every secret, re-invite every member.

See also

Architecture

How the CLI and the service fit together.

Cryptography

Exact client-side constructions, keys, and parameters.