fix(legal): declare what the app sends as collected, not shared (D84) #44

Merged
whitlocktech merged 1 commits from fix/play-data-safety-collected into main 2026-10-29 17:30:31 -05:00
Member

Step 2 of M8's order (docs android/PLAN.md M8, decided by the org lead 2026-10-29): the data-safety audit and the generator.

Why

Play counts data that leaves the device as collected, whoever runs the server it lands on. PLAY_DATA_SAFETY.md answered "not collected" almost everywhere because we run no server the app talks to. That sentence is still true, and /privacy keeps it, but it was the wrong answer to the form's question.

The audit

I read every request body, query and header in the app's data/api/ on edge (19c2f77, the branch the first Play bundle ships from). More leaves the phone than section 2 listed:

What the app sends Console type
Sign-in: username, password, SSO code, 2FA code, tokens; username/password changes Personal info → User IDs
Game-account link codes, in-app game-account creation (name + password), unlinking a Steam id Personal info → User IDs
Contact form: name, email, message Name, Email address, Other in-app messages
Trusted-device token plus the phone's make and model as its label Device or other IDs
Push topic URL registered with the deployment Device or other IDs
User-Agent RunicGatewayApp/x (Android y) on every request App info and performance → Diagnostics
Staff: news posts, wiki categories, broadcasts, help-page replies, kick/ban reasons Other in-app messages, Other user-generated content
Notification read state, stream and channel preferences App activity → App interactions
Wiki, market and creature-atlas search text App activity → In-app search history

The server address and the inbox snapshot stay on the phone and are still "not collected". There are no SDKs, no crash reporter and no advertising ID. The dependency list and the manifest confirm that, and the release build forbids cleartext.

Everything collected is declared collected, not shared, encrypted in transit, required, as decided.

What changed

  • collection.mjs: new rows for the items above. PlayMapping now takes a list of types plus required and purposes, checked against PLAY_DATA_TYPES and PLAY_PURPOSES, the console's own vocabulary. The check caught a real error: the old file filed "Other user-generated content" under Messages, and on the form it is under App activity.
  • playDataSafety.mjs: one answer per data type, folded across rows. It also lists every type that is not collected, and answers the form's security questions: encrypted in transit (yes), processed ephemerally (no), and the free text.
  • Tests: the premise test now asserts the part that is still true, that nothing is shared. New tests check the vocabulary and that every collected row has types, required and purposes.
  • /privacy renders the new rows, so legal.lastUpdated moves to 2026-10-29. PLAN.md gains D84, which amends D33.

Not in this PR

  • Report, block and hide (step 3) will add its own rows when it lands.
  • Account deletion is an open question. The form asks about it, and the app starts accounts in two places: the sign-up link to the deployment's website, and in-app game-account creation. The platform has no self-service account deletion. The generated notes state these facts and don't answer the question. I'm asking the org lead separately.

Checked

check:terms, check:tokens, check:datasafety, astro check, npm test (57 pass), build, check:links, check:terms:built and check:csp all pass. check:terms:built caught two uses of "shard" on /privacy before the commit, and I replaced them with core's words. I didn't run check:facts, test:served or check:a11y; CI runs them.

  • AI-assisted (Claude Code)

🤖 Generated with Claude Code

https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY

Step 2 of M8's order (docs `android/PLAN.md` M8, decided by the org lead 2026-10-29): the data-safety audit and the generator. ## Why Play counts data that leaves the device as **collected**, whoever runs the server it lands on. `PLAY_DATA_SAFETY.md` answered "not collected" almost everywhere because we run no server the app talks to. That sentence is still true, and `/privacy` keeps it, but it was the wrong answer to the form's question. ## The audit I read every request body, query and header in the app's `data/api/` on `edge` (19c2f77, the branch the first Play bundle ships from). More leaves the phone than section 2 listed: | What the app sends | Console type | |---|---| | Sign-in: username, password, SSO code, 2FA code, tokens; username/password changes | Personal info → User IDs | | Game-account link codes, in-app game-account creation (name + password), unlinking a Steam id | Personal info → User IDs | | Contact form: name, email, message | Name, Email address, Other in-app messages | | Trusted-device token **plus the phone's make and model** as its label | Device or other IDs | | Push topic URL registered with the deployment | Device or other IDs | | User-Agent `RunicGatewayApp/x (Android y)` on every request | App info and performance → Diagnostics | | Staff: news posts, wiki categories, broadcasts, help-page replies, kick/ban reasons | Other in-app messages, Other user-generated content | | Notification read state, stream and channel preferences | App activity → App interactions | | Wiki, market and creature-atlas search text | App activity → In-app search history | The server address and the inbox snapshot stay on the phone and are still "not collected". There are no SDKs, no crash reporter and no advertising ID. The dependency list and the manifest confirm that, and the release build forbids cleartext. Everything collected is declared **collected, not shared, encrypted in transit, required**, as decided. ## What changed - **`collection.mjs`**: new rows for the items above. `PlayMapping` now takes a list of `types` plus `required` and `purposes`, checked against `PLAY_DATA_TYPES` and `PLAY_PURPOSES`, the console's own vocabulary. The check caught a real error: the old file filed "Other user-generated content" under Messages, and on the form it is under App activity. - **`playDataSafety.mjs`**: one answer per data type, folded across rows. It also lists every type that is not collected, and answers the form's security questions: encrypted in transit (yes), processed ephemerally (no), and the free text. - **Tests**: the premise test now asserts the part that is still true, that **nothing is shared**. New tests check the vocabulary and that every collected row has types, `required` and purposes. - `/privacy` renders the new rows, so `legal.lastUpdated` moves to 2026-10-29. PLAN.md gains **D84**, which amends D33. ## Not in this PR - Report, block and hide (step 3) will add its own rows when it lands. - **Account deletion is an open question.** The form asks about it, and the app starts accounts in two places: the sign-up link to the deployment's website, and in-app game-account creation. The platform has no self-service account deletion. The generated notes state these facts and don't answer the question. I'm asking the org lead separately. ## Checked `check:terms`, `check:tokens`, `check:datasafety`, `astro check`, `npm test` (57 pass), `build`, `check:links`, `check:terms:built` and `check:csp` all pass. `check:terms:built` caught two uses of "shard" on `/privacy` before the commit, and I replaced them with core's words. I didn't run `check:facts`, `test:served` or `check:a11y`; CI runs them. - [x] AI-assisted (Claude Code) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
wtclaude added 1 commit 2026-10-29 08:20:54 -05:00
fix(legal): declare what the app sends as collected, not shared (D84)
All checks were successful
PR checks / checks (pull_request) Successful in 2m33s
d6d5a70ed4
Play counts data that leaves the device as collected, whoever runs the
server it lands on. The generated Data Safety notes answered "not
collected" because we run no server, which is true of /privacy and the
wrong answer to the form (org lead, 2026-10-29, Android PLAN.md M8).

An audit of the app's edge (every body, query and header in data/api/)
found more leaving the phone than section 2 listed. New rows: the
User-Agent, game-account links and creation, the Contact form, staff
posts and moderation, notification read state and preferences, search
text. The sign-in and trusted-device rows now say what is sent (the
credentials, the device's make and model).

- PlayMapping: a list of console types, plus required and purposes,
  checked against PLAY_DATA_TYPES / PLAY_PURPOSES (the first version
  filed "Other user-generated content" under Messages).
- The generator answers per data type, lists every type not collected,
  and answers the form's security questions.
- The premise test now asserts what is still true: nothing is shared.
- legal.lastUpdated moves to 2026-10-29 with /privacy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
whitlocktech merged commit 338cc6f3de into main 2026-10-29 17:30:31 -05:00
whitlocktech deleted branch fix/play-data-safety-collected 2026-10-29 17:30:33 -05:00
Sign in to join this conversation.
No description provided.