mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
docs: write the Data safety form down, and correct what it says
The Play Console Data safety form said "App doesn't collect or share data" and "Data isn't encrypted". Both are wrong, and the pair contradicts itself: with nothing collected there is no encryption answer to give. The error was reading "collect" as "the developer receives it". Google defines it as transmitting data off the user's device, including by any SDK the app controls - and PRIVACY.md already lists six categories that do. An app requesting seven android.permission.health.* permissions while its public label reads "doesn't collect or share data" is the same kind of contradiction that got the Health Connect declaration rejected twice, and Google cross-checks the two forms against each other. The trap worth writing down: the user-initiated transfer and service provider carve-outs are exceptions to *sharing*, not to *collection*. Publishing a post is a transfer the user expects, so it need not be declared as sharing - but it is still collection. Two exemptions Amethyst genuinely earns and should claim: private DMs under the end-to-end encryption exception (NIP-17/NIP-44), and the My Fitness dashboard under the on-device-only exception, since it computes locally and transmits nothing. Only a workout the user publishes is collected. Verified against the code rather than assumed: no analytics, crash reporting, advertising or attribution SDK exists in any module, so the Analytics and Advertising purposes are never ticked; location is ACCESS_COARSE_LOCATION only; audio covers voice notes and NIP-53 rooms. Two items are left as explicit decisions rather than answered here, because neither is a form-filling choice: - Whether the push notification proxy processes on the developer's behalf (service provider, not shared) or is an independent operator (shared), which decides the Device or other IDs row. - Whether to narrow network_security_config.xml to loopback and .onion so "encrypted in transit" can truthfully be Yes. Today the global cleartextTrafficPermitted="true" keeps user-configured ws:// relays working, so the claim cannot be made unconditionally - but leaving it means the public label reads "Data isn't encrypted" beside a health permission request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TPShPiTfg16yesupcLgyqf
This commit is contained in:
@@ -40,6 +40,7 @@ Related code:
|
||||
- `service/workouts/health/HealthConnectManager.kt` — the only place the app touches Health Connect.
|
||||
- `ui/screen/loggedIn/workouts/health/HealthConnectRationaleActivity.kt` — the in-app rationale screen.
|
||||
- `PRIVACY.md` § "Health and fitness data (Health Connect)".
|
||||
- `docs/play-data-safety.md` — the Data safety form, which Google cross-checks against this one.
|
||||
|
||||
## Rejection 2 — the app contradicted this declaration, and what changed
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
# Play Console — Data safety form
|
||||
|
||||
Source text for the **Data safety** form (Play Console → *Monitor and improve → Policy and
|
||||
programs → App content → Data safety*). Keep this file and the form in sync, the same way
|
||||
`health-connect-play-declaration.md` tracks the Health apps declaration. Google cross-checks the
|
||||
two against each other and against `PRIVACY.md`; a disagreement between them is what a reviewer
|
||||
finds first.
|
||||
|
||||
## Why this file exists
|
||||
|
||||
The form previously said **"App doesn't collect or share data"** and **"Data isn't encrypted"**.
|
||||
Both are wrong, and the pair is self-contradictory — if nothing is collected there is no
|
||||
encryption answer to give.
|
||||
|
||||
The error came from reading "collect" as "the developer receives it". Google does not define it
|
||||
that way:
|
||||
|
||||
> **"Collect" means transmitting data from your app off a user's device.** This includes data
|
||||
> transmitted by libraries/SDKs and webviews controlled by your app.
|
||||
|
||||
Amethyst has no server, and that remains true and worth saying in the listing — but the test is
|
||||
whether data *leaves the phone*, not who receives it. `PRIVACY.md` § "Data sent off-device"
|
||||
already lists six categories that do.
|
||||
|
||||
**The trap:** Google's *user-initiated transfer* and *service provider* carve-outs are exceptions
|
||||
to **sharing**, not to **collection**. A user tapping Post is a transfer they expect, so it need
|
||||
not be declared as sharing — but it is still collection and must be declared.
|
||||
|
||||
## Two exemptions Amethyst genuinely earns
|
||||
|
||||
- **Private DMs — end-to-end encryption.** "User data that is sent off device, but that is
|
||||
unreadable by you or anyone other than the sender and recipient as a result of end-to-end
|
||||
encryption does not need to be disclosed." NIP-17/NIP-44 qualifies. Do not declare DM contents.
|
||||
- **The My Fitness dashboard — on-device only.** "User data accessed by your app that is only
|
||||
processed locally on the user's device and not sent off device does not need to be disclosed."
|
||||
The dashboard reads Health Connect, computes on device, displays, and transmits nothing. Only a
|
||||
workout the user *publishes* is collected.
|
||||
|
||||
## 1. Overall questions
|
||||
|
||||
| Question | Answer |
|
||||
| --- | --- |
|
||||
| Does your app collect or share any of the required user data types? | **Yes** |
|
||||
| Is all of the user data collected by your app encrypted in transit? | **See § 4 — decide before submitting** |
|
||||
| Do you provide a way for users to request that their data is deleted? | **Yes** — see § 5 |
|
||||
|
||||
## 2. Data types — collected
|
||||
|
||||
Everything below is **optional** (the user chooses to post, to upload, to enable push, to connect
|
||||
Health Connect) and its purpose is **App functionality** only. Amethyst ships no analytics, crash
|
||||
reporting, advertising or attribution SDK — verified: no Crashlytics, Firebase Analytics, AppsFlyer,
|
||||
Adjust, Sentry or Bugsnag anywhere in `libs.versions.toml` or any module's `build.gradle.kts`. So
|
||||
never tick Analytics, Advertising or marketing, Fraud prevention, or Personalization.
|
||||
|
||||
| Category | Type | Collected | Shared | Why |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Location | Approximate location | Yes | No | A geohash the user attaches to a post or a location chat. `ACCESS_COARSE_LOCATION` only — never precise. |
|
||||
| Personal info | Name | Yes | No | Display name in the user's published kind-0 profile, if they set one. |
|
||||
| Personal info | User IDs | Yes | No | The Nostr public key accompanies every published event and the push registration. |
|
||||
| Personal info | Other info | Yes | No | Profile bio, picture and website, if set. |
|
||||
| Financial info | Other financial info | Yes | No | Zap amounts appear in published zap events; NWC relays payment instructions to the user's own wallet. |
|
||||
| Health and fitness | Health info | Yes | No | Heart rate, **only** in a workout the user chooses to publish. |
|
||||
| Health and fitness | Fitness info | Yes | No | Exercise, distance, steps, calories, elevation, **only** in a published workout. |
|
||||
| Messages | Other in-app messages | Yes | No | Public posts, replies, articles. **Private DMs are excluded** — E2EE exemption. |
|
||||
| Photos and videos | Photos / Videos | Yes | No | Uploads to the media server the user selects. |
|
||||
| Audio | Voice or sound recordings | Yes | No | Voice notes, and speaking in a NIP-53 audio room. |
|
||||
| Calendar | Calendar events | Yes | No | NIP-52 calendar events the user publishes. |
|
||||
| Device or other IDs | Device or other IDs | Yes | **See § 3** | *(Play build, push enabled)* FCM registration token. |
|
||||
|
||||
## 3. Data types — not collected
|
||||
|
||||
**Contacts** (follow lists are Nostr public keys, not device contacts) · **Web browsing history** ·
|
||||
**App info and performance** (no crash or analytics SDK) · **Files and docs** (covered by photos,
|
||||
videos and audio) · **Personal info → Email address** · **Financial info → payment info or purchase
|
||||
history** · **Precise location** · **App activity**.
|
||||
|
||||
### The one item needing your decision
|
||||
|
||||
The **push token** path is the only place a third party plausibly receives data outside a
|
||||
user-initiated publish: `PRIVACY.md` says the token, public key and a preferred relay are
|
||||
"registered with Google Firebase Cloud Messaging so a notification proxy can wake the app."
|
||||
|
||||
- If that proxy processes the data **on your behalf**, the *service provider* exception applies →
|
||||
**Shared: No**.
|
||||
- If it is an independent operator, it is a third party → **Shared: Yes** for *Device or other IDs*.
|
||||
|
||||
Decide this from how the proxy is actually operated. Everything else in the table is No because
|
||||
publishing to relays is a user-initiated transfer the user reasonably expects.
|
||||
|
||||
> **This is the judgment call in the whole form.** Answering "shared with third parties" for
|
||||
> *Health and fitness* would make the public label read that Amethyst shares health data with third
|
||||
> parties — which is precisely the prohibited use the Health Connect policy names, and would
|
||||
> undercut the declaration. "Collected, not shared" is both accurate under Google's definition and
|
||||
> the answer to defend. Be ready to defend it: the user takes a deliberate action, sees the post,
|
||||
> and confirms it.
|
||||
|
||||
## 4. Encrypted in transit — resolve before submitting
|
||||
|
||||
Relays are `wss://`, media is HTTPS, DMs are NIP-44. **But** `amethyst/src/main/res/xml/network_security_config.xml`
|
||||
sets `cleartextTrafficPermitted="true"` on the global `base-config` so user-configured `ws://`
|
||||
relays keep working. Google allows "yes" only when encryption covers *all* collected data, so as
|
||||
the app stands today that cannot be claimed unconditionally.
|
||||
|
||||
Two honest ways forward:
|
||||
|
||||
1. **Narrow the config** to loopback and `.onion` only, and drop the global permit. Loopback never
|
||||
leaves the device and `.onion` is encrypted by Tor, so "encrypted in transit" becomes truthfully
|
||||
**Yes** — and it is a real security improvement. Cost: a user with a plain `ws://` relay breaks.
|
||||
2. **Leave it** and answer **No**. Accurate, but the public label then reads "Data isn't encrypted"
|
||||
next to a health-permission request — the same contradiction that has already cost two
|
||||
submissions.
|
||||
|
||||
Option 1 is the better outcome. It is a behaviour change for `ws://` relay users, so it is a
|
||||
maintainer's decision, not a form-filling one.
|
||||
|
||||
## 5. Data deletion
|
||||
|
||||
The developer runs no server and holds nothing, so there is no developer-held copy to request
|
||||
deletion of. Users can:
|
||||
|
||||
- wipe everything local by clearing app storage or uninstalling (`PRIVACY.md` § "Data stored on
|
||||
your device");
|
||||
- delete an account's data in-app;
|
||||
- request deletion of published events with NIP-09 — noting, as `PRIVACY.md` already says, that
|
||||
relays may not honour it and public content should be assumed permanent.
|
||||
|
||||
Say this plainly rather than claiming deletion guarantees the protocol cannot give.
|
||||
|
||||
## 6. Keep these three in sync
|
||||
|
||||
The Health apps declaration, this form, and `PRIVACY.md` are one statement split across three
|
||||
places. A reviewer reads all three. Before any resubmission, check that each data type here also
|
||||
appears in `PRIVACY.md`, and that nothing here contradicts
|
||||
`health-connect-play-declaration.md` § 4.
|
||||
Reference in New Issue
Block a user