Concepts

Vaults, generations, keys, the mirror, the restore proof.

Five ideas explain everything secretree does.

Vault

A vault is a git repository (or, later, any key-value store) on a host that only ever sees ciphertext. It holds the backups and the sync history of one or more source repositories. On the remote it looks like this:

README.md                              plaintext restore instructions
vault.json, vault.json.sig             public keys of members, signed
repos/<repo-id>/NNNNNN.manifest.age    encrypted, signed manifest of one generation
repos/<repo-id>/NNNNNN.bundle.age      encrypted git bundle
repos/<repo-id>/NNNNNN.state.tar.age   encrypted out-of-repo state (optional)
repos/<repo-id>/ledger/NNNNNN.json.age disclosure ledger entries

Every write is one new commit on the vault's branch. secretree never force-pushes; configure the branch as protected on the host and a stolen push credential cannot rewrite history either.

Generations and the chain

A generation is one snapshot of a repository: a full git bundle, or an incremental bundle that builds on the previous generation, plus a manifest listing every ref, every file's hash, and the SHA-256 of the previous manifest. That last field makes the generations a hash chain: a host that drops or replaces a generation breaks the chain, and verify reports it.

A new full generation is written automatically every 20 incrementals, when the incrementals outgrow the last full, when a member is added or removed, or on backup --full.

Pushes through the helper and secretree backup produce the same kind of generation; the backup additionally carries the state archive and runs the restore proof.

Keys

Two keys per device, both in the OS key store:

Key Purpose Who holds one
age identity (X25519) decrypt manifests, bundles, state, ledger every member and agent
Ed25519 signing key (OpenSSH format) sign manifests, vault.json, collaboration events every device allowed to write

vault.json lists the recipients (age public keys) and the allowed signers (OpenSSH allowed_signers lines). Every ciphertext is encrypted to all recipients at the time of writing. Adding a member takes effect for new generations; member add writes a full one immediately so the newcomer has the whole history. Removing a member moves their signer into revoked_signers with a cut-off generation: what they signed while in stays valid, anything later is refused.

The printed recovery kit holds the age identity, the signing key and the signing key's fingerprint. The fingerprint is the trust root on a fresh machine: vault.json is verified against it before anything else is believed.

The mirror

The remote helper keeps a plaintext bare repository under .git/secretree/mirror that represents the vault's logical state. A fetch syncs the chain into the mirror and lets git fetch from it; a push lets git push into the mirror (so fast-forward rules are git's own) and then appends one generation to the vault. When two people push at once, the host rejects the second vault write, the mirror is rolled back, and git shows the familiar "fetch first". The mirror is derived state: if it ever disagrees with the chain it is rebuilt.

The restore proof

A backup that was never restored is a hope. After every secretree backup, and on secretree verify, secretree clones the vault afresh from the remote into a private directory, verifies every signature and the whole hash chain, rebuilds the repository from the bundles, compares every ref with the source, runs git fsck, and decrypts the state archive. Until that is green it does not say "done", and status shows the last generation that was proven.

Collaboration data

Pull requests, comments, reviews, checks and deployments are signed JSON events on the ref refs/secretree/collab. The helper syncs that ref like any branch, so it is encrypted inside bundles and never appears on the remote in the clear. Event files have unique names, so two devices writing at once merge as a plain union. Policy (.secretree/policy.json: required approvals, required checks) is enforced by the merging client and verifiable by every other client, because the approvals and checks it counts are signed.

What the host learns

That a vault exists, its random ids, how many generations it has and when they were written, the ciphertext sizes, and the members' public keys. Not branch names, commit ids, messages, file names, authors, code, comments or logs. Traffic analysis (activity rhythm from sizes and timing) is not mitigated.