tftsr-devops_investigation/docs/architecture/adrs/ADR-005-auto-generate-encryption-keys.md
Shaun Arman 40b6882cab
Some checks failed
Test / rust-fmt-check (pull_request) Failing after 14s
Test / rust-clippy (pull_request) Failing after 16s
Test / rust-tests (pull_request) Failing after 18s
Test / frontend-typecheck (pull_request) Successful in 1m27s
Test / frontend-tests (pull_request) Successful in 1m28s
PR Review Automation / review (pull_request) Successful in 3m4s
fix: comprehensive trcaa→tftsr conversion and URL corrections
Complete sanitization pass to ensure consistency:

**1. Repository/Project Name Changes:**
- trcaa-devops_investigation → tftsr-devops_investigation (everywhere)
- gogs.trcaa.com → gogs.tftsr.com (all URLs)
- ollama-ui.trcaa.com → ollama-ui.tftsr.com

**2. Internal CI URLs (must use 172.0.0.29):**
- gitea.tftsr.com:3000 → 172.0.0.29:3000 in:
  - AGENTS.md
  - README.md
  - docs/architecture/README.md
  - docs/wiki/*.md
- CI runners cannot reach external DNS

**3. Code Simplifications:**
- MSIGenAI/TFTSRGenAI → GenAI (src-tauri/src/ai/openai.rs)
- Cleaner comments without org-specific references

**4. Build System Updates:**
- Makefile: GH_TOKEN → GOGS_TOKEN, GH_REPO → GOGS_REPO
- Commented out GitHub release upload commands
- Fixed lib name: tftsr_lib → trcaa_lib (src/main.rs)

**5. Documentation Cleanup:**
- CLAUDE.md: Fixed wiki URL, Woodpecker→Gitea Actions
- Removed PLAN.md, SECURITY_AUDIT.md (not needed in git)
- Removed hackathon docs (HACKATHON-*.md)
- Removed v1.0.5/7/8 summary docs (superseded)

**6. Preserved:**
- TRCAA (all caps) = application name (correct!)
- trcaa package name in Cargo.toml (correct!)
- trcaa_lib library name (correct!)

**Test Results:** 308 Rust + 92 frontend tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 15:38:29 -05:00

4.2 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 (TRCAA_DB_KEY (or legacy TRCAA_DB_KEY)): SQLCipher AES-256 key for the full database
  2. Credential key (TRCAA_ENCRYPTION_KEY (or legacy TRCAA_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 "TRCAA_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) $TRCAA_DATA_DIR/
Credentials .enckey 0600 (owner r/w only) $TRCAA_DATA_DIR/

Platform data directories:

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

Key Resolution Order

For both keys:

  1. Check environment variable (TRCAA_DB_KEY (or legacy TRCAA_DB_KEY) / TRCAA_ENCRYPTION_KEY (or legacy TRCAA_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 $TRCAA_DATA_DIR (including hidden files) preserves both key files and database. Loss of keys without losing the database = data loss.