docs / security
Threat model and cryptography
What botdb protects, what relays and folder hosts can see and do, and the cryptographic design behind it.
Summary
- Remotes (relays and shared folders) store only encrypted, padded data. They can’t read it or forge changes.
- A relay sees metadata: database ids, replica ids and sequence numbers, padded sizes, timing, client IP addresses and public keys.
- A relay can withhold or delay batches. Withholding shows up as gaps in per-replica sequence numbers.
- Local copies are plain SQLite files: use OS disk encryption. The vault key lives in the OS keychain on macOS and Windows, and in an owner-only file elsewhere.
- Removing a member requires
botdb rotate-key. They keep what they already synced. - Read-only access is enforced end to end: every change is signed with the database’s write key, which read-only invites don’t contain, and every replica checks the signature.
Assets and trust
| Party | Trusted with |
|---|---|
| Your devices | Everything: plaintext data and keys. |
| Holders of your phrase | Everything: the phrase decrypts the vault catalog, which holds every key. |
| Invited members | Plaintext of that one database, including history. Writes only with a read-write invite. |
| Relays and folder hosts | Availability only. They hold ciphertext. |
| The network | Nothing. Use TLS for relays. |
What a relay can see
- Database ids and the public keys registered for them
- Replica ids, sequence numbers and key epochs of batches
- Padded sizes of batches and snapshots
- When each client reads and writes, and from which IP address
- The registration token, sent when a client registers a database (on botdb cloud, your account token, which links your databases to one account)
A folder host (iCloud, Dropbox, and so on) sees the equivalent: file names (database ids, replica ids, sequence numbers and epochs), padded file sizes, modification times, and each snapshot’s .vec file, which lists in plaintext the sequence numbers it covers per replica.
What a relay can and can’t do
| Action | Possible? | Notes |
|---|---|---|
| Read data | No | Batches and snapshots are encrypted with keys the relay never sees. |
| Modify, forge or re-label a batch | No | Authenticated encryption with the database id, replica id, sequence number and epoch as associated data. |
| Replay a batch into another database or position | No | The associated data wouldn’t match. Re-delivering the same batch has no effect. |
| Accept writes from read-only members | No | Pushes require a signature from the write key, which read-only invites don’t contain. |
Accept writes from members removed by rotate-key |
No | The old keys are no longer registered; their requests are rejected. |
| Withhold or delay batches | Yes | Gaps in a replica’s sequence numbers are detectable. |
| Delete everything | Yes | Every device has a full copy and pushes it again on the next sync. |
| Correlate activity | Yes | Timing, sizes and IP addresses are visible. Use a VPN or Tor if that matters. |
Shared folders
A shared folder has no request signing, but that doesn’t widen who can write:
- Every batch and snapshot carries an Ed25519 signature from the write key, and replicas verify it before applying anything. Members with a read-only invite, and anyone else with access to the folder, can’t add changes.
- After
rotate-key, replicas that have the new key refuse anything sealed under an older epoch, so a removed member can’t keep writing with the old key. - Anyone with access to the folder can still delete or withhold files. Every device keeps a full copy and re-uploads what’s missing.
botdb cloud
botdb cloud is a relay like any other, run by us on Cloudflare. Everything above applies to it: it holds the same padded ciphertext and metadata a self-hosted relay would. In addition it keeps an account per vault (a random id, the hash of its token, the plan and an optional email) and which database ids belong to which account, to enforce plan limits. It never receives your recovery phrase, database keys or plaintext. If you’d rather not trust anyone with metadata, self-host a relay or sync through a folder; you can switch at any time with botdb remote add.
Local storage
Local copies are ordinary SQLite files in ~/.botdb/replicas/, readable by any process running as your user. botdb doesn’t encrypt them itself; protect them with full-disk encryption (FileVault, BitLocker, LUKS). On Unix the home directory is created with mode 0700 and config.json with 0600.
The vault key is stored in the OS keychain on macOS and Windows, or in ~/.botdb/vault.key (mode 0600) on other platforms, with BOTDB_KEYSTORE=file, or when writing to the keychain fails. With BOTDB_PHRASE, no key is stored; treat the environment of such machines as holding your phrase.
Automatic local snapshots (~/.botdb/backups/) are encrypted with each database’s own key.
Members and revocation
An invite contains the database’s keys. Once shared, it can’t be taken back. botdb rotate-key <db> starts a new key epoch:
- Each remote switches to the new keys and drops data from older epochs. Relays reject requests made with the old keys.
- botdb re-uploads everything under the new key.
- Your devices receive the new key through the vault catalog.
- Members you keep need a new invite.
- A removed member keeps everything they synced before the rotation.
Out of scope
- A compromised device, or malware running as your user. It can read local files and the keychain.
- Hiding metadata: that you use botdb, how many databases you have, how often you write, and roughly how much.
- Clock manipulation on your own devices. A device whose clock runs ahead wins last-writer-wins conflicts.
Cryptography
Primitives
| Purpose | Algorithm |
|---|---|
| Encryption | XChaCha20-Poly1305 with random 24-byte nonces |
| Key derivation | HKDF-SHA256, salt botdb/v1 |
| Request signing | Ed25519 (strict verification) |
| Change signing | Ed25519 over every sealed batch and snapshot |
| Size hiding | PADMÉ padding |
| Recovery phrase | BIP39, 24 words (256 bits) |
| Hashing | SHA-256 |
Keys
recovery phrase (24 words)
└─ master secret (256 bits)
├─ catalog secret HKDF "botdb/v1/catalog/{id,key,write}"
│ └─ vault catalog: every database's secret and remotes
└─ backup key HKDF "botdb/v1/backup" archives (botdb backup create)
database secret (random, per database, per epoch)
├─ root key
│ ├─ data key HKDF "botdb/v1/data" batches, snapshots, local snapshot files
│ └─ read signing key HKDF "botdb/v1/read-auth" Ed25519
└─ write secret (omitted in read-only invites)
└─ write signing key HKDF "botdb/v1/write-auth" Ed25519: relay writes and change signatures
(its public key is in every invite, so read-only members can verify changes)
The catalog’s id and keys are derived from the master secret, so a new machine with only the phrase can find and decrypt it on a remote.
rotate-key generates a new root key and write secret. Root keys of earlier epochs are kept (and included in invites) so older history can still be decrypted locally, but replicas refuse new data sealed under an older epoch.
Encryption and binding
Every batch is padded, then encrypted with the data key for its epoch, then signed with the write key. What leaves the device is signature || nonce || ciphertext, where the signature covers "botdb/v1/sealed-sig" ‖ length of the associated data ‖ associated data ‖ nonce || ciphertext. Snapshots on remotes are sealed the same way. The associated data binds the ciphertext to its context:
| Object | Key | Associated data |
|---|---|---|
| Batch | data key | "botdb/v1/batch" ‖ database id ‖ replica id ‖ sequence ‖ epoch |
| Snapshot on a remote | data key | "botdb/v1/snapshot" ‖ database id ‖ epoch |
| Local snapshot file | data key | "botdb/v1/backup" ‖ database id |
| Archive | backup key | "botdb/v1/archive" |
Decryption fails if any part of the context differs, so a batch can’t be moved to another database, replica, position or epoch.
Padding
Plaintexts are terminated with a 0x80 byte and zero-padded to a PADMÉ length before encryption. PADMÉ leaks at most O(log log n) bits of the length and adds at most about 12% overhead.
Request signing
Relay requests are signed with Ed25519 over the method, path and query, a timestamp and the SHA-256 of the body. The relay rejects requests with timestamps more than 5 minutes from its clock. See Relay protocol.
Secrets in memory
Key material is zeroized when dropped.