Security model
What is protected, what is not, which crypto.
This page is blunt on purpose. A tool that overstates its guarantees is worse than none.
What is protected
| Adversary | Outcome |
|---|---|
| The host is breached: stolen tokens, provider CVEs, a rogue employee | They get ciphertext, the members' public keys, sizes and timestamps. They cannot forge a generation (no signing key). They can delete or roll back the vault; that is detected by the hash chain, not prevented. |
| The host is curious | It learns that a vault exists and how often it changes. Not branch names, commit ids, messages, file names, authors, code, comments or logs. |
| A stolen vault push credential | Same as a breached host: garbage is rejected (bad signature), deletion is detected. A protected branch on the host stops rewrites too. |
| A removed member | Loses access to every later generation. What they signed while in stays valid; anything they sign afterwards is refused. What they already fetched stays theirs. |
| A tampered blob, a dropped generation, a replaced manifest | verify, restore and every fetch fail loudly. secretree never appends to a chain it could not verify. |
What is not protected
- A compromised machine that holds a key. Your laptop, your runner. They can read the source directly and the key store as you. This is the trust root by design; nothing running on that machine could change it.
- Traffic analysis. The sizes of incremental generations reveal how busy you are.
- Availability. A host can refuse to serve. Keep two remotes; the format allows any number.
- Losing every key and the printed recovery kit. Nobody can help you then, including the author.
- Secrets inside the repository. They are just more plaintext to secretree; keep them out of git and out of the state archive.
- Deniability or anonymity. The host knows the vault exists and who pushes to it (by its own credentials).
Cryptography
secretree contains no cryptographic primitives of its own.
- Encryption: age, X25519 key agreement and ChaCha20-Poly1305, through the reference Go implementation
filippo.io/age. Every ciphertext is encrypted to all current recipients. - Signatures: OpenSSH signatures (
ssh-keygen -Y sign), Ed25519, namespacesecretree-v1, throughgithub.com/hiddeco/sshsigandgolang.org/x/crypto/ssh. Signatures cover ciphertext, so the chain can be verified without a decryption key. - Integrity: SHA-256 of every ciphertext and plaintext in the signed manifest; SHA-256 of the previous manifest in each one (hash chain).
- Keys are generated on the device and stored in the OS key store; on macOS they are written through
security -iso they never appear in a process listing. A printed recovery kit is the offline copy.
Format
The on-remote format is versioned and frozen: docs/vault-format.md. A vault remains restorable with git, age, ssh-keygen and shasum alone, following docs/restore-by-hand.md; the same instructions sit in every vault's plaintext README.md.
Reporting a vulnerability
Please report privately through a GitHub security advisory on the repository rather than a public issue. The security policy has the details of what is in scope.