Files
LibreMail/docs/play-data-safety.md
T
JMR-devandClaude Fable 5 3f7024d05d docs(play): add privacy policy, data-safety mapping, and permissions justification
Repo-actionable deliverables for the Google Play compliance work (issue #17),
every claim verified against the code and the built release artifacts:

- PRIVACY.md: user-facing privacy policy (device-local mail cache, optional
  SQLCipher encryption, traffic only to the user's own mail provider,
  on-device-only contacts autocomplete, strictly local opt-in debug reports,
  no ads/analytics/tracking SDKs).
- docs/play-data-safety.md: Play Data safety questionnaire mapping -- answer
  'no data collected/shared' with per-category code evidence, the policy
  exemptions relied on, a dependency audit, and a conservative fallback.
- docs/play-permissions.md: merged-manifest permission audit (incl. the
  WorkManager-injected WAKE_LOCK / RECEIVE_BOOT_COMPLETED) with paste-ready
  Console justifications for READ_CONTACTS, POST_NOTIFICATIONS, and the
  FOREGROUND_SERVICE_DATA_SYNC declaration + demo-video script.
- docs/play-compliance.md: verified targetSdk 37 (requirement: 35+), 16 KB
  page-size compliance (all packaged .so PT_LOAD p_align=0x4000, incl.
  sqlcipher-android 4.16.0), bundleRelease AAB check, the Gmail-app-password /
  no-CASA OAuth note, the console-steps checklist with drafted content-rating
  and listing answers, and repo findings (push-mail default vs docs, README
  minSdk/app-lock drift, debug-key release fallback).
- README.md: link PRIVACY.md and note the no-Google-OAuth/no-CASA status
  (fuller README pass stays issue #20).

Part of #17.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 21:52:40 -05:00

111 lines
8.4 KiB
Markdown

<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
# Google Play Data safety form — mapping (issue #17)
Fill-in guide for Play Console → **App content → Data safety**. Every answer below is grounded
in this repository's code; re-verify against source if the data flows change. Companion docs:
[`PRIVACY.md`](../PRIVACY.md) (the policy to link in the form),
[`play-permissions.md`](play-permissions.md), [`play-compliance.md`](play-compliance.md).
## How Play defines "collection", and why LibreMail declares none
Play's definition ([Play Console Help — Provide information for Google Play's Data safety
section](https://support.google.com/googleplay/android-developer/answer/10787469)): *"Collect"
means transmitting data from your app off a user's device*, with exemptions that do **not** need
to be disclosed:
1. **On-device access/processing** — data "only processed locally on the user's device and not
sent off device".
2. **End-to-end encryption** — data unreadable by anyone other than sender and recipient.
3. **Ephemeral processing** — data held in memory and "retained for no longer than necessary to
service a specific request in real time".
LibreMail's data flows fall under exemptions 1 and 3:
- The developer **operates no servers and receives no user data**. There is no analytics,
crash-reporting, or ad SDK in the dependency tree (see audit below), and the only
developer-directed channel that exists in code — opt-in debug-report upload
(`app/src/main/kotlin/org/libremail/reporting/ReportUploadWorker.kt`) — is dead in shipped
builds because `DEBUG_REPORT_ENDPOINT` defaults to `""` (`app/build.gradle.kts`), which makes
the upload worker fail without transmitting.
- All other traffic is the app doing its job as the user's mail agent against **servers the
user chose** (their own IMAP/SMTP provider; Microsoft's OAuth/Graph endpoints for the Outlook
account type). Each transfer services a specific user request in real time (sign-in, sync,
send, server search); the app retains nothing off-device and the developer can never access
any of it.
The same reasoning is the established practice of comparable open-source mail clients on Play
that declare no collection. If a Play reviewer pushes back, use the conservative alternative at
the bottom of this page — it is also truthful.
## Form answers
| Form question | Answer |
|---|---|
| Does your app collect or share any of the required user data types? | **No** |
| Is all of the user data collected by your app encrypted in transit? | Not asked when "No" above; for the record: **yes**, all connections are TLS (`ImapClient.kt`, `SmtpSender.kt` set `ssl.checkserveridentity=true`; `MailSecurity.NONE` is not offered in the UI — `ManualSetupScreen.kt:211`) |
| Do you provide a way for users to request that their data is deleted? | Not asked when "No" above; see account-deletion notes in [`play-compliance.md`](play-compliance.md) |
| Privacy policy URL | `https://github.com/JMR-dev/LibreMail/blob/main/PRIVACY.md` |
Result shown on the store listing: **"No data collected"** / **"No data shared with third
parties"**.
## Category-by-category evidence
Every Play data-safety category, the truthful answer, and where the code proves it:
| Play category | Collected? | Shared? | Evidence in code |
|---|---|---|---|
| Personal info → Name | No | No | Account display name stored in local Room DB only (`data/local/entity/AccountEntity` via `AccountRepositoryImpl.kt`); appears off-device only inside mail the user sends |
| Personal info → Email address | No | No | The user's own address is their mail login, sent only to their chosen provider to authenticate/send (`ImapClient.kt`, `SmtpSender.kt`, `GraphSender.kt`) — user-initiated, real-time, never to the developer |
| Personal info → User IDs | No | No | No developer-side accounts or IDs exist; OAuth tokens go only between the device and `login.microsoftonline.com` (`auth/OutlookAuthManager.kt`) |
| Financial info / Health / Location | No | No | No such APIs or permissions anywhere in the merged manifest (see [`play-permissions.md`](play-permissions.md)) |
| Messages → Emails | No | No | Mail syncs from the user's server *to* the device (`MailSyncer`), is cached locally (Room, optional SQLCipher — `di/DatabaseModule.kt`), and is transmitted only when the user sends a message to their own SMTP/Graph endpoint |
| Photos and videos / Audio files / Files and docs | No | No | Attachments are chosen via the system document picker (`ComposeScreen.kt` `OpenMultipleDocuments`, no storage permission), stored under `cacheDir` (`MailRepositoryImpl.kt:361`), and leave the device only inside mail the user sends |
| Calendar | No | No | No calendar API usage |
| Contacts | **No** | No | `contacts/ContactsRepository.kt` queries `ContactsContract` **on-device** for ≤8 autocomplete matches; results are held in memory for the compose screen. Nothing is uploaded — Play's on-device exemption applies |
| App activity (interactions, search history, installed apps) | No | No | No analytics SDK; server search sends the query string to the *user's own* IMAP server as an IMAP `SEARCH` command (user-initiated, ephemeral) |
| Web browsing | No | No | The reader WebView has JavaScript disabled and network loads blocked unless the user enables remote images (`ui/reader/HtmlBody.kt:62,98`) — and even then requests go to hosts referenced by the email, not to the developer |
| App info and performance (crash logs, diagnostics) | **No** | No | Crash/debug reports are written to local app storage only (`reporting/ReportStore.kt` → `filesDir/debug_reports`); upload requires an explicit user tap **and** a configured endpoint, and the endpoint is empty in this repo (`ReportSubmitter.isEnabled` → false) |
| Device or other IDs | No | No | No advertising ID (no `AD_ID` permission in the merged manifest), no device-ID reads; debug reports include only `Build.MANUFACTURER`/`MODEL`/OS version, and stay on device (`reporting/DiagnosticsCollector.kt`) |
### Dependency audit (no ads / analytics / tracking SDKs)
The complete runtime dependency list (`app/build.gradle.kts` + `gradle/libs.versions.toml`) is:
AndroidX (core, lifecycle, activity, navigation, webkit, Compose BOM, Room, DataStore,
WorkManager, Hilt-androidx), Dagger Hilt, kotlinx-coroutines, Eclipse Angus Mail (IMAP/SMTP),
AppAuth-Android (OAuth), and Zetetic SQLCipher. There is **no** Google Play Services, Firebase,
ad, analytics, or crash-reporting dependency, and the merged release manifest contains no
`com.google.android.gms.permission.AD_ID` permission (verified in
`app/build/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml`).
## Security-practices section of the form
- **Encrypted in transit:** yes — TLS everywhere, hostname verification pinned on
(`mail.<proto>.ssl.checkserveridentity=true` in `ImapClient.kt:480` / `SmtpSender.kt:50`).
- **Encryption at rest (optional extra credit, not a form field):** credentials are always
encrypted with an Android Keystore key (`data/security/KeystoreCrypto.kt`,
`CredentialStore.kt`); the mail cache can be SQLCipher-encrypted with a Keystore-sealed random
key (`data/security/DatabaseKeyStore.kt`, opt-in, default off —
`SettingsRepository.kt:44`).
- **Independent security review badge:** not requested (optional program).
## If anything changes, this form must change
| Future change | Data-safety impact |
|---|---|
| Configuring a real `DEBUG_REPORT_ENDPOINT` (issue #34) | Declare **App info and performance → Crash logs / Diagnostics**: collected, optional (user-initiated), not shared, encrypted in transit, user can delete (reports are deletable pre-submit) |
| Any opt-in telemetry from issues #10/#11 | Declare the specific types as collected + optional; backlog decision requires it stay strictly opt-in (F-Droid constraint) |
| Any new SDK with network access | Re-run this audit; SDKs count toward the form ("data transmitted by libraries/SDKs") |
## Conservative alternative declaration (only if Google rejects "no collection")
Declare the following, all with *Collected: yes · Optional: no · Shared: no · Processed
ephemerally: yes · Purpose: App functionality · Encrypted in transit: yes · Deletion: user can
delete data in-app (remove account)*:
- Personal info → Email address (account sign-in)
- Messages → Emails (sending/syncing the user's own mail with their provider)
Contacts, crash logs, and diagnostics remain **not collected** under any reading — they
demonstrably never leave the device in this codebase.