Files
runicgateway.com/PLAY_DATA_SAFETY.md
wtclaude d6d5a70ed4
All checks were successful
PR checks / checks (pull_request) Successful in 2m33s
fix(legal): declare what the app sends as collected, not shared (D84)
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
2026-10-29 08:20:25 -05:00

19 KiB
Raw Blame History

Google Play Data Safety — the answers, and what they are based on

The Play Console asks, for every category of data, whether the app collects it, whether it is shared, whether collection is required or optional, and why. This file holds the answers for the Runic Gateway Android app, generated from the same inventory the published privacy policy renders — see /privacy, section 2.

This is not a filled-in form. Play’s definitions change and no check here can read them. Every answer below is a fact about the code with the reasoning attached; read the console’s current wording against them when you fill the form. What this file exists to prevent is somebody answering from memory about what the app stores.

The premise every answer rests on

We operate no server the app talks to. The app ships pointed at nothing: its first screen asks for the address of a Runic Gateway deployment and validates it before anything else in the app runs. That deployment belongs to whoever runs that community. Data therefore travels from the device to their server, and there is no endpoint of ours anywhere in the path — not for content, not for telemetry, and not for crash reports, of which there are none.

Play still counts what the app sends to that server as collected: the data leaves the device, and the form does not ask whose server it lands on. So everything the app sends is declared collected, not shared, and required, which is the conservative answer and the accurate one (org lead, 2026-10-29). What stays true, and what /privacy says, is that none of it reaches us.

Data collection and security

  • Does the app collect or share any of the required user data types? Yes, it collects them (below). It shares none.
  • Is all of the user data collected by the app encrypted in transit? Yes. The release build’s network security config sets cleartextTrafficPermitted="false" for every connection, and the app refuses an http deployment address outside debug builds. Read from: app/src/main/res/xml/network_security_config.xml, core/net/ServerUrl.kt.
  • Is any of it processed ephemerally? No. The deployment keeps what it receives, under its operator’s policy, and we cannot promise otherwise on its behalf.
  • Free text on security practices, where the console offers it: credentials are held in Android’s encrypted storage (AES-256-GCM via Jetpack Security), and push notifications carry no content: a relay receives a stream name and a reference, and the app fetches the actual message over its own authenticated connection.

Data types the app collects

Category Data type Collected Shared Required Purposes From
Personal info Name Yes No Yes App functionality Your name, email address and message, if you use Contact
Personal info Email address Yes No Yes App functionality Your name, email address and message, if you use Contact
Personal info User IDs Yes No Yes App functionality; Fraud prevention, security, and compliance; Account management Your sign-in, and the tokens it issues; Linking a game account
Messages Other in-app messages Yes No Yes App functionality Your name, email address and message, if you use Contact; What you post and send from the app
App activity App interactions Yes No Yes App functionality; Personalization Which notifications you have read, and which you want
App activity In-app search history Yes No Yes App functionality What you search for
App activity Other user-generated content Yes No Yes App functionality What you post and send from the app
App info and performance Diagnostics Yes No Yes App functionality; Fraud prevention, security, and compliance The app’s version and your Android version
Device or other IDs Device or other IDs Yes No Yes App functionality; Fraud prevention, security, and compliance; Account management The trusted-device token and the device’s name, if you asked for one; Push registration, if you turn notifications on

Data types the app does not collect

The form lists every type, so every one is answered. Vocabulary read from Play Console Help on 2026-10-29; check it against the console’s current list.

  • Location: none collected.
  • Personal info: Address, Phone number, Race and ethnicity, Political or religious beliefs, Sexual orientation, Other info: not collected.
  • Financial info: none collected.
  • Health and fitness: none collected.
  • Messages: Emails, SMS or MMS: not collected.
  • Photos and videos: none collected.
  • Audio files: none collected.
  • Files and docs: none collected.
  • Calendar: none collected.
  • Contacts: none collected.
  • App activity: Installed apps, Other actions: not collected.
  • Web browsing: none collected.
  • App info and performance: Crash logs, Other app performance data: not collected.

Held on the device only

Not collected, because none of it leaves the phone; listed so nobody declares it by mistake.

  • The address of the deployment you chose. It never leaves the phone. It is the destination of requests, not the contents of one.
  • A snapshot of your notifications, so the inbox opens without a signal. The snapshot is written on the phone from data the deployment had already delivered. It is not uploaded anywhere.
  • No analytics, no crash reporting, no advertising. No crash reporter, no advertising ID, no analytics identifier, and no library that would generate one is linked into the build.

Each answer, and why it is the truthful one

Your sign-in, and the tokens it issues

Personal info → User IDs. Collected, not shared, required.

Purposes: App functionality; Account management; Fraud prevention, security, and compliance.

Signing in sends your username and password to the deployment, or with single sign-on a one-time code from the provider, plus a two-factor code when the account has one. The deployment answers with an access and a refresh token and the username, role and account id they belong to. The tokens are held in encrypted storage on the device (AES-256-GCM through Jetpack Security) and are sent to exactly one place: the deployment that issued them, with every request. Changing your username or password, setting up two-factor sign-in and generating recovery codes from the app go to the same place.

  • Why that answer: The username, password and tokens leave the phone for the deployment the user chose, and Play counts that as collection whoever runs the server. None of it reaches us. Security is a purpose because the deployment checks two-factor codes and trusted devices with it.
  • Retention: On the device until you sign out
  • In detail: Signing out clears them; uninstalling the app removes them with it.
  • Read from: data/api/AuthApi.kt, data/api/SsoApi.kt, data/api/MeApi.kt, core/auth/EncryptedTokenStore.kt

The trusted-device token and the device’s name, if you asked for one

Device or other IDs → Device or other IDs. Collected, not shared, required.

Purposes: Account management; Fraud prevention, security, and compliance.

Ticking “trust this device” during two-factor sign-in stores an opaque token so the deployment can skip the second factor next time. It lives in its own encrypted store, deliberately separate from the session, because it has to outlive a sign-out to be worth anything — and the deployment holds only a hash of it, so the copy on your phone is the only usable one. The phone’s make and model, such as “Samsung SM-S918B”, goes with the request as the device’s label, so you can tell it apart in the deployment’s list of trusted devices. It is a model name, not a hardware identifier.

  • Why that answer: The token identifies this device to the deployment, and the label is sent with it. Both are presented to the deployment that minted the token and to nothing else.
  • Retention: On the device until it expires or you revoke it
  • In detail: Thirty days, and revocable at any time from the deployment’s Trusted Devices screen, which is also where it can be revoked if the phone is lost.
  • Read from: core/auth/EncryptedTrustTokenStore.kt, core/auth/DeviceNameProvider.kt

The app’s version and your Android version

App info and performance → Diagnostics. Collected, not shared, required.

Purposes: App functionality; Fraud prevention, security, and compliance.

Every request carries a User-Agent such as “RunicGatewayApp/0.6.0 (Android 15)”. It is there because a deployment turns away traffic that looks like a scanner, and it names nothing about you or the phone beyond those two version numbers.

  • Why that answer: It is technical information about the app and the device, and it is sent with every request. The deployment uses it to tell the app from a scanner.
  • Retention: Under the deployment operator’s policy
  • In detail: A deployment’s access log may keep it, as any web server keeps the user agent of a browser.
  • Read from: di/NetworkModule.kt, core/net/UserAgentInterceptor.kt

The address of the deployment you chose

No console type. Not collected. Stored on the device only.

The app ships pointed at nothing and asks for an address on first run. That address is stored in ordinary preferences rather than encrypted storage — it is not a secret, it is the equivalent of a bookmark — and it is what every other screen in the app talks to.

  • Why that answer: It never leaves the phone. It is the destination of requests, not the contents of one.
  • Retention: On the device until you change it or uninstall
  • Read from: core/prefs/ServerPreferences.kt

Push registration, if you turn notifications on

Device or other IDs → Device or other IDs. Collected, not shared, required. Mention the relay in the free-text security section if the console asks.

Purposes: App functionality.

Push is off until you enable it. When you do, the app mints a random, unguessable topic name on the notification relay the deployment nominates, and registers that topic’s URL with the deployment so it has somewhere to send a nudge. What actually travels through the relay is content-free — a stream name and a reference, never the message — and the app then fetches the real content over its authenticated connection to the deployment. A leaked topic name therefore reveals nothing, which is the reason the relay needs no account and holds nothing about you.

  • Why that answer: The topic URL is an address for this device, and the app sends it to the deployment. The relay is the one the deployment nominates to deliver its own notifications, so it carries them on the deployment’s behalf rather than receiving a share. It only ever sees a topic name and a tickle; the app pulls the content itself, authenticated. No hop reaches a server we operate.
  • Retention: Until you turn push off, sign out, or uninstall
  • In detail: Signing out or disabling push unregisters the device with the deployment and discards the topic. The relay retains whatever its own operator configures it to; if the deployment points at a relay it does not run, that relay is a third party to both of us, and it still only ever sees a tickle.
  • Read from: core/push/NtfyTopic.kt, core/push/PushPreferences.kt, data/api/NotificationsApi.kt

Linking a game account

Personal info → User IDs. Collected, not shared, required.

Purposes: App functionality; Account management.

Linking your site account to a game sends the one-time code the game gave you. Where a game server offers it, the app can also create a new game account, which sends the account name and password you chose. Unlinking sends the id being unlinked, such as a Steam id. All of it goes to the deployment, which keeps the link against your site account.

  • Why that answer: Game account names, Steam ids and link codes identify a player, and the app sends them to the deployment.
  • Retention: Held by the deployment until you unlink, under its operator’s policy
  • Read from: data/api/PlayerShardApi.kt, data/api/PlayerRustApi.kt, data/api/MeApi.kt

Your name, email address and message, if you use Contact

Personal info → Name; Personal info → Email address; Messages → Other in-app messages. Collected, not shared, required.

Purposes: App functionality.

The Contact screen sends the name, email address and message you type to the deployment, which passes them on to its operator by email so they can reply. Nothing is sent until you press send.

  • Why that answer: The form’s three fields leave the phone for the deployment, whose operator receives them.
  • Retention: Held by the deployment’s operator, under their policy
  • In detail: The message is delivered to the operator’s mailbox, and kept as long as they keep it.
  • Read from: data/api/PublicApi.kt, data/api/dto/ContactDto.kt

What you post and send from the app

Messages → Other in-app messages; App activity → Other user-generated content. Collected, not shared, required.

Purposes: App functionality.

Most of the app is reading. What it sends is what you write, and most of that is for staff: news posts, wiki categories, a broadcast to the game, the answer to a player’s help page, and the account and reason on a kick or a ban. All of it is a live write to the deployment. Apart from the notification snapshot described below, nothing is cached for offline use and nothing is duplicated anywhere else — the app with no signal is an app with almost no content, which is a limitation and also an accurate description of where the data lives.

  • Why that answer: Posts, broadcasts and replies are written to the community’s own installation. We have no copy, no access and no way to obtain one.
  • Retention: Held by the deployment, under its operator’s policy
  • Read from: data/api/AdminApi.kt

Which notifications you have read, and which you want

App activity → App interactions. Collected, not shared, required.

Purposes: App functionality; Personalization.

Opening a notification marks it read on the deployment, so the badge agrees on every device you use. Your choice of notification streams, and of which arrive by push or email, is saved there too.

  • Why that answer: Read state and preferences are actions in the app that are sent to, and kept by, the deployment.
  • Retention: Held by the deployment, under its operator’s policy
  • Read from: data/api/NotificationsApi.kt

What you search for

App activity → In-app search history. Collected, not shared, required.

Purposes: App functionality.

Searching the wiki, a game’s market or its creature atlas sends what you typed to the deployment, because the deployment does the searching. The app keeps no history of searches.

  • Why that answer: Search text leaves the phone in the request to the deployment.
  • Retention: Under the deployment operator’s policy
  • In detail: The query is part of the request’s address, so a deployment’s access log may record it. That is why it is declared rather than called ephemeral.
  • Read from: data/api/PublicApi.kt

A snapshot of your notifications, so the inbox opens without a signal

No console type. Not collected. Stored on the device only.

The app keeps the most recent notifications it has already fetched — at most thirty, and only the first page — on the device, so opening the inbox shows you what you had rather than a spinner. It is a copy of what the deployment already sent you and it is refreshed from there; nothing is written here that was not read from your own account. It is scoped to the account that fetched it, so a second person signing in on the same phone is never shown the first one’s messages.

  • Why that answer: The snapshot is written on the phone from data the deployment had already delivered. It is not uploaded anywhere.
  • Retention: Until you sign out, or the thirty are pushed out by newer ones
  • In detail: Signing out deletes the snapshot outright. It lives in the app’s ordinary preference store rather than the encrypted one — sign-in tokens are the thing that store is for — which is worth stating plainly: on a device where someone has root, these are readable, and they are notification bodies rather than credentials.
  • Read from: core/inbox/DataStoreInboxCache.kt, data/repository/AuthRepository.kt

No analytics, no crash reporting, no advertising

App info and performance → Crash logs. Not collected. The advertising ID declaration is "no".

There is no third-party SDK in the app at all — no Firebase, no Crashlytics, no advertising identifier, no measurement library. That is checkable rather than claimed: it is what the dependency list and the manifest say, and a build that gained one would gain permissions with it.

  • Why that answer: No crash reporter, no advertising ID, no analytics identifier, and no library that would generate one is linked into the build.
  • Retention: Nothing to retain
  • Read from: app/build.gradle.kts, app/src/main/AndroidManifest.xml

The rest of the listing

  • Privacy policy URL: /privacy on this site. It is the URL Play is given, and section 2 of it is about the app specifically.
  • Target audience: adults. The beta is stated as 18 or older (D31); the app contains no content directed at children and no age verification.
  • Account deletion: the app creates no account with us — an account belongs to the deployment the user chose, and is deleted there. The only list we hold is the beta signup, which is erased on request; /privacy section 4 says how to ask.
  • Accounts the app can start: the sign-in screen links to the deployment’s own sign-up page, which opens in the browser, and where a game server offers it the app creates a game account on it. The platform has no self-service account deletion today; a deployment’s admin deletes accounts from its admin panel. Read from: ui/auth/LoginViewModel.kt, data/api/PlayerShardApi.kt.
  • Data deletion request URL: the contact address published on /privacy, which is read from the mounted brand.json rather than typed anywhere in the source (D13).

What the website collects, for the same reviewer

Not part of the Data Safety form — that form is about the app — but a reviewer who follows the privacy policy URL lands on a page covering three things, so it is worth knowing which of them the site itself is responsible for:

  • Your email address — Until the beta ends, or until you ask.
  • The wording you agreed to, and when — For the life of the row.
  • A one-way hash of your IP address — never the address — With the row; the rate-limit log is pruned after 48 hours.
  • Your browser’s user-agent string, truncated — With the row; blanked on removal.
  • The web server’s access log — Short-term operational retention, then rotated away.

Last generated from data dated 2026-10-29. Regenerate with npm run play:datasafety after any change to what the app stores.