ITMS-91064: NSPrivacyTracking and NSPrivacyTrackingDomains must agree in every privacy manifest (true needs domains, domains need true); check, scan and privacy-manifest fail on either mismatch with the exact ITMS text; App Privacy tracking no longer demands the app manifest's true: SDK manifests that declare tracking (Pods, frameworks, SwiftPM, Carthage, plugins, the built IPA) are info, known tracking SDKs info, else a warning; privacy-manifest --propose writes true only with appPrivacy.trackingDomains; apple upload checks every manifest in the IPA (XML or binary) before any network call, apple build after the export. Pitfall 29

This commit is contained in:
CRTkafa 2026-10-12 10:27:21 +08:00
parent b7bfe89151
commit c8213be2b6
19 changed files with 631 additions and 44 deletions

View file

@ -7,7 +7,8 @@ description: Add and check Apple privacy manifests (PrivacyInfo.xcprivacy) for a
`storebound scan` runs the same checks as part of its report (no manifest, undeclared required-reason APIs, SDKs older than their first manifest version) and proposes NSPrivacyTracking and the collected data types from the detected SDKs.
Scan: `storebound privacy-manifest --ios ios/App` (local, no credentials). Capacitor / Cordova / React Native plugins
Scan: `storebound privacy-manifest --ios ios/App` (local, no credentials; `--propose` prints the app manifest
storebound proposes). Capacitor / Cordova / React Native plugins
live outside the iOS folder; they are found the way Node resolves them: the dependencies of the app's package.json
(next to capacitor.config) in the node_modules there and in every folder above it up to the repo root (so a monorepo's
`apps/native/node_modules` works), workspace packages (package.json `workspaces`, Bun / npm / yarn), local SwiftPM
@ -15,6 +16,10 @@ packages (Capacitor 8's CapApp-SPM `Package.swift`), Pods and SwiftPM checkouts.
cannot find. Exit 1 when something blocks upload.
## What Apple checks at upload
- **ITMS-91064 Invalid tracking information**: "ITMS-91064: Invalid tracking information - A PrivacyInfo.xcprivacy file contains invalid tracking information at the following path: "PrivacyInfo.xcprivacy". NSPrivacyTracking must be true if NSPrivacyTrackingDomains isn't empty. Keys and values in your app's privacy manifests must be valid." Raised for
true with an empty list as well as for domains with false, in the app's manifest or any SDK's. `storebound
apple upload` checks every manifest in the IPA (app root, Frameworks, bundles; XML or binary) before sending it,
`apple build` after the export, `check` / `scan` / `privacy-manifest` in the project.
- **ITMS-91053 Missing API declaration**: the binary uses a required-reason API and no manifest in the app or in the
SDK that calls it declares the category with a reason.
- **ITMS-91061 Missing privacy manifest**: an SDK from Apple's list of commonly used SDKs (Firebase, Google utilities,
@ -31,9 +36,14 @@ cannot find. Exit 1 when something blocks upload.
- `NSPrivacyAccessedAPICategorySystemBootTime` + `35F9.1` (elapsed time between events)
- `NSPrivacyAccessedAPICategoryDiskSpace` + `E174.1` (check space before writing)
Pick the reason that is true; read Apple's reason list ("Describing use of required reason API") if unsure.
3. Tracking: `NSPrivacyTracking` true only if the app tracks (ad SDK with IDFA after ATT); then list
`NSPrivacyTrackingDomains` the SDK docs name. Keep it consistent with App Privacy "used for tracking" and the ATT
prompt (`store-declarations`).
3. Tracking (ITMS-91064): in every manifest `NSPrivacyTracking` and `NSPrivacyTrackingDomains` must agree: true
needs a non-empty list of the domains the code of THAT bundle sends tracking data to, and a non-empty list needs
true. When the tracking comes from an SDK (ad network, mediation such as LevelPlay / AppLovin MAX / AdMob,
attribution), the SDK's own manifest declares it and the app's manifest says **false**, while App Privacy in App
Store Connect still says "used for tracking" and the ATT prompt stays (`store-declarations`). Never set the app's
manifest to true with an empty list: Apple refuses the build as Invalid Binary a minute after the upload. Only
when the app's own code tracks, list its domains in `appPrivacy.trackingDomains`;
`storebound privacy-manifest --ios ios/App --propose` prints the manifest with true only then.
4. `NSPrivacyCollectedDataTypes`: what the app itself collects; SDK manifests cover their own collection. Xcode's
Privacy Report (Product > Archive > Generate Privacy Report) merges all manifests; compare it with App Privacy.
5. SDKs flagged "ships no PrivacyInfo.xcprivacy": update to a version that has one (ad networks and mediation
@ -44,5 +54,8 @@ cannot find. Exit 1 when something blocks upload.
- The scan is a text search: it can miss calls inside binary frameworks (their manifest must cover them) and flag a
harmless symbol name; confirm before adding a category you do not use.
- A manifest must be in the **app target's** Copy Bundle Resources, not only in the project.
- `check` saying "tracking declared by the <SDK> manifest" (info) is the correct setup for SDK tracking; a warning
that no manifest declares tracking means: check the SDK version ships its manifest (`pod install`, resolve
packages), not "set the app's manifest to true".
- Static libraries cannot carry a manifest bundle by themselves; the vendor ships a resource bundle or you merge their
declarations into the app's manifest as their docs say.

View file

@ -62,7 +62,9 @@ CLI: `storebound` (or `bunx storebound` in a project that has it, or `bun <toolc
(Advertising Data, Gameplay Content, Customer Support, Environment Scanning, Hands, Head) are answered only here.
`scan --write` proposes `collected` + `purposes` for detected types the block does not answer yet; `linked` and
`tracking` are never proposed (ask the owner). `storebound check` compares the block with the iOS project:
tracking true <-> `NSUserTrackingUsageDescription` and `NSPrivacyTracking` true; the app's
tracking true <-> `NSUserTrackingUsageDescription` and a manifest that declares tracking: the tracking SDK's own
manifest (Pods, frameworks, plugins, the built IPA; info) or the app's, which says true only with its own
`NSPrivacyTrackingDomains` (true with an empty list is ITMS-91064, see `privacy-manifest`); the app's
`NSPrivacyCollectedDataTypes` <-> the types marked collected (mismatches both ways).
6. **Export compliance.** `export-compliance` skill (`ITSAppUsesNonExemptEncryption` in Info.plist, `apple encryption`).
7. **Privacy manifest (iOS).** `privacy-manifest` skill; keep it consistent with App Privacy.

View file

@ -376,3 +376,19 @@ that fails without the fix (see CONTRIBUTING.md).
Responses?" (Cancel / Publish); afterwards "Published … ago by …" shows, which `console verify apple-app-privacy`
reads as the published signal.
- **Guard:** appstore-first-release and store-iap-setup skills; `tests/console-play-plans.test.ts`.
## 29. ITMS-91064: NSPrivacyTracking true with an empty NSPrivacyTrackingDomains
- **Symptom:** Apple marks the build Invalid Binary a minute after the upload: "ITMS-91064: Invalid tracking information - A PrivacyInfo.xcprivacy file contains invalid tracking information at the following path: "PrivacyInfo.xcprivacy". NSPrivacyTracking must be true if NSPrivacyTrackingDomains isn't empty. Keys and values in your app's privacy manifests must be valid."
- **Cause:** the app's own PrivacyInfo.xcprivacy said NSPrivacyTracking true with an empty NSPrivacyTrackingDomains
because App Privacy says "used for tracking", but the tracking came from an ad SDK (mediation, attribution) whose own
manifest declares it. Apple's text names the other direction (domains listed with false); both are refused.
- **Do:** in every manifest, NSPrivacyTracking true needs a non-empty NSPrivacyTrackingDomains (domains the code of
THAT bundle sends tracking data to), and a non-empty list needs true. When only an SDK tracks, the app's manifest
says false; App Privacy in App Store Connect still says tracking = yes and ATT stays. Set
`appPrivacy.trackingDomains` only for the app's own tracking domains. A bad SDK manifest: update the SDK. Rebuild
and upload a new build number.
- **Guard:** `check`, `scan` and `privacy-manifest` fail on either mismatch with the ITMS text; `check` reports
tracking declared by an SDK manifest as info and warns only when no manifest declares it; `privacy-manifest
--propose` writes true only with `appPrivacy.trackingDomains`; `apple upload` checks every manifest in the IPA
(app root, Frameworks, bundles) before any network call, `apple build` after the export;
`tests/privacy-tracking.test.ts`.