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.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.
The two shares
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:- 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 - Your seed phrase - re-derives the master key offline via PBKDF2, bypassing
key.encand the service entirely. This is the owner’s escape hatch and the one artifact that makes seed-phrase compromise require full rotation.
- Your identity (to ask the service to strip the outer wrap) plus the ability to recompute
- Project keys in memory - derived from the master key via HKDF. Live only for the duration of a
capyrun.
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.encblob the client presents during co-decrypt. The service does not storekey.encitself; 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.
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.encfiles. Yes, on anykey.encthey obtain. But we don’t storekey.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 usesSHA256(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-heldPROJECT_KEY.
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.- Continue decrypting secrets after being kicked.
key.encis double-wrapped. Once kicked, the service refuses to strip the outer wrap, andkey.encbecomes 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.
capy kickfrom every org the user belonged to. O(1); effective on the attacker’s next request.- 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.
- Rotate values the attacker may have pushed under the user’s identity.
- 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.