refactor(security): derive backup exclusion set from DatabaseFiles #135

Merged
JMR-dev merged 4 commits from refactor-db-file-backup-sot into main 2026-07-02 15:58:06 +00:00
JMR-dev commented 2026-07-02 14:46:32 +00:00 (Migrated from github.com)

What

Closes #103.

Makes BackupPolicy.EXCLUDED_DATABASE_PATHS the true single source of truth by deriving it from DatabaseFiles rather than hand-listing paths, and fixes DatabaseFiles.clear to use context.deleteDatabase.

1. Backup exclusion set is now derived from DatabaseFiles (security-relevant)

  • New DatabaseFiles.fileNames(name) helper returns a DB file plus its statically-nameable SQLite sidecars (-wal/-shm/-journal) — the one place that knows which files make up a database.
  • BackupPolicy.EXCLUDED_DATABASE_PATHS is now fileNames(NAME) + fileNames(ACCOUNTS_NAME). This adds libremail-accounts.db (+ sidecars) — the accounts + encrypted-credentials DB that #118/#111 split out of libremail.db — to the never-back-up set, matching the field's stated intent. A newly added database can no longer silently fall out of the exclusions.

2. DatabaseFiles.clear fix

  • Now wipes the cache DB via context.deleteDatabase(NAME), which also removes the -mj* master-journal temp files the hand-rolled suffix list missed.
  • Still wipes only the cache DB (NAME) and never the accounts DB (ACCOUNTS_NAME) — the sign-in-survives-cache-wipe separation from #111 is preserved. The only caller (DatabaseModule.provideDatabase) already passes a Context, so no no-Context fallback is needed.

3. Doc + test updates

  • Updated the comments in data_extraction_rules.xml (API 31+) and backup_rules.xml (API 29-30) to note libremail-accounts.db is also kept off-device by the strict include-allowlist.
  • BackupPolicyTest: asserts the accounts DB (+ sidecars) is in the exclusion set and that the set is exactly the DatabaseFiles-derived list (lockstep, no drift).
  • DataExtractionRulesTest: the derived secretPaths now includes the accounts DB, so assertSafe checks it against every include section; added an explicit test pinning both DBs into the guarded set.

Security note

There is no active backup leak today. The backup XML is a strict include-allowlist (only datastore/libremail_settings.preferences_pb is backed up), so libremail-accounts.db was already excluded by omission. This PR closes the SoT drift #118 introduced (credentials changed DBs but the exclusion SoT did not) and the -mj* gap, so the security posture no longer depends on the allowlist staying strict by luck.

Scope

Strictly BackupPolicy.kt, DatabaseFiles.kt, the two backup XML resources, and their tests. Does not touch di/DatabaseModule.kt (#93), the migrator, or app-lock code.

Testing

Fast CI gate all green locally (JDK 21): assembleDebug, testDebugUnitTest, lintDebug, ktlintCheck, detekt, plus compileDebugAndroidTestKotlin.

🤖 Generated with Claude Code

## What Closes #103. Makes `BackupPolicy.EXCLUDED_DATABASE_PATHS` the true single source of truth by **deriving** it from `DatabaseFiles` rather than hand-listing paths, and fixes `DatabaseFiles.clear` to use `context.deleteDatabase`. ### 1. Backup exclusion set is now derived from `DatabaseFiles` (security-relevant) - New `DatabaseFiles.fileNames(name)` helper returns a DB file plus its statically-nameable SQLite sidecars (`-wal`/`-shm`/`-journal`) — the one place that knows which files make up a database. - `BackupPolicy.EXCLUDED_DATABASE_PATHS` is now `fileNames(NAME) + fileNames(ACCOUNTS_NAME)`. This **adds `libremail-accounts.db` (+ sidecars)** — the accounts + encrypted-credentials DB that #118/#111 split out of `libremail.db` — to the never-back-up set, matching the field's stated intent. A newly added database can no longer silently fall out of the exclusions. ### 2. `DatabaseFiles.clear` fix - Now wipes the cache DB via `context.deleteDatabase(NAME)`, which also removes the `-mj*` master-journal temp files the hand-rolled suffix list missed. - Still wipes **only** the cache DB (`NAME`) and **never** the accounts DB (`ACCOUNTS_NAME`) — the sign-in-survives-cache-wipe separation from #111 is preserved. The only caller (`DatabaseModule.provideDatabase`) already passes a `Context`, so no no-`Context` fallback is needed. ### 3. Doc + test updates - Updated the comments in `data_extraction_rules.xml` (API 31+) and `backup_rules.xml` (API 29-30) to note `libremail-accounts.db` is also kept off-device by the strict include-allowlist. - `BackupPolicyTest`: asserts the accounts DB (+ sidecars) is in the exclusion set and that the set is exactly the `DatabaseFiles`-derived list (lockstep, no drift). - `DataExtractionRulesTest`: the derived `secretPaths` now includes the accounts DB, so `assertSafe` checks it against every include section; added an explicit test pinning both DBs into the guarded set. ## Security note **There is no active backup leak today.** The backup XML is a strict include-*allowlist* (only `datastore/libremail_settings.preferences_pb` is backed up), so `libremail-accounts.db` was already excluded by omission. This PR closes the SoT drift #118 introduced (credentials changed DBs but the exclusion SoT did not) and the `-mj*` gap, so the security posture no longer depends on the allowlist staying strict by luck. ## Scope Strictly `BackupPolicy.kt`, `DatabaseFiles.kt`, the two backup XML resources, and their tests. Does not touch `di/DatabaseModule.kt` (#93), the migrator, or app-lock code. ## Testing Fast CI gate all green locally (JDK 21): `assembleDebug`, `testDebugUnitTest`, `lintDebug`, `ktlintCheck`, `detekt`, plus `compileDebugAndroidTestKotlin`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.