Combobulating

ADR-0007 — Memory Graph Encryption: Secure Enclave / Keystore + SQLCipher

Status

Accepted (2026-05-07). Source: plan.md §14, §20.2; technical_spec.md §6.1, §6.7, §11.

Context

The memory graph holds the user’s structured personal history: events, transactions, conversations (summaries only), health snapshots, actions, and reasoning traces. ADR-0005 commits to on-device storage; this ADR commits to the encryption design.

Adversaries (threat_model.md):

Constraints:

Decision

Aura uses SQLCipher 4.x with AES-256 as the at-rest cipher for memory.sqlite. The cipher passphrase is derived from a platform-managed key wrapped by hardware where available.

iOS. A 32-byte seed is generated once via SecRandomCopyBytes and stored in the Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly plus kSecAttrAccessControl requiring user presence. The Secure Enclave wraps the seed where available (A12 Bionic and later). On launch, the seed is fetched and an HKDF-SHA256 derivation produces the SQLCipher passphrase: passphrase = HKDF-SHA256(seed, salt=device_id, info="aura.memory.v1", len=32). File protection is set to NSFileProtectionComplete so the DB file is unreadable when the device is locked. iCloud Backup is disabled for the file via kCFURLIsExcludedFromBackupKey.

Android. A symmetric AES-256 GCM key is generated in the AndroidKeyStore under the alias aura_memory_v1 with setUserAuthenticationRequired(true) and AUTH_BIOMETRIC_STRONG. A static seed encrypted under this key is stored in EncryptedSharedPreferences. On launch, the seed is decrypted (which forces a biometric / device-credential prompt if the auth window has expired) and SQLCipher is initialised with the resulting passphrase. android:allowBackup="false" is set on the manifest to disable Android Auto Backup.

sqlite-vss coexistence. load_extension may be disabled in default Android SQLite builds. The team will compile sqlite-vss as a static library and load it via db.execSQL("SELECT load_extension('libvss0.so')") after the SQLCipher passphrase is applied. [TEAM TO VERIFY in Week 4 against the asg017/sqlite-vss issue tracker that the static-link path works on AndroidX SQLite + SQLCipher 4.x. Reference: https://github.com/asg017/sqlite-vss]

Panic wipe. The 5-tap power gesture (technical_spec.md §11.6) wipes the seed from Keychain / Keystore (irreversible), revokes OAuth refresh tokens via the oauth2.googleapis.com/revoke endpoint, deletes the app sandbox files, and relaunches into fresh-install state. A 3-second hold-cancel window protects against accidental wipe.

Audit log. Every read, write, delete, export, and wipe operation produces an audit_log row with hash_chain = sha256(prev_hash || row_canonical). A daily Merkle root is computed at 00:05 local time over the previous day’s rows and stored in merkle_daily. The latest 30 roots are surfaced in Settings.

Consequences

Positive:

Negative / costs:

Alternatives

End of ADR-0007.