Apache-2.0 · client-side encrypted

Secrets you keep,
not secrets you trust.

One zero-knowledge vault for the credentials you'd otherwise scatter across .ssh, kubeconfig, Talos config, and someone's Notion page. The server stores ciphertext and signed events only. Sharing membership rotates per-scope keys atomically. Configured witnesses can detect server-side forks.

Get started →Source on GitHubNo telemetry. Ciphertext only on either backend.
example — fd0 sync
$ fd0 sync
 POST /v1/sync  scope=work  push=3
 200 OK  pull=0  sth=verified@43
✓ chain advanced (seq=7)
✓ STH verified

$ fd0 get DEPLOY_KEY
ghp_xxxxxxxxxxxxxxxxxxxx
Cryptography
Ed25519 · X25519
XChaCha20-Poly1305 AEAD
Transparency
RFC 6962 + witness
independent cosigner
Releases
signed installer
cosign-verifiable manifest
Source
Apache-2.0
github.com/ValentinKolb/fd0.sh
Three guarantees

Built into the protocol, not the deployment.

Encryption
The server cannot decrypt.

Every secret is sealed by the client before it leaves the device. The server stores ciphertext and signed event metadata — an operator with full database access reads no secret values or secret names.

Membership
Add or remove members atomically.

Each scope has its own key. Adding a member wraps the key to their card; removing one rotates it. Cryptographic, not policy-enforced.

Transparency
Forks are detectable.

The server signs every tree head. With configured witnesses or clients comparing notes, divergent histories become publishable evidence.

Install

Install the client. Pick a backend.

Same client either way. The main difference is who runs the server. Client-side encryption and scope membership work the same; witness enforcement depends on your client config.

01 · Install the client
curl -fsSL https://fd0.sh/install | sh
Installs fd0 and fd0-agent to ~/.local/bin. Verifies the release manifest when cosign is installed.
02 · Pick a backend
Self-host
Run fd0-server yourself.

Download the minimal compose file, run one fd0-server on localhost, and put your own TLS terminator in front. Pin image tags for production.

mkdir fd0-server && cd fd0-server
curl -fsSLO https://fd0.sh/files/compose.yml
umask 077
printf 'METRICS_TOKEN=%s\n' "$(openssl rand -hex 32)" > .env
case "$(uname -m)" in arm64|aarch64) printf 'FD0_SERVER_IMAGE=%s\n' 'ghcr.io/valentinkolb/fd0-server:latest-arm64' >> .env ;; esac
docker compose up -d
One primary per client — clients set [sync].server. For redundancy run a standby with FD0_REPLICATE_FROM=<primary> (the primary lists it in FD0_PEERS); it mirrors the primary read-only for disaster recovery.
Hosted at fd0.sh
Use the managed instance.

A managed primary plus a disaster-recovery backup operated by Kolb Antik GmbH in Germany. Same ciphertext-only contract: the operator cannot decrypt secrets.

# ~/.fd0/config.toml — this is the default
[sync]
server    = "https://api.fd0.sh"
on_unlock = true
The production runbook linked from /docs/server covers hosting topology, backups, metrics, witnesses, and key rotation.
Quickstart

Four steps.

01
Generate identity, seal the vault

fd0 init writes a fresh Ed25519 keypair to ~/.fd0/vault.enc, sealed under a passphrase. The passphrase only protects the file at rest — it never reaches the server.

$ fd0 init
Choose a passphrase: ********
✓ identity created (upIamMlsgn…)
✓ vault written to ~/.fd0/vault.enc
02
Unlock once per session

The agent holds the unlocked identity in memory. The CLI signs and decrypts through it without re-prompting until you run fd0 lock.

$ fd0 unlock
Passphrase: ********
✓ agent started
✓ vault unlocked (passphrase)
03
Create a scope, store a secret

A scope is a group of secrets with its own encryption key. Adding or removing members rotates that key.

$ fd0 scope create --label work
$ fd0 set DEPLOY_KEY "ghp_xxxxxxxxxxxxxxxxxxxx" --scope work
$ fd0 get DEPLOY_KEY
ghp_xxxxxxxxxxxxxxxxxxxx
04
Sync — local goes to the server

fd0 sync is the explicit network command. The agent can also sync after unlock when on_unlock is enabled.

$ fd0 sync
 POST /v1/sync  scope=work  push=3 events
 200 OK         pull=0      sth=verified@tree_size=43
✓ local chain updated
✓ STH verified against the pinned server key
Compare

Same shape, different trust model.

vs. Bitwarden / Vaultwarden
Same use case. fd0's server cannot decrypt under any operator access — cryptographic, not policy.
vs. 1Password Teams
Same team-sharing model. fd0 keys live on devices you control; the server is yours or fd0.sh.
vs. 1Password SSH / Bitwarden SSH
Same per-sign agent protocol pattern. fd0 adds team-scoped keys — sharing an SSH key is the same op as sharing a password, cryptographic at scope membership.
vs. pass (Git + GPG)
fd0 has cryptographic membership built in: add/remove rotates the per-scope key atomically.
vs. HashiCorp Vault
Different scope. fd0 is a human-facing secret store; Vault is a policy engine for service-to-service auth.
Architecture

Four binaries. Keys stay on your device.

The client and the agent run locally. After unlock, private key material stays in the agent and is never written to disk in plaintext. Standard tools (ssh, git, scp) attach through the ssh-agent socket. The server holds ciphertext and signed events; the witness holds signed tree heads. Neither holds secret keys.

consumers           your device           server                  observer
┌────────────────┐  ┌─────────────────┐   ┌────────────────────┐   ┌────────────────────┐
│ ssh, scp,      │  │ fd0  (CLI)      │   │ fd0-server         │   │ fd0-witness        │
│ git, rsync,    │ ─│ fd0-agent       │ ─▶│ ciphertext +       │ ─▶│ cosigns verified   │
│ kubectl …      │ ◀│   unlocked keys │   │ signed events      │ ✓ │ archives forks     │
│                │  │   vault.enc     │   │                    │   │ independent host   │
└────────────────┘  └─────────────────┘   └────────────────────┘   └────────────────────┘
   ssh-agent         single primary (api.fd0.sh) · DR backup mirrors it read-only
   protocol
standard protocols on both sides · zero-knowledge contract end-to-end
Built on top — fd0 ssh

SSH keys + host inventory, scope-shared, zero on disk.

Keys are generated inside fd0-agent and served via the standard ssh-agent protocol. Hosts are structured entries that render to a regular ~/.ssh/fd0.conf you Include from your own config. Native SSH features (jump hosts, agent forwarding, port forwarding) all work because we are just an agent. Add a teammate to the scope and they get the same SSH access on their next sync — cryptographic, not policy.

01 · Generate a key
$ fd0 key add laptop
✓ ed25519 (scope: personal)

ssh-ed25519 AAAAC3NzaC1lZD…
laptop@fd0
02 · Add a host
$ fd0 ssh add prod-db \
    app@db.internal \
    --jump bastion \
    --key laptop \
    --scope work

✓ host added
✓ ~/.ssh/fd0.conf rendered
03 · Use native ssh
$ ssh prod-db
$ ssh prod-db "uname -a"
$ scp dump.sql prod-db:/tmp/
$ git push origin main
For your team
$ fd0 scope add-member bob --scope work
$ fd0 sync

Bob's next sync gives him the key, the host, and the tags. fd0 scope remove-member rotates the per-scope key — his next sync drops access.

Standard ssh-agent protocol

fd0-agent talks the same protocol as OpenSSH ssh-agent. ssh, git, scp, rsync, VS Code Remote — anything that respects SSH_AUTH_SOCKworks. fd0 never writes anything to the remote server.

Specified, not implied.

Docs cover the common workflows. fd0 --help covers the full command surface. The specification covers the wire format, cryptographic constructions, on-disk format, transparency log, and threat model.