fix(security): keep accounts/credentials out of the auth-bound cache DB so key invalidation doesn't sign the user out #111

Closed
opened 2026-07-02 03:40:53 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-07-02 03:40:53 +00:00 (Migrated from github.com)

Origin: code review of PR #45 (screen-lock app gate), finding #4 (was above-the-cut). The safe code-level fixes on the review branch shrink this bug's blast radius but do NOT fully resolve it; the real fix is architectural and needs a device-tested migration, so it is split out here.

Problem

AccountEntity and CredentialEntity live in the same libremail.db that SQLCipher encrypts under the auth-bound passphrase when app-lock + encrypted-cache are on. When that key is invalidated (a genuine biometric re-enrollment or lock removal/re-add), the whole file becomes undecryptable, so the "clear + re-sync" recovery wipes the user's accounts and stored credentials along with the mail cache — and the re-sync no-ops on an empty accounts table, dropping the user into onboarding. The credentials' own sealing key (the non-auth KeystoreCrypto master key) actually survives the invalidation; only the SQLCipher container they sit in is lost.

Already mitigated on the fix branch (not a substitute for the fix)

  • sealWithMaster() now deletes the orphaned auth key, and KeyInvalidationPolicy no longer wipes on a merely-lapsed auth window — so spurious wipes are gone. Only a genuine key invalidation now triggers the wipe, and it still destroys accounts.

Suggested fix

Move AccountEntity / CredentialEntity (and likely AccountSettingsEntity) into a separate small Room database that is NOT bound to the auth key — sealed by the surviving master key (or plaintext with the existing column-level master-key encryption for credentials) — so a cache-key invalidation wipes only the mail cache and the user stays signed in. Requires:

  • a new database + DAOs move + Hilt wiring,
  • a one-time data migration for existing installs (copy the account/credential tables out of libremail.db, then drop them), crash-safe and idempotent, handling the currently-encrypted case,
  • exported Room schemas + a migration test (per CLAUDE.md), and
  • on-device validation of the upgrade path (a botched migration would destroy real users' accounts — strictly worse than the current bug, which is why it was deliberately NOT attempted blind on the review branch).
Origin: code review of PR #45 (screen-lock app gate), finding #4 (was above-the-cut). The safe code-level fixes on the review branch shrink this bug's blast radius but do NOT fully resolve it; the real fix is architectural and needs a device-tested migration, so it is split out here. ## Problem `AccountEntity` and `CredentialEntity` live in the same `libremail.db` that SQLCipher encrypts under the **auth-bound** passphrase when app-lock + encrypted-cache are on. When that key is invalidated (a genuine biometric re-enrollment or lock removal/re-add), the whole file becomes undecryptable, so the "clear + re-sync" recovery wipes the user's accounts and stored credentials along with the mail cache — and the re-sync no-ops on an empty accounts table, dropping the user into onboarding. The credentials' own sealing key (the non-auth `KeystoreCrypto` master key) actually survives the invalidation; only the SQLCipher container they sit in is lost. ## Already mitigated on the fix branch (not a substitute for the fix) - `sealWithMaster()` now deletes the orphaned auth key, and `KeyInvalidationPolicy` no longer wipes on a merely-lapsed auth window — so **spurious** wipes are gone. Only a genuine key invalidation now triggers the wipe, and it still destroys accounts. ## Suggested fix Move `AccountEntity` / `CredentialEntity` (and likely `AccountSettingsEntity`) into a separate small Room database that is NOT bound to the auth key — sealed by the surviving master key (or plaintext with the existing column-level master-key encryption for credentials) — so a cache-key invalidation wipes only the mail cache and the user stays signed in. Requires: - a new database + DAOs move + Hilt wiring, - a one-time data migration for existing installs (copy the account/credential tables out of `libremail.db`, then drop them), crash-safe and idempotent, handling the currently-encrypted case, - exported Room schemas + a migration test (per CLAUDE.md), and - on-device validation of the upgrade path (a botched migration would destroy real users' accounts — strictly worse than the current bug, which is why it was deliberately NOT attempted blind on the review branch).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#111