Set a keystore before the first login
The shortest form is a file path:keystorePath is shorthand for keystore: fileKeystore(path). Any other backend is a KeystoreAdapter passed as keystore. The two options are mutually exclusive, and supplying both throws.
The write-once identity
This is the one thing on this page that cannot be undone, so it is worth reading in full. On its first login, your bot generates an identity and publishes the public half to Cloak. That publication is write-once server-side. There is no republish, no rotate, and no overwrite. From then on, every human member who shares a conversation key with your bot wraps that key to the identity Cloak holds. If the private half is not persisted:1
Run 1 looks perfect
The bot generates an identity, publishes it, receives keys, decrypts, and sends. Everything works. The SDK prints one loud warning at the moment it publishes an identity with no keystore, and that warning is not gated behind
debug.2
The process exits
The private half died with it. Cloak still holds the public half, forever.
3
Run 2 fails permanently
The bot generates a new identity and tries to publish it. The server refuses, because it already holds one. The SDK discards the useless fresh identity and
login() throws, naming your keystore.the server already holds a published identity for this bot, but the local keystore (…) is new: its freshly generated identity was rejected (op-238 publication is write-once)There is no local fix. With no keystore there is nothing to restore from, and the only recovery is resetting the bot’s identity server-side.
This replaced an older, worse symptom. The SDK used to start fine and then fail every decryption, so the visible problem was garbled traffic rather than a startup error. If you find a doc or a post describing a “Bad MAC” here, it is describing the behavior this guard was built to remove. Under HPKE the underlying decryption failure text is
open failed: bad tag, and you should not be seeing it for this reason any more.Where the failure surfaces
login() reads the keystore as its first async step, before it opens any socket and before it makes any identity decision. Failures land there.
What lives in the keystore
You never read or write any of it by hand. Your job is to make sure it survives restarts and stays private.
Peer pins and trust on first use
The first wrap your bot receives from a given member pins that member’s identity key. Later wraps from the same member must match the pin or they are rejected. Those pins live in the keystore, so they survive a restart. A bot that keeps its keystore does not silently re-enter trust-on-first-use on every start, which is what makes the mismatch check able to fire at all.Protect it
The keystore holds live keys. Anyone who can read it can impersonate your bot and decrypt its traffic.- Never commit it. Add this to your
.gitignore:
.gitignore
- Keep it out of logs and out of backups that leave your control.
- On a host with a writable disk, point
keystorePathat a mounted volume, not at the ephemeral container filesystem. A container that redeploys onto a fresh filesystem is the most common way a bot walks through the one-way door.
Keystore backends
fileKeystore()
The default, and what keystorePath gives you. Two properties belong to this adapter specifically:
- Writes are atomic. Each save goes to a temporary sibling file and is renamed into place, so an interrupted write can never leave you with a truncated keystore.
- The file is written with
0600permissions, so only your user can read it.
envKeystore()
For hosts with no writable volume. It reads CLOAK_KEYSTORE (override with varName), accepting either raw keystore JSON or base64 of it, and writes base64 back into process.env.
A custom adapter
Implement three methods. Onlydescribe() is optional.
Shutting down
destroy() flushes queued keystore writes as well as closing the connection, so always await it. A shutdown immediately after a key landed would otherwise drop that key and cost a re-acquire on the next start.
Next
Connection lifecycle
When the keystore is read, and what carries across a reconnect.
Deploying a bot
Mounted volumes, secret stores, and the one deployment decision that matters.
How bots work
Why your bot holds keys in the first place.
What is encrypted, and what is not
What those keys do and do not cover.