---
title: Database Encryption
---

# Database Encryption

> **Status:** available, off by default (shipped 2026-07-25). Turn it on in its own **Database
> Encryption** section in Settings. Both tiers ship — the transparent (OS-keychain) tier and the
> zero-knowledge passphrase tier.

## What it is

Opt-in encryption of your **live** local database at rest — `mission-control.db` and its
cold-storage archive. When it's on, the database files on disk are ciphertext and can't be opened
without your key. It's **off by default**, so the ~99% who never turn it on are completely
unaffected: the app opens today's plaintext databases exactly as before.

This closes the one gap in Omniscio's at-rest story — [backups were already encrypted](is-my-data-encrypted.md),
but the live file wasn't. It's built on an encryption-capable SQLite engine (a drop-in swap of the
database driver); with encryption off it behaves identically to the old engine.

## Where to find it

The controls live in their own **Database Encryption** section in Settings, alongside the rest of
your app configuration — there is no dialog that appears on its own and nothing to configure
until you deliberately turn the feature on. That section is also where you set, change, or remove
a passphrase and where you generate a fresh recovery code.

> **This section has no sidebar row.** It is reached by **searching Settings** for
> "Database Encryption" (or "encryption", "passphrase", "recovery code") — so when the steps
> below say *Settings → Database Encryption*, that search is how you get there. The section
> itself then has everything: the on/off switch, **Passphrase**, and **Recovery code**.

## How it behaves

### The key model (why there's a recovery code)

- A random 32-byte **data-encryption key (DEK)** encrypts the database. The DEK is stored **wrapped**,
  and the wrap is the only thing that differs between tiers:
  - **Transparent (default, built):** the DEK is sealed by your computer's **OS keychain** (the same
    vault that guards your API keys), so the app unlocks the database automatically when you're
    logged in — zero friction, no prompt.
  - **Passphrase (zero-knowledge, built):** the DEK is wrapped by a key derived from a passphrase you
    set; the keychain copy is removed, so nothing but your passphrase (or the recovery code) opens it,
    and you're prompted for it at launch before the database opens.
- **A recovery code is MANDATORY at enable** (both tiers). A lost keychain — an OS reinstall, a new
  machine, a corrupted profile — would otherwise make the encrypted database permanently unreadable;
  the recovery code is the always-available way back in. It's shown **once** and must be saved.
  - **Lost the code you saved?** Open **Settings → Database Encryption** (search Settings for
    "Database Encryption" — see *Where to find it* above) → **Recovery code → Generate new code**
    — it re-wraps the same key under a fresh code (the old code stops
    working), shown once like the original. So a mislaid recovery code is no longer terminal.

Switching tiers just re-wraps the DEK (instant); only turning encryption on or off does the
one-time heavy re-encryption of the data.

### How it works day to day

- **Turn it on** → the app generates the key + a one-time recovery code (shown in a dialog you must
  acknowledge) → the actual encryption of your existing database runs at the **next restart** (a
  full safety backup first, then a crash-safe copy → re-key → atomic swap, so a power loss can't lose
  data; if the backup can't be written — e.g. the disk is full — the change is refused and the
  database is left untouched) → done.
- **Turn it off** → the database is decrypted back to plaintext at the next restart.
- **Add a passphrase** (Settings → Database Encryption — the search route above — once encryption is on) → your existing key is
  re-wrapped behind the passphrase and the keychain copy is removed. It's **instant** — your data is
  not re-encrypted — and takes effect at the next launch, where you're asked for the passphrase before
  the database opens. **Change** or **Remove** it the same way (Remove switches back to the automatic
  keychain unlock, so the database auto-opens again).
- **At launch in passphrase mode** → a small unlock window appears before anything else; type your
  passphrase to continue. A wrong one is retryable; Cancel quits without ever opening the database. If
  you forget the passphrase — or lose the keychain in the transparent tier — the same window takes
  your **recovery code** instead.
- Every database connection — the main one, the off-thread read worker, and the cold-storage archive
  — is keyed with the same key, so nothing breaks.
- Encryption disables the database's memory-mapping speed trick, so the encrypted profile uses a
  page-cache instead; the overhead is opt-in and measured.

### Security guarantees (never-regress)

- The key is **never written to disk in plaintext** and **never crosses to the app's UI layer** —
  only wrapped blobs persist, and they're stripped from every settings read (screen + CLI). Your
  passphrase travels only one way (into the app, to unlock or re-wrap) and never comes back out; its
  wrapped-key blob is stored as its own ciphertext, so it survives a keychain loss.
- Turning encryption on/off — and setting, changing, or removing the passphrase — is a **dedicated,
  human-only action** (`security-boundary-human-only` in the CLI-parity manifest; blocked on the web
  bridge) — an AI agent can never flip your database's encryption state, set a passphrase, or touch
  the key material.

## For agents

### Where the code lives

Engine swap, key management, crash-safe migration, boot reconcile, the enable/disable service + IPC,
and the gated Settings UI are catalogued with their invariants and named tests in the contract:
[database-encryption-contract.md](../../.claude/memory/contracts/database-encryption-contract.md).

## Related

What Omniscio encrypts and what it does not, across the whole app, is mapped on the
[is my data encrypted](is-my-data-encrypted.md) page — that page's backup half is what this one
closes the gap in. The encrypted snapshots it refers to are described on the
[Backup Mirror](backup-mirror.md) page.
