storebound scan: Google Play parity

Play rules verified against the live Policy Center / Play Console Help pages
(checked 2026-10-11, cited in cli/lib/scan/play-rules.ts): restricted
permissions and their declarations (background location, photo / video, All
files access, AccessibilityService, REQUEST_INSTALL_PACKAGES, Health Connect,
full-screen intent, VpnService), prominent disclosure and the in-app privacy
policy, Data safety vs SDK disclosures, Families (certified ad SDKs, AD_ID,
location), AD_ID on targetSdk 33+, 16 KB page sizes (Unity, React Native, NDK,
AGP, prebuilt .so), testing for new personal accounts, Play metadata, ads at
launch, payments outside Play Billing, impersonation. Detection reads manifest
components, accessibility configs, strings.xml, native toolchain versions and a
local build's merged manifest. New fixtures and tests; README table per store.
This commit is contained in:
CRTkafa 2026-10-12 00:05:35 +08:00
parent ae58948abd
commit 84ef94253c
57 changed files with 1360 additions and 80 deletions

View file

@ -16,16 +16,25 @@ prove, with guideline, evidence (file:line), fix and skill: 2.1(a) demo account
deletion, 4.8 Sign in with Apple, 3.1.1 Restore Purchases and outside payment, 3.1.2 subscription info, 5.1.2 ATT,
5.1.1(ii) purpose strings, 5.1.1(i) privacy policy link, privacy manifest (ITMS-91053 / 91061), 2.3.10 other
platforms, 2.3.7 keywords, 2.3.8 "For Kids", 4.2 web wrappers, 2.5.1 UIWebView and private URL schemes, 2.5.5 IPv6,
ATS, export compliance, 1.3 kids ads, 5.6.1 / 3.2.2(x) review prompts, privacy label vs SDKs, and the Play target SDK,
delete-account URL, App access, foreground service, exact alarm, QUERY_ALL_PACKAGES, SMS / call log and package name
rules. After a rejection, run it too: the same cause often hides elsewhere. A finding the owner decided is fine goes
ATS, export compliance, 1.3 kids ads, 5.6.1 / 3.2.2(x) review prompts, privacy label vs SDKs. On Google Play: target
SDK, delete-account URL, App access, package name, the restricted permissions and their Console declarations
(background location, photo / video, All files access, AccessibilityService, REQUEST_INSTALL_PACKAGES, Health Connect,
full-screen intent, VpnService, foreground services, exact alarms, QUERY_ALL_PACKAGES, SMS / call log), prominent
disclosure and the in-app privacy policy (User Data), Data safety vs the SDKs' disclosures, Families (certified ad SDKs,
AD_ID, location) from `play.targetAges`, the AD_ID permission, 16 KB page sizes (Unity, React Native, NDK, AGP, prebuilt
.so), the closed-testing requirement of new personal accounts, Play metadata (title length, emoji, caps, claims,
keyword repetition, other apps), interstitials at launch, payments outside Play Billing and brand names in the package
(low "check"). When the project was built locally it also reads the merged manifest under build/intermediates. After
a rejection, run it too: the same cause often hides elsewhere. A finding the owner decided is fine goes
into `scan.ignore` with the reason (never silence one yourself).
Checked at review, not by scan (look at these by hand): crashes and hangs on the reviewer's device (2.1; `store-health`),
links that are dead at review time (`storebound validate` checks the configured URLs), screenshots and description
that do not match the app (2.3, `store-screenshots`), poor or unfinished UI (4.0), "too simple" and spam / template
apps (4.2, 4.3; `portfolio-originality`), IAPs that fail in the sandbox (2.1(b), `store-iap-setup`), privacy policy
content (5.1.1(i), `privacy-policy`), local law (5.2).
content (5.1.1(i), `privacy-policy`), local law (5.2). Play: whether a restricted permission serves the core purpose
(the declaration and its video decide), the wording and timing of a prominent disclosure, ad placements in Unity /
Flutter / React Native code, the Health apps declaration, alignment inside prebuilt .so files.
## 1. Triage (pick one, say why)
| route | when | what you do |
@ -81,9 +90,22 @@ Plain, polite, specific; one guideline per paragraph; no blame, no threats, no "
what the SDKs collect, or remove the SDK; then resubmit from Publishing overview.
- **Ads policy / disruptive ads**: no interstitials at launch, on exit or mid-gameplay; close buttons visible; rewarded
ads only opt-in. Ads must not imitate system UI.
- **Families**: if the target audience includes children, only Families-certified ad SDKs, no ad id, teacher-approved
rules. If the app is not for children, make sure store art and the target audience form say so.
- **Metadata**: no keyword stuffing, no "best/#1", no unrelated keywords (`aso-basics`).
- **Families**: if the target audience includes children, only Families Self-Certified Ads SDKs (answer/9859768;
AppLovin left the program), no advertising ID in child-only apps (remove AD_ID with tools:node="remove"), no location
in child-only apps, a neutral age screen for mixed audiences. If the app is not for children, make sure store art and
the target audience form say so. Set `play.targetAges` so `storebound scan` checks it.
- **Sensitive permissions** (background location, photo / video, All files access, AccessibilityService, install
packages, Health Connect, full-screen intent, VpnService, SMS / call log, QUERY_ALL_PACKAGES): the rejection names the
permission. Either remove it from the merged manifest (an SDK may add it: tools:node="remove") or fill the App content
declaration with the core feature and a short video that shows the prominent disclosure and the runtime prompt.
- **Prominent disclosure / privacy policy (User Data)**: an in-app disclosure right before the permission prompt that
names the data and its use, an affirmative accept, and a privacy policy linked inside the app and in Play Console.
- **16 KB page sizes**: Play Console flags native libraries that are not 16 KB aligned; from 2027-02-01 such updates
cannot be released. Update Unity / React Native / NDK / AGP (scan names the version) and rebuild.
- **Metadata**: title 30 characters or less, no emoji, ALL CAPS, "best / #1 / free / new / sale" in the title, no
keyword stuffing or unrelated keywords, no other apps' names (`aso-basics`).
- **Impersonation**: a name, icon or package that suggests another brand; rename (the package cannot change, so a new
app may be needed) or show the written permission in the appeal.
- **Strikes and suspensions**: one policy strike affects the account; repeated strikes can terminate it. Fix first,
then appeal from the notice with the change described. Do not upload the same app under a new package to dodge it.
- **Rejected updates**: the previous version stays live; fix and send changes for review again.