Database encryption
Exolvra stores everything — LLM provider API keys, conversations, agent memories, webhook secrets, audit logs — in a single SQLite file. This page explains how to encrypt that file at rest and wrap the most sensitive credentials with a second layer, so a stolen backup or a compromised host doesn't immediately hand an attacker the keys to your entire deployment.
What this page is for
Encryption on Exolvra is opt-in and off by default. A fresh install writes its database in plaintext, which is fine for local personal use but not for anything production. This page walks through enabling the encryption subsystem, choosing a master key source, migrating an existing install, and recovering when something goes wrong.
What gets encrypted
When you enable the feature, three things happen:
- The whole SQLite file —
~/.exolvra/exolvra.dbplus its WAL and SHM side files — is wrapped with SQLCipher (AES-256-CBC + HMAC-SHA512 integrity). Every table, every row, every index is encrypted on disk. - Sensitive rows in the
settingstable get a second layer of application-level AES-256-GCM encryption on top of SQLCipher. The field-level wrapper uses a different subkey and binds each ciphertext to its{section}:{key}storage slot, so even if the database key leaked an attacker couldn’t copy-paste credentials between slots. - The ASP.NET Data Protection key ring at
~/.exolvra/dpkeys/is wrapped with another master-key-derived subkey. This closes the “dpkeys is plaintext XML on Linux” gap that used to make the existing MCP credential encryption only as strong as filesystem permissions.
Postgres deployments skip the SQLCipher layer — that’s a SQLite-only feature — but still get field-level encryption on sensitive credentials. Production Postgres installs should pair this with tablespace or volume-level encryption from the cloud provider (RDS encryption at rest, Azure Database with customer-managed keys).
Enabling encryption
Set two flags in your instance configuration before first launch — either edit ~/.exolvra/config.json or use environment variables:
{
"Exolvra": {
"Encryption": {
"EnableDatabaseEncryption": true,
"EnableFieldEncryption": true,
"AutoGenerateKey": false
}
}
}
The same settings as environment variables:
Exolvra__Encryption__EnableDatabaseEncryption=true
Exolvra__Encryption__EnableFieldEncryption=true
The two flags are independent. You can enable just SQLCipher (whole-file encryption) without the field-level layer, or just field-level without SQLCipher, but the recommended setup is to enable both.
Next, provide a master key. Exolvra tries four sources in order and uses the first one that succeeds.
Choosing a master key source
Environment variable (recommended for Docker and Kubernetes)
Set EXOLVRA_MASTER_KEY to a base64-encoded 32-byte value. Generate one with OpenSSL:
openssl rand -base64 32
Then inject it via your container orchestrator’s secret store:
docker run -e EXOLVRA_MASTER_KEY="$(cat master.key)" <your-exolvra-image>
This is the simplest key source and works everywhere. The trade-off is that process environment variables are readable from /proc/<pid>/environ on Linux by any process running as the same user, so a second compromised process on the same host can retrieve the key.
OS keychain (recommended for self-hosted single-machine installs)
On Windows, Exolvra stores the key in a DPAPI blob at ~/.exolvra/master.key.dpapi bound to the local machine. On Linux it uses secret-tool (libsecret/gnome-keyring/KWallet). On macOS it uses the security binary (Keychain Access). The advantage: an attacker who copies the data directory to a different machine cannot decrypt the key.
Linux and macOS need the helper binary installed. On Debian/Ubuntu:
sudo apt install libsecret-tools
If you’re running headless you also need a keyring daemon like gnome-keyring or KeePassXC available to secret-tool.
The keychain source activates automatically when a key is present. You don’t need any extra config — Exolvra writes to it the first time AutoGenerateKey generates a key, or you can manually seed it:
# Linux
echo -n "$(openssl rand -base64 32)" | secret-tool store \
--label="Exolvra Master Key" service exolvra.master-key account exolvra
# macOS
security add-generic-password -a exolvra -s exolvra.master-key \
-w "$(openssl rand -base64 32)" -U
Machine-bound encrypted keyfile (fallback for metal deployments)
If neither a KMS nor an OS keychain is available, Exolvra falls back to an encrypted keyfile at ~/.exolvra/master.key. The file is AES-256-GCM encrypted with a subkey derived from the machine’s identity (/etc/machine-id on Linux, MachineGuid from the Windows registry, IOPlatformUUID on macOS). Copying this file to a different machine renders it useless — which is also the main trade-off: if the machine identity changes, the file becomes unrecoverable. Container recreation, hostname changes, and OS reinstalls all qualify.
Don’t use the keyfile source in containers that recreate cleanly — it will lock you out on the next restart.
External KMS (Azure Key Vault or AWS KMS)
External KMS-backed master keys (Azure Key Vault, AWS KMS) are not supported yet. If you configure Exolvra:Encryption:AzureKeyVaultUri or AwsKmsKeyId, Exolvra fails fast at startup with a clear “not implemented” error rather than silently falling back to a weaker key source — a configured-but-broken KMS should never quietly downgrade your security. Until KMS support lands, use the OS keychain, the EXOLVRA_MASTER_KEY environment variable, or the encrypted keyfile.
First-run auto-generation
For clean self-hosted installs where you don’t want to provision a key ahead of time, set Exolvra:Encryption:AutoGenerateKey: true. On first start, Exolvra generates a random 32-byte key, stores it in the OS keychain (preferred) or the keyfile (fallback), and continues. Exolvra logs a loud warning with the key-source location and a reminder to back it up:
[WARN] Exolvra.Encryption: Generated and stored a new master key in the OS keychain.
BACK THIS UP: if the keychain is lost, the encrypted database becomes unrecoverable.
Auto-generate is off by default. Production deployments should provision the key via their own secret management system so they know exactly where it lives.
Migrating an existing install
When you enable encryption on a deployment that already has a plaintext database, Exolvra handles the conversion automatically on the next start:
- Detects that the file is plaintext.
- Takes a timestamped backup at
~/.exolvra/exolvra.db.plaintext.bak.<timestamp>. - Uses SQLCipher’s
sqlcipher_export()to copy the data into a fresh encrypted file. - Atomically renames the encrypted file over the original.
- Verifies the result by reopening with the key.
- Automatically deletes the plaintext backup so you don’t leave an unprotected copy on disk.
Migration is one-time. Subsequent starts see an already-encrypted file and skip the whole flow.
Before you migrate
- Back up your data directory with your normal backup tool first. The automatic migration takes its own backup but deletes it after verification — take your own copy if you want a long-term fallback.
- Rotate any credentials that were stored plaintext before encryption was enabled. Encryption protects data at rest going forward, but a stolen backup taken before encryption still contains plaintext secrets.
- Test on a staging copy if you’re running anything load-bearing.
If migration fails
If the migration can’t complete, the original file is still intact on disk (the migration works on a temp file until the atomic rename at the very end). The backup at ~/.exolvra/exolvra.db.plaintext.bak.<timestamp> is also still there, so you have two recovery paths. Exolvra logs a clear message pointing at the backup path.
Daily operations
Once encryption is enabled and a key is configured, Exolvra behaves exactly as before. There’s no runtime cost you’d notice, no UI changes, no new admin pages. Every database connection is encrypted transparently, and every sensitive settings value is encrypted on write and decrypted on read.
You can verify encryption is active by inspecting the database file directly with a SQLite tool:
sqlite3 ~/.exolvra/exolvra.db "SELECT count(*) FROM sqlite_master;"
Error: file is not a database
That’s the expected output on an encrypted file: a plain sqlite3 binary without the SQLCipher patch cannot open it. Use sqlcipher with the PRAGMA key command if you need to inspect the contents.
Recovery scenarios
Lost master key
There is no recovery. This is the trade-off of fail-closed encryption: Exolvra hard-fails at startup with an explicit error when no key can be resolved, instead of silently generating a new one and losing all your data. Your options are:
- Restore the key from your secret management backup (KMS, password vault, wherever you stored it).
- Restore the database from a pre-encryption plaintext backup and disable encryption until you have a working key workflow.
- Delete
~/.exolvra/exolvra.dband start over. This destroys all data.
Back up your master key the same way you back up your production database passwords. Treat it as a tier-one secret.
Rotated DPAPI state (Windows)
DPAPI binds its blobs to the Windows install, not to the data directory. If you copy a Windows data directory to a different machine or reinstall Windows, the DPAPI blob at ~/.exolvra/master.key.dpapi becomes undecryptable. Restore the blob from a backup of the source machine, or provide the key via EXOLVRA_MASTER_KEY instead.
Changed /etc/machine-id (Linux keyfile)
The keyfile source uses the machine identity as part of its derivation. If /etc/machine-id changes — after container recreation, cloning a VM image, a filesystem restore that reset the file — the keyfile cannot be decrypted. Use the OS keychain or env var sources in scenarios where the machine identity is unstable.
Corrupt Data Protection key ring
The dpkeys/ directory is now wrapped with the master key, so it fails closed if the master key changes. If MCP installations start throwing decryption errors after a rotation event, either restore the previous key or delete the affected installations from the MCP Library and reinstall them.
Hardening checklist for production
Work through this before enabling encryption on a production instance:
- Generate a 32-byte random master key using
openssl rand -base64 32or your KMS’s key generation. - Store the key in your secret management system (cloud secret manager, password vault, KMS).
- Decide on a key source — environment variable for containers, OS keychain for single-machine hosts, KMS when the package lands.
- Take a full data-directory backup before turning on the feature for the first time.
- Rotate any plaintext credentials that were in the settings table or
config.jsonbefore encryption was enabled. - Verify the migration completes cleanly on a staging copy first.
- Confirm the plaintext backup is deleted after a successful migration (Exolvra does this automatically, but check).
- Set up key rotation reminders even though automated rotation isn’t shipped yet — plan for it.
- Lock down
~/.exolvra/with filesystem permissions (chmod 700) so only the Exolvra service account can read it. - Monitor Exolvra’s logs for any decryption warnings — those point to real problems.
What’s not covered
- File blob storage (
~/.exolvra/storage/and~/.exolvra/files/) — agent-generated images, PDFs, and research attachments are still plaintext on disk. This is explicitly out of scope for the current feature and is planned as a follow-up. - Automated key rotation — the envelope format has a 4-byte key version field so rotation is a forward-compatible upgrade, but the rotation tool itself isn’t shipped yet.
- FIPS-validated operation — the primitives (AES-GCM, HKDF, HMAC-SHA256) are all FIPS-eligible on compliant platforms, but Exolvra doesn’t enforce FIPS mode.
- Process environment exposure — skill configuration values are decrypted and pushed into
Environment.SetEnvironmentVariableat startup so external tools can read them. This is a known limitation; decrypted secrets are visible in/proc/<pid>/environfor the lifetime of the process.
Where to go next
- Security & cloud mode — capability controls and multi-tenant hardening
- Audit log — track security-relevant events
- Admin overview — the full admin setup checklist