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.
$ 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
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.
Each scope has its own key. Adding a member wraps the key to their card; removing one rotates it. Cryptographic, not policy-enforced.
The server signs every tree head. With configured witnesses or clients comparing notes, divergent histories become publishable evidence.
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.
curl -fsSL https://fd0.sh/install | sh
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
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
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
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)
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
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
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
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.
$ fd0 key add laptop ✓ ed25519 (scope: personal) ssh-ed25519 AAAAC3NzaC1lZD… laptop@fd0
$ fd0 ssh add prod-db \ app@db.internal \ --jump bastion \ --key laptop \ --scope work ✓ host added ✓ ~/.ssh/fd0.conf rendered
$ ssh prod-db $ ssh prod-db "uname -a" $ scp dump.sql prod-db:/tmp/ $ git push origin main
$ 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.
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.
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.