Skip to main content
ChatCLI keeps your sessions, transcripts, prompt history, long-term memory, knowledge contexts and attachments on disk under ~/.chatcli. All of it is encrypted by default with AES-256-GCM. There is nothing to configure: on the first start ChatCLI creates a key, keeps it in the operating system’s keychain, and seals every store with it. Someone who copies your ~/.chatcli directory, a backup of it, or a stolen disk image gets ciphertext. They do not get your conversations.
Encryption at rest protects data on disk. A process running as your user while ChatCLI is unlocked can still read what ChatCLI can read. It complements full-disk encryption (FileVault, LUKS, BitLocker) and does not replace it.

What is encrypted

Each payload is bound to its own path and tenant (authenticated data), so a file copied to another store or another tenant does not open there. The credentials in auth-profiles.json (OAuth and API keys) were already encrypted, with their own key. That key now lives in the keychain too. Not encrypted, on purpose:
  • the daily budget counter, costs/daily-spend.json, which is read before any key is needed;
  • exports you ask for (/session export, /memory export, /context export), which are files you take somewhere else (see Exports);
  • the hash-chained audit trail (CHATCLI_AUDIT_LOG_PATH), which is sealed line by line only under a key from CHATCLI_ENCRYPTION_KEY: it is a file operators ship to a log pipeline;
  • coder checkpoints, which are copies of the project files that are already on disk in plaintext;
  • the scratch directory of an agent session, which is removed when the session ends.

Where the key lives

ChatCLI keeps one secret in the operating system’s credential store. The key that seals your data is derived from it (HKDF-SHA256), and so is a separate key for the credential store. Both come from the same secret but are cryptographically separate. Where there is no keychain (a server, a container, Linux without secret-tool, or CHATCLI_KEYCHAIN_BACKEND=file), the secret is the file ~/.chatcli/.auth-key, mode 0600. The data is still encrypted, but whoever copies that file together with the data can open it. /config security warns about this case. For stronger protection there, set CHATCLI_ENCRYPTION_KEY from a secret manager. Once the key is in the keychain, a marker next to where the key file was (~/.chatcli/.auth-key.keychain) records it. From then on ChatCLI never creates a new key for this installation: a context that cannot reach the keychain (an SSH session, a container, a cron job with CHATCLI_KEYCHAIN_BACKEND=file) gets an error instead of a fresh key that would orphan your data, and a key file that shows up later (a restored backup) does not replace the keychain key. CHATCLI_KEYCHAIN_BACKEND chooses the store:
  • auto (default): the keychain where one is available, the file otherwise.
  • keychain: the keychain always.
  • file: the file always.
Switching between them never changes the key, so nothing breaks and nothing is lost. Moving to file after the key went to the keychain writes the same key to ~/.chatcli/.auth-key (the keychain item is kept), and moving back reads it from the keychain again.

Keep a recovery key

The REPL reminds you of this once, on the first start after your data is encrypted. The keychain holds the only copy of the default key. If it is lost — a reinstalled system, a reset keychain, a new machine — the encrypted stores cannot be opened. Keep a copy outside the machine once:
It prints the recovery key (only in an interactive terminal; it is never logged). Store it in a password manager, not in a file next to your data. On any machine, setting it as CHATCLI_ENCRYPTION_KEY opens every store sealed with the default key:
The same value is what a cron job, a container or an SSH session that cannot reach the keychain needs. A recovery file instead of the raw key. If you prefer a file you can keep on a USB stick or a cloud drive:
It asks for a passphrase twice (at least 12 characters) and writes the recovery key sealed with it (Argon2id + AES-256-GCM). The file opens nothing without the passphrase, so keep the two in different places. On a new machine, or after a reinstall:
It asks for the passphrase and moves your data onto this machine’s keychain key; the key is never shown or typed.

Choosing the key yourself

CHATCLI_ENCRYPTION_KEY takes precedence over the keychain. Use it when the key must come from outside the machine:
  • servers and the gateway in production;
  • Kubernetes, where the operator injects it from a Secret (spec.features.encryptionKeyRef);
  • CI;
  • sharing encrypted stores between machines.
It is not a one-way choice. ChatCLI always has its own key in the keychain, separate from yours. /config security use-keychain opens every file with your key and writes it again with the keychain key (it does not copy your key into the keychain); after that, remove the variable. ChatCLI encrypts with one key at a time (yours while the variable is set, the keychain key otherwise), but when a file does not open with that key it tries the other one, so a file encrypted with either key keeps opening. A secret of 32 characters or more is used directly. A shorter one is treated as a passphrase and stretched with Argon2id. Keep it in a secret manager: data sealed with it does not open without it.
chatcli server never creates a key on its own; on Kubernetes the key lives in a Secret. See Kubernetes.

Kubernetes

chatcli server never invents a key on its own: a key created inside a container would be lost on the next restart while the stores live on a volume. On Kubernetes the key lives in a Secret instead, and encryption at rest is on by default on both deployment paths.
  • A key you supply through extraEnv or an envFrom Secret always wins: the server reads its key Secret only when CHATCLI_ENCRYPTION_KEY is empty.
  • Opt out with security.encryptionAtRest: false (chart) or spec.features.encryptionAtRest: false (operator).
  • A deleted key Secret is never replaced behind your back. If the Secret is missing but the server’s stores already hold encrypted data, the server does not create a new key and does not start; the operator, which records that it created the Secret (annotation platform.chatcli.io/atrest-key-created), stops the reconcile with an error instead of recreating it. Restore the Secret from your backup to recover. To start over and discard the encrypted data, delete the stores (and, with the operator, remove the annotation).
  • Back the key Secret up together with the PVC data (Velero, or your secret manager).

Upgrading from an older version

Older versions wrote everything in plaintext unless CHATCLI_ENCRYPTION_KEY was set. The first start of a version with encryption on by default migrates your data:
  1. The key is created and kept in the keychain.
  2. An existing ~/.chatcli/.auth-key file is moved into the keychain when the keychain holds no key yet. The file is removed only after the keychain returns the same key. If the keychain already holds a different key (another installation may depend on it), nothing moves automatically: the file keeps being used and the keychain is left untouched. /config security use-keychain moves it on request, after checking the file key and keeping the keychain’s value as auth-key.previous.
  3. Every store still in plaintext is sealed in the background. Each file is replaced atomically, so an interrupted pass loses nothing and the next start resumes it.
    • Files modified in the last 10 minutes are left for the next start, because another ChatCLI window may still be writing them. A writer seals its own files on its next save anyway.
    • Short-lived commands (chatcli -p) migrate a share of the files per run.
  4. When everything is sealed, ChatCLI records it and stops checking.
The background pass runs with the default keychain key only. With CHATCLI_ENCRYPTION_KEY set (a server, the operator, or you opted in earlier), nothing is rewritten behind your back: each store is sealed on its next save, and /config security reseal seals the rest when you ask. The pass only touches the files each store reads sealed; an export, a note or a repository you keep in those directories is left alone, and each file keeps its modification time. Your existing data keeps opening during and after the migration: a plaintext file is read as it is and sealed on its next save. To seal everything at once instead of waiting, run:
An older ChatCLI version cannot read the encrypted stores: it does not know where the key lives. To go back to one, first write the stores back in plaintext with the new version:
With encryption off, reseal decrypts every store instead of sealing it. Then install the older version and keep CHATCLI_ENCRYPTION_KEY=off while you use the new one.

Checking the status

/config security shows: Unavailable means the key could not be read: a locked macOS keychain in an SSH session, or Linux with secret-tool but no desktop session. ChatCLI then writes new data in plaintext and cannot open sealed files until the key is reachable again. It never generates a new key in that state, because a new key would orphan the data sealed with the old one. Unlock the keychain, or set CHATCLI_KEYCHAIN_BACKEND=file on machines that never have a desktop session.

Rotating the key

From the default keychain key to a key of your own: set CHATCLI_ENCRYPTION_KEY and run /config security reseal. The keychain key still opens what it sealed, and the reseal moves every store to the new key. From a key of your own back to the keychain key: with CHATCLI_ENCRYPTION_KEY still set, run /config security use-keychain. It re-encrypts every store with the keychain key (your key is not copied anywhere); then remove the variable from your environment. If you forget to remove it, nothing breaks: new data is encrypted with your key again, and files encrypted with either key keep opening. From one CHATCLI_ENCRYPTION_KEY to another:
  1. Set the new key in CHATCLI_ENCRYPTION_KEY, and the old one in CHATCLI_ENCRYPTION_KEY_PREVIOUS (a comma-separated list).
  2. Run /config security reseal. Every store is rewritten with the new key.
  3. When it reports no errors, unset CHATCLI_ENCRYPTION_KEY_PREVIOUS.

Turning it off

New writes are plaintext. Files that were already sealed keep opening with the key from the keychain, so turning encryption off never locks you out of your own data. To also write the existing stores back in plaintext, run /config security reseal while it is off.

Exports

Exports are files you take somewhere else, so they come out in plaintext (mode 0600) and the command says so: A plaintext export imports anywhere. Keep it as carefully as the data it holds, and delete it when you no longer need it. For what to do when something goes wrong — a locked keychain, a new machine, a cron job, a downgrade — see Encrypted data: protect it and recover it.

Security overview

Every protection layer and its status.

Session management

Sessions, autosave and the transcript journal.