tftsr-devops_investigation/docs/architecture/adrs/ADR-005-auto-generate-encryption-keys.md
Shaun Arman fdb4fc03b9 docs(architecture): add C4 diagrams, ADRs, and architecture overview
Comprehensive architecture documentation covering:

- docs/architecture/README.md: Full C4 model diagrams (system context,
  container, component), data flow sequences, security architecture,
  AI provider class diagram, CI/CD pipeline, and deployment diagrams.
  All diagrams use Mermaid for version-controlled diagram-as-code.

- docs/architecture/adrs/ADR-001: Tauri vs Electron decision rationale
- docs/architecture/adrs/ADR-002: SQLCipher encryption choices and
  cipher_page_size=16384 rationale for Apple Silicon
- docs/architecture/adrs/ADR-003: Provider trait + factory pattern
- docs/architecture/adrs/ADR-004: Regex + Aho-Corasick PII detection
- docs/architecture/adrs/ADR-005: Auto-generate encryption keys at
  runtime (documents the fix from PR #24)
- docs/architecture/adrs/ADR-006: Zustand state management rationale

- docs/wiki/Architecture.md: Updated module table (14 migrations, not
  10), corrected integrations description, updated startup sequence to
  reflect key auto-generation, added links to new ADR docs.

- README.md: Fixed stale database paths (tftsr → trcaa) and updated
  env var descriptions to reflect auto-generation behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 09:35:35 -05:00

4.0 KiB

ADR-005: Auto-generate Encryption Keys at Runtime

Status: Accepted Date: 2026-04 Deciders: sarman


Context

The application uses two encryption keys:

  1. Database key (TFTSR_DB_KEY): SQLCipher AES-256 key for the full database
  2. Credential key (TFTSR_ENCRYPTION_KEY): AES-256-GCM key for token/API key encryption

The original design required both to be set as environment variables in release builds. This caused:

  • Critical failure on Mac: Fresh installs would crash at startup with "file is not a database" error
  • Silent failure on save: Saving AI providers would fail with "TFTSR_ENCRYPTION_KEY must be set in release builds"
  • Developer friction: Switching from cargo tauri dev (debug, plain SQLite) to a release build would crash because the existing plain database couldn't be opened as encrypted

Decision

Auto-generate cryptographically secure 256-bit keys at first launch and persist them to the app data directory with restricted file permissions.


Key Storage

Key File Permissions Location
Database .dbkey 0600 (owner r/w only) $TFTSR_DATA_DIR/
Credentials .enckey 0600 (owner r/w only) $TFTSR_DATA_DIR/

Platform data directories:

  • macOS: ~/Library/Application Support/trcaa/
  • Linux: ~/.local/share/trcaa/
  • Windows: %APPDATA%\trcaa\

Key Resolution Order

For both keys:

  1. Check environment variable (TFTSR_DB_KEY / TFTSR_ENCRYPTION_KEY) — use if set and non-empty
  2. If debug build — use hardcoded dev key (never touches filesystem)
  3. If .dbkey / .enckey exists and is non-empty — load from file
  4. Otherwise — generate 32 random bytes via OsRng, hex-encode to 64-char string, write to file with mode 0600

Plain-to-Encrypted Migration

When a release build encounters an existing plain SQLite database (written by a debug build), rather than crashing:

1. Detect plain SQLite via 16-byte header check ("SQLite format 3\0")
2. Copy database to .db.plain-backup
3. Open plain database
4. ATTACH encrypted database at temp path with new key
5. SELECT sqlcipher_export('encrypted')   -- copies all tables, indexes, triggers
6. DETACH encrypted
7. rename(temp_encrypted, original_path)
8. Open encrypted database with key

Alternatives Considered

Option Pros Cons
Auto-generate keys (chosen) Works out-of-the-box, no user config Key file loss = data loss (acceptable: key + DB on same machine)
Require env vars (original) Explicit — users know their key Crashes on fresh install, poor UX
Derive from machine ID No file to lose Machine ID changes break DB on hardware changes
OS keychain Most secure Complex cross-platform implementation; adds dependency
Prompt user for password User controls key Poor UX for a tool; password complexity issues

Why not OS keychain: The tauri-plugin-stronghold already provides a keychain-like abstraction for credentials, but integrating SQLCipher key retrieval into Stronghold would create a chicken-and-egg problem: Stronghold itself needs to be initialized before the database that stores Stronghold's key material.


Consequences

Positive:

  • Zero-configuration installation — app works on first launch
  • Developers can freely switch between debug and release builds
  • Environment variable override still available for automated/enterprise deployments
  • Key files are protected by Unix file permissions (0600)

Negative:

  • If .dbkey or .enckey are deleted, the database and all stored credentials become permanently inaccessible
  • Key files are not themselves encrypted — OS-level protection depends on filesystem permissions
  • Not suitable for multi-user scenarios where different users need isolated key material (single-user desktop app — acceptable)

Mitigation for key loss: Document clearly that backing up $TFTSR_DATA_DIR (including hidden files) preserves both key files and database. Loss of keys without losing the database = data loss.