Google Auto Backup of the age-encrypted blob, passphrase-gated restore

Per request: re-enable allowBackup but scope it to ONE file via
include-rules — files/gbackup/latest.json.age, an age-encrypted export
written only while a backup passphrase is set (never plaintext to
Google; DB/prefs stay excluded). On a new device the OS restores the
blob, the Today screen detects an empty DB + present blob and offers
'Gjenopprett' — which demands the passphrase typed, since the Keystore
key never migrates. Clearing the passphrase deletes the blob.

scrypt work factor dropped 18→15: age's desktop default needs a 256 MiB
working set and OOM-crashed on-device (found the hard way); 15 = 32 MiB,
still real brute-force cost, and largeHeap covers decrypting foreign
files made at 18. Verified by instrumented round-trip tests (write →
empty DB → restore; wrong passphrase keeps DB empty; cleared passphrase
removes blob) — UI automation couldn't drive the multi-field dialog
reliably, so the proof lives in the test instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-06-11 19:20:48 +02:00
commit c9ee76387f
11 changed files with 264 additions and 24 deletions

View file

@ -77,9 +77,10 @@ ScheduleEngine — pure: next occurrence, daily consumption,
Room with exported schemas committed under `app/schemas/`; migrations are
proven by instrumented `MigrationTestHelper` tests before they touch real
data. Google Auto Backup is disabled (`allowBackup=false` + full
`dataExtractionRules` opt-out) — health data leaves the device only through
the app's own encrypted backup.
data. Google Auto Backup carries exactly one file: the app's own
age-encrypted export (written only while a backup passphrase is set) —
include-rules keep the database and settings out, and restoring on a new
device requires typing the passphrase, since Keystore keys never migrate.
## Backup format