scan, privacy answers and credentials from a second game's first release (lessons 35-37): review-prompt needs rating context, not a bare "rate"; account signals skip import lines and module paths, config-only accounts say "from config"; accounts.inApp without deletion code stays a medium 5.1.1(v) finding; privacy-manifest --propose builds NSPrivacyCollectedDataTypes from appPrivacy.types and check warns on an empty list; appPrivacy.types is authoritative in the App Privacy sheet (unlisted = not collected), ad / support / AR types default to not collected without tracking or ad SDKs; Play advertising ID from SDKs, the AD_ID permission, app-ads.txt or the flag, never from a Device ID alone; scan.ignore[].evidence and warnings for entries that match nothing; empty credentials template, placeholder values count as missing (check, doctor)
This commit is contained in:
parent
86cd79dd45
commit
1366e7e231
34 changed files with 881 additions and 71 deletions
|
|
@ -641,7 +641,17 @@ export interface ShipConfig {
|
|||
* them in App Store Connect). `storebound scan` proposes them from the detected SDKs and code. */
|
||||
appPrivacy?: AppPrivacyConfig;
|
||||
/** `storebound scan`: findings the owner has looked at and accepts, by rule id (with the reason, shown in the report). */
|
||||
scan?: { ignore?: { rule: string; store?: 'apple' | 'play'; /** Only findings whose title contains this text (one check of a rule that has several). */ match?: string; reason: string }[] };
|
||||
scan?: {
|
||||
ignore?: {
|
||||
rule: string; store?: 'apple' | 'play';
|
||||
/** Only findings whose TITLE contains this text (one check of a rule that has several), case-insensitive. */
|
||||
match?: string;
|
||||
/** Only findings with an evidence line ("file:line text", as the report prints it) containing this text, e.g.
|
||||
* "src/main.ts:12" or a code fragment. Combine with match to narrow further. */
|
||||
evidence?: string;
|
||||
reason: string;
|
||||
}[];
|
||||
};
|
||||
/** Ads: `validate` checks that <app.website>/app-ads.txt is reachable and contains every line listed here. */
|
||||
ads?: { appAdsTxt?: string[] };
|
||||
/** Rendered store graphics dir (<renders>/<languageId>/<size>/NN.png|jpg). Defaults to shots.out. */
|
||||
|
|
@ -1069,8 +1079,8 @@ export function validateConfig(c: unknown): string[] {
|
|||
e.push(...validateAppPrivacyTypes(cfg.appPrivacy.types));
|
||||
}
|
||||
for (const [i, x] of (cfg.scan?.ignore ?? []).entries())
|
||||
if (!x || typeof x.rule !== 'string' || !x.rule || typeof x.reason !== 'string' || !x.reason.trim() || (x.store !== undefined && x.store !== 'apple' && x.store !== 'play') || (x.match !== undefined && (typeof x.match !== 'string' || !x.match.trim())))
|
||||
e.push(`scan.ignore[${i}]: { rule: '<rule id from storebound scan>', reason: 'why this is fine', store?: 'apple' | 'play', match?: '<text in the finding title>' }`);
|
||||
if (!x || typeof x.rule !== 'string' || !x.rule || typeof x.reason !== 'string' || !x.reason.trim() || (x.store !== undefined && x.store !== 'apple' && x.store !== 'play') || (x.match !== undefined && (typeof x.match !== 'string' || !x.match.trim())) || (x.evidence !== undefined && (typeof x.evidence !== 'string' || !x.evidence.trim())))
|
||||
e.push(`scan.ignore[${i}]: { rule: '<rule id from storebound scan>', reason: 'why this is fine', store?: 'apple' | 'play', match?: '<text in the finding title>', evidence?: '<text in an evidence line (file:line text)>' }`);
|
||||
if (cfg.ads?.appAdsTxt && !cfg.ads.appAdsTxt.every(l => typeof l === 'string' && l.split(',').length >= 3))
|
||||
e.push('ads.appAdsTxt: lines like "example-network.com, pub-123, DIRECT, abc123"');
|
||||
const seen = new Set<string>();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue