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
7
tests/fixtures/scan/capacitor-game/README.md
vendored
Normal file
7
tests/fixtures/scan/capacitor-game/README.md
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# capacitor-game fixture
|
||||
|
||||
A made-up Capacitor 8 farm game (first release) with the patterns a real-use report found: a module named
|
||||
`buildings/register` imported for its side effects, production rates next to a `bonus` object, accounts and in-app
|
||||
deletion claimed only by the config (no auth or deletion code), five App Privacy types in `appPrivacy.types` while the
|
||||
app's PrivacyInfo.xcprivacy lists none, a random install id declared as Device ID in Data safety, and no ad SDK or
|
||||
AD_ID permission. Not a buildable project.
|
||||
5
tests/fixtures/scan/capacitor-game/android/app/src/main/AndroidManifest.xml
vendored
Normal file
5
tests/fixtures/scan/capacitor-game/android/app/src/main/AndroidManifest.xml
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application android:label="Example Farm" />
|
||||
</manifest>
|
||||
5
tests/fixtures/scan/capacitor-game/capacitor.config.json
vendored
Normal file
5
tests/fixtures/scan/capacitor-game/capacitor.config.json
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"appId": "org.example.farmgame",
|
||||
"appName": "Example Farm",
|
||||
"webDir": "dist"
|
||||
}
|
||||
10
tests/fixtures/scan/capacitor-game/ios/App/App/Info.plist
vendored
Normal file
10
tests/fixtures/scan/capacitor-game/ios/App/App/Info.plist
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Example Farm</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
23
tests/fixtures/scan/capacitor-game/ios/App/App/PrivacyInfo.xcprivacy
vendored
Normal file
23
tests/fixtures/scan/capacitor-game/ios/App/App/PrivacyInfo.xcprivacy
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
10
tests/fixtures/scan/capacitor-game/package.json
vendored
Normal file
10
tests/fixtures/scan/capacitor-game/package.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "farm-game",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@capacitor/android": "8.0.0",
|
||||
"@capacitor/core": "8.0.0",
|
||||
"@capacitor/ios": "8.0.0",
|
||||
"@capacitor/preferences": "8.0.0"
|
||||
}
|
||||
}
|
||||
7
tests/fixtures/scan/capacitor-game/src/buildings/register.ts
vendored
Normal file
7
tests/fixtures/scan/capacitor-game/src/buildings/register.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { registry } from './registry';
|
||||
|
||||
export class Farm { level = 1 }
|
||||
export class Mill { level = 1 }
|
||||
|
||||
registry.register('farm', Farm);
|
||||
registry.register('mill', Mill);
|
||||
11
tests/fixtures/scan/capacitor-game/src/economy.ts
vendored
Normal file
11
tests/fixtures/scan/capacitor-game/src/economy.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export const bonus = { gen: (r: number) => 1 + r * 0.1 };
|
||||
export const F = {
|
||||
genRate: (lv: number, mult: number) => lv * 2 * mult,
|
||||
};
|
||||
|
||||
export function tick(f: typeof F, lv = 1, r = 0): number {
|
||||
const rate = F.genRate(lv, bonus.gen(r));
|
||||
const frameRate = 60; // bonus coins drop every second
|
||||
const exchangeRate = 10; // coins per gem
|
||||
return rate * frameRate / exchangeRate;
|
||||
}
|
||||
10
tests/fixtures/scan/capacitor-game/src/main.ts
vendored
Normal file
10
tests/fixtures/scan/capacitor-game/src/main.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import './buildings/register';
|
||||
import { Preferences } from '@capacitor/preferences';
|
||||
import { F, tick } from './economy';
|
||||
|
||||
export async function boot(): Promise<void> {
|
||||
const saved = await Preferences.get({ key: 'farm' });
|
||||
const installId = saved.value ?? crypto.randomUUID();
|
||||
await Preferences.set({ key: 'farm', value: installId });
|
||||
setInterval(() => tick(F), 1000);
|
||||
}
|
||||
41
tests/fixtures/scan/capacitor-game/storebound.config.ts
vendored
Normal file
41
tests/fixtures/scan/capacitor-game/storebound.config.ts
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Fixture config (made up, example.com placeholders only).
|
||||
export default {
|
||||
app: {
|
||||
name: 'Example Farm',
|
||||
profile: 'game',
|
||||
privacyPolicyUrl: 'https://example.com/privacy',
|
||||
supportUrl: 'https://example.com/support',
|
||||
website: 'https://example.com',
|
||||
contactEmail: 'support@example.com',
|
||||
},
|
||||
accounts: { enabled: true, deletionUrl: 'https://example.com/delete-account', inApp: 'Settings > Delete account' },
|
||||
reviewAccess: { signInRequired: false, instructions: 'No sign-in needed.' },
|
||||
languages: [{ id: 'en', play: 'en-US', apple: 'en-US' }],
|
||||
play: { packageName: 'org.example.farmgame', accountType: 'organization', targetAges: ['13-15', '16-17', '18+'] },
|
||||
apple: { bundleId: 'org.example.farmgame', usesNonExemptEncryption: false },
|
||||
appPrivacy: {
|
||||
tracking: false,
|
||||
types: {
|
||||
USER_ID: { collected: true, linked: true, tracking: false, purposes: ['APP_FUNCTIONALITY'] },
|
||||
DEVICE_ID: { collected: true, linked: false, tracking: false, purposes: ['APP_FUNCTIONALITY', 'ANALYTICS'] },
|
||||
GAMEPLAY_CONTENT: { collected: true, linked: true, tracking: false, purposes: ['APP_FUNCTIONALITY'] },
|
||||
PRODUCT_INTERACTION: { collected: true, linked: false, tracking: false, purposes: ['ANALYTICS'] },
|
||||
CRASH_DATA: { collected: true, linked: false, tracking: false, purposes: ['APP_FUNCTIONALITY'] },
|
||||
},
|
||||
},
|
||||
dataSafety: {
|
||||
collectsData: true,
|
||||
encryptedInTransit: true,
|
||||
data: {
|
||||
PSL_USER_ACCOUNT: { shared: false, collect: ['account', 'functionality'] },
|
||||
PSL_DEVICE_ID: { shared: false, collect: ['functionality', 'analytics'] },
|
||||
PSL_OTHER_APP_ACTIVITY: { shared: false, collect: ['analytics'] },
|
||||
PSL_CRASH_LOGS: { shared: false, collect: ['analytics'] },
|
||||
},
|
||||
},
|
||||
scan: {
|
||||
ignore: [
|
||||
{ rule: 'privacy-labels', evidence: 'src/no-such-file.ts', reason: 'stale entry: matches nothing' },
|
||||
],
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue