> ## Documentation Index
> Fetch the complete documentation index at: https://capy.sc/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Zero-Trust Secrets Management: How Capy Works

> Why neither the Capy client nor the service can decrypt alone. Two-share model: master key on client, outer wrap on service. Breach yields only ciphertext.

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

<Frame>
  <img src="https://mintcdn.com/capy-c95c79c0/N7qpVCkQCaY3Lo-w/images/diagrams/01-trust.svg?fit=max&auto=format&n=N7qpVCkQCaY3Lo-w&q=85&s=92317b2d9e087280ef27d88c472bdd0f" className="block dark:hidden" alt="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." width="368" height="192" data-path="images/diagrams/01-trust.svg" />

  <img src="https://mintcdn.com/capy-c95c79c0/N7qpVCkQCaY3Lo-w/images/diagrams/01-trust-dark.svg?fit=max&auto=format&n=N7qpVCkQCaY3Lo-w&q=85&s=0fe4c161d6749e8f4b355992fa5c9d11" className="hidden dark:block" alt="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." width="368" height="192" data-path="images/diagrams/01-trust-dark.svg" />
</Frame>

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.

<Note>
  **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.
</Note>

**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](/internals/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

<Columns cols={2}>
  <Card title="Architecture" icon="sitemap" href="/internals/architecture" horizontal>
    How the CLI and the service fit together.
  </Card>

  <Card title="Cryptography" icon="lock" href="/internals/cryptography" horizontal>
    Exact client-side constructions, keys, and parameters.
  </Card>
</Columns>
