Skip to content

feat(db): optional SQLCipher at-rest database encryption (opt-in, off by default) - #232

Merged
kafkade merged 2 commits into
mainfrom
kafkade-sqlcipher-at-rest-encryption
Jul 28, 2026
Merged

feat(db): optional SQLCipher at-rest database encryption (opt-in, off by default)#232
kafkade merged 2 commits into
mainfrom
kafkade-sqlcipher-at-rest-encryption

Conversation

@kafkade

@kafkade kafkade commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Description

Adds optional, off-by-default at-rest encryption for the live toku.db, implementing ADR-016. Encryption is scoped to a new sqlcipher cargo feature on toku-db; when the feature is disabled — the default — the database path is byte-for-byte unchanged, and a build compiled without the feature behaves exactly as before. Toku's local-first, offline-first stance is preserved: encryption is entirely opt-in and never phones home.

What's included

  • toku-db sqlcipher feature selecting rusqlite/bundled-sqlcipher-vendored-openssl. Scoped to toku-db only — the workspace root and the toku-sync relay stay on standard SQLite.
  • Keyed open paths. New open_encrypted / open_no_migrate_encrypted constructors issue a raw-key PRAGMA key as the first statement (before WAL, foreign_keys, and migrations), then probe sqlite_master so a wrong passphrase yields a clean "incorrect passphrase or not an encrypted Toku database" error — never a panic or partial open. The unkeyed constructors are untouched.
  • Key derivation & config. A dedicated DB passphrase is stretched with Argon2id (m=64 MiB, t=3, p=1) into a 256-bit key. Only the salt, KDF parameters, and a verifier are persisted in a new [encryption] section of config.toml — the passphrase and derived key are never written to disk.
  • toku db command group: encrypt, decrypt, status, and forget. Migrations use sqlcipher_export(), keep a verified .bak of the original until the swap is confirmed, are idempotent, and refuse to run destructively without --yes. Without the feature compiled, these print a clear "built without at-rest encryption support" message.
  • Key availability across short-lived CLI processes, in precedence order: opt-in TOKU_DB_PASSPHRASE env var → opt-in OS-keychain cache (toku db encrypt --remember) → interactive prompt (the baseline). Both caching paths are explicitly opt-in.
  • Entry-point wiring. Every toku.db open site (CLI dispatch, web request handlers, sync client) resolves the key once and opens keyed when encryption is enabled. The C FFI gains a toku_open_encrypted entry point (a pure ABI addition; toku_open is unchanged) with a regenerated toku.h.
  • Docs & CI. Threat-model and recovery docs updated to reflect the shipped feature, prominently documenting that a lost passphrase is unrecoverable by design (no backdoor). A new non-gating sqlcipher CI job proves the feature compiles and its tests pass, without altering the Validate merge gate.

Related Issues

Closes #225

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Documentation update
  • Refactoring (no functional changes)
  • CI / infrastructure
  • Other (describe below)

Crate

  • toku-core — Domain models, traits, state machine
  • toku-db — SQLite persistence, migrations, FTS5
  • toku-import — Importers (Goodreads, Calibre, StoryGraph)
  • toku-meta — Metadata fetching (Open Library, Google Books)
  • toku-cli — CLI binary
  • toku-export — Exporters (CSV, JSON, Markdown, BibTeX)
  • docs/ — Documentation

Also touches toku-web, toku-ffi, and toku-sync-client (key-aware open sites and the FFI ABI addition).

Data Integrity Checklist

  • No user data is sent to any server without explicit opt-in — encryption is fully local; nothing is transmitted
  • Import operations are idempotent (re-importing the same file creates no duplicates) — N/A to this change; db encrypt/decrypt are themselves idempotent
  • User edits to metadata are never overwritten by auto-enrichment — unaffected
  • New fields track provenance (source + timestamp) — N/A; [encryption] stores only salt/KDF params/verifier
  • Export round-trip is preserved (if applicable) — encrypt→decrypt round-trip verified (ciphertext ↔ SQLite format 3)

Checklist

  • I have read CONTRIBUTING.md
  • cargo fmt --check passes
  • cargo clippy --workspace -- -D warnings passes (also -p toku-db --features sqlcipher)
  • cargo test --workspace passes (feature off; -p toku-db --features sqlcipher also green)
  • I have updated documentation (threat model, recovery guide, CHANGELOG)

Note: A lost passphrase makes the encrypted database permanently unrecoverable — there is no backdoor by design. This is documented prominently in docs/recovery.md.

@kafkade
kafkade enabled auto-merge (squash) July 28, 2026 04:10
@kafkade
kafkade merged commit fe22405 into main Jul 28, 2026
12 checks passed
@kafkade
kafkade deleted the kafkade-sqlcipher-at-rest-encryption branch July 28, 2026 04:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SQLCipher at-rest DB encryption behind an off-by-default toku-db cargo feature (per ADR-016)

1 participant