diff --git a/docs/design/BRAND_GUIDE.md b/docs/design/BRAND_GUIDE.md index 4deb64f..beb0595 100644 --- a/docs/design/BRAND_GUIDE.md +++ b/docs/design/BRAND_GUIDE.md @@ -731,7 +731,18 @@ Example: or: -**“Create an onboarding illustration explaining encrypted local data storage using this visual system.”** +**“Create an onboarding illustration explaining that cycle history stays on this device using this visual system.”** + +Note the wording, because it changed. This example used to say *encrypted local +data storage*, and the database is **not** encrypted — see *Deliberately out of +scope* in [`../security/SECURITY.md`](../security/SECURITY.md), which owns that +claim and explains why. + +A prompt is a claim the moment somebody draws it. A padlock on an onboarding +screen is a promise, and this one would have been a promise the product does not +keep — which is worse than saying nothing, because the true story here is +genuinely good: the data never leaves, so there is no server to breach and +nothing to sell. Draw that instead. --- diff --git a/docs/security/SECURITY.md b/docs/security/SECURITY.md index 5b6545a..b5d0df0 100644 --- a/docs/security/SECURITY.md +++ b/docs/security/SECURITY.md @@ -185,6 +185,33 @@ Written down so an unknown gap becomes a known one: - **A rooted or compromised device.** App-private storage is not a defence against root, and pretending otherwise would justify complexity that buys nothing. +- **Encryption of the cycle database at rest.** Evaluated on 2026-08-19 and + deliberately not built. Android's file-based encryption already covers the + case people picture: a phone that is off, or that has not been unlocked since + boot, has app-private storage that cannot be read at all. What app-level + encryption would add is narrower — a file *copied off* the device and read + elsewhere, a phone with no screen lock set at all, and crypto-shredding on + delete. + + This app's own architecture decides the rest. `ReminderCoordinator` collects + Room flows at **every process start**, including processes started with no + Activity after a reboot, and `ReminderWorker` reads the forecast on a + schedule. So the key would have to be available with no user present — which + is the same availability condition the platform key already has. A key that + anything running as this app can reach unattended defends a **file**, not a + **process**, and against every adversary listed above it adds nothing. + + The cost is recorded so nobody has to re-derive it: roughly +1 MB downloaded + and +2 MB installed on a 2 MB app. The risk matters more than the size. A key + held in the Android Keystore can be lost in the field, and with platform + backup off there is no second copy and no telemetry that would tell us it + happened. Today, failing to open the database is a bug. Afterwards it would be + somebody's history, gone, silently. + + **What would reverse this:** a key bound to the app lock's own user secret, + once export exists and has given the user a copy they control. That version + does defend a seized, unlocked phone — and it is only defensible once losing + the key is not the same as losing everything. - **Forensic recovery of deleted rows.** Delete My Data removes the data through the database; it does not overwrite flash. - **Someone who knows the unlock PIN.** Biometric/PIN gating raises the bar over diff --git a/docs/security/SECURITY_CHECKLIST.md b/docs/security/SECURITY_CHECKLIST.md index 5233cad..eac75b7 100644 --- a/docs/security/SECURITY_CHECKLIST.md +++ b/docs/security/SECURITY_CHECKLIST.md @@ -51,6 +51,7 @@ The one group that is not generic. Every item proves part of ### What the device and the lock screen expose - [ ] Cycle history is in app-private storage only — proves nothing landed in shared or external storage +- [ ] No shipped string, illustration, screenshot or store listing says the cycle database is **encrypted** — proves the product does not claim a control [`SECURITY.md`](SECURITY.md) records as deliberately out of scope. It is on this list because the claim is attractive, easy to draw, and was already sitting in an artwork brief before anybody checked whether it was true - [ ] Lock-screen text in **Discreet** and **Maximum privacy** modes contains no menstrual detail, checked on a real lock screen at every mode — proves the notification privacy feature actually works, which is the breach most likely to happen - [ ] The health database's inclusion in platform auto-backup is a **recorded decision**, not a default — proves the local-first promise is not undone by the OS - [ ] Release build has verbose logging off and no raw cycle date in any crash payload — proves §45's logging rule survived the build type