feat(ios,macos): Apple privacy manifest (PrivacyInfo.xcprivacy)
Apple has enforced a `PrivacyInfo.xcprivacy` privacy manifest at App
Store submission since May 2024 for iOS / iPadOS / visionOS /
watchOS, and rolled the requirement out to macOS in late 2024.
Without the file, App Store Connect rejects archive uploads with
"missing required privacy manifest". This commit adds the manifest
for both iOS and macOS Runner targets.
apps/chanora_flutter/ios/Runner/PrivacyInfo.xcprivacy
apps/chanora_flutter/macos/Runner/PrivacyInfo.xcprivacy
Identical content. Declarations:
NSPrivacyCollectedDataTypes:
NSPrivacyCollectedDataTypeAudioData
Microphone audio transmitted to the user's chosen voice
server while connected and unmuted. Not linked to user
identity (no Apple ID / IDFA tied), not used for tracking.
Purpose: AppFunctionality (communications).
NSPrivacyTracking: false
NSPrivacyTrackingDomains: []
Chanora performs no cross-app / cross-website tracking.
NSPrivacyAccessedAPITypes:
FileTimestamp (C617.1)
tokio + rusqlite file I/O for identity.tskey, chanora.db,
audio_meta.json, chanora.log inside the app container.
UserDefaults (CA92.1)
Indirect via path_provider Flutter plugin querying for
Application Support / Documents directories.
SystemBootTime (35F9.1)
tracing-subscriber timestamps log records relative to boot.
DiskSpace (85F4.1)
rusqlite checks before sqlite page writes.
All four "required reason" API categories use Apple's published
allow-list reason codes; no fingerprinting / analytics usage.
apps/chanora_flutter/ios/Runner.xcodeproj/project.pbxproj
apps/chanora_flutter/macos/Runner.xcodeproj/project.pbxproj
Added PrivacyInfo.xcprivacy to the Runner group and to the
Runner target's "Copy Bundle Resources" build phase via the
xcodeproj Ruby gem (via a one-shot script). With this, the file
is placed at Runner.app/PrivacyInfo.xcprivacy where Apple's
validator looks for it — `find Runner.app -name
PrivacyInfo.xcprivacy` shows our manifest at the bundle root
alongside Flutter's and connectivity_plus's.
Verified on the M1 Mac (coder@100.118.130.73):
flutter build ios --release --no-codesign 4.0 s
-> Runner.app/PrivacyInfo.xcprivacy present
flutter build ipa --release --no-codesign 28.4 s
-> Runner.xcarchive built (171.4 MB)
-> archive's Runner.app/PrivacyInfo.xcprivacy present
-> archive's Runner.app/Frameworks/chanora_bridge.framework
built fresh via the chanora_bridge.podspec prepare_command
under xcodebuild's sandbox (no PATH / env weirdness).
P1 follow-ups noted by xcodebuild's validator (not blockers for
this commit but for App Store submission):
* Real app icon (currently default placeholder)
* Real launch image (currently default placeholder)
* Paid Apple Developer Program account, registered App ID, and
Distribution provisioning profile (Personal Team sideloads
still work as today).
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
FD3C80659716BF7A0C95C7AF /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1937FD83C5CC909094CDC137 /* PrivacyInfo.xcprivacy */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -47,6 +48,7 @@
|
||||
0B4754099284EEDCD859A973 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
1937FD83C5CC909094CDC137 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
@@ -164,6 +166,7 @@
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
1937FD83C5CC909094CDC137 /* PrivacyInfo.xcprivacy */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
@@ -267,6 +270,7 @@
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
FD3C80659716BF7A0C95C7AF /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?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>
|
||||
<!-- Privacy Manifest for Chanora.
|
||||
Apple started enforcing this file at App Store submission in
|
||||
May 2024 for iOS / iPadOS / visionOS / watchOS apps. Without
|
||||
it, archive upload to App Store Connect is rejected.
|
||||
|
||||
Reference: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
|
||||
-->
|
||||
|
||||
<!-- Whether the app collects data that has been linked to the
|
||||
user's identity. Chanora never gathers third-party analytics,
|
||||
user accounts, ad identifiers, or device identifiers. The
|
||||
only "data" leaving the device is the voice + chat content
|
||||
the user explicitly sends to a TeamSpeak server they have
|
||||
chosen. Per Apple's definition that is "not collected by
|
||||
the app" because the destination is user-selected, not us.
|
||||
-->
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<!-- Microphone audio that is transmitted to the user's
|
||||
chosen voice server while the user is connected and
|
||||
unmuted. Apple's data-type taxonomy classifies this
|
||||
as "Audio Data". The audio is not linked to the user
|
||||
(no Apple ID, no IDFA tied to the stream) and not
|
||||
used for tracking. -->
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeAudioData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<!-- Communications: the user is talking to other
|
||||
people on their chosen server. -->
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<!-- The app does not track users across other apps + websites
|
||||
owned by other companies. -->
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
|
||||
<!-- "Required reason" APIs Chanora uses. Apple maintains a list
|
||||
of system APIs that need a declared reason because they
|
||||
historically were abused for fingerprinting. Chanora uses
|
||||
the file-timestamp APIs (via tokio file I/O for storing the
|
||||
identity key + bookmark db + audio_meta + chanora.log) and
|
||||
the user-defaults API (via Flutter's path_provider plugin
|
||||
which queries NSUserDefaults to resolve Application Support
|
||||
paths). Documented reasons below match Apple's published
|
||||
allow-list. -->
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<!-- File-timestamp APIs used by tokio + rusqlite when
|
||||
reading / writing identity.tskey, chanora.db, and
|
||||
chanora.log. Reason `C617.1`: app-internal,
|
||||
timestamps of files inside the app's container. -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<!-- UserDefaults read indirectly via path_provider's
|
||||
query for the app's documents directory. Reason
|
||||
`CA92.1`: access user defaults to read information
|
||||
only accessible to the app itself. -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<!-- System boot time read by tracing-subscriber for
|
||||
timestamping log records. Reason `35F9.1`: measure
|
||||
elapsed time between events that occur within the
|
||||
app. -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<!-- Disk space queried by rusqlite when checking sqlite
|
||||
page capacity. Reason `E174.1`: display disk space
|
||||
to the user. Actually we don't display it; we just
|
||||
read it. The next-closest reason is `85F4.1`:
|
||||
ensure disk space available before writes. -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>85F4.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -27,6 +27,7 @@
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
45F255D1DE0134185DB5423D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 06E1AA7E1FB968C1D78DA8DE /* PrivacyInfo.xcprivacy */; };
|
||||
9B86175918197ECC64969956 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EFEADEEFAB54DFEAAD7A70E9 /* Pods_Runner.framework */; };
|
||||
C2DC22E19FDCE26B9D79442E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDE04BBB936AA14C2B58FA0E /* Pods_RunnerTests.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -62,6 +63,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
06E1AA7E1FB968C1D78DA8DE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
@@ -181,6 +183,7 @@
|
||||
33E51914231749380026EE4D /* Release.entitlements */,
|
||||
33CC11242044D66E0003C045 /* Resources */,
|
||||
33BA886A226E78AF003329D5 /* Configs */,
|
||||
06E1AA7E1FB968C1D78DA8DE /* PrivacyInfo.xcprivacy */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
@@ -315,6 +318,7 @@
|
||||
files = (
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||
45F255D1DE0134185DB5423D /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?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>
|
||||
<!-- Privacy Manifest for Chanora.
|
||||
Apple started enforcing this file at App Store submission in
|
||||
May 2024 for iOS / iPadOS / visionOS / watchOS apps. Without
|
||||
it, archive upload to App Store Connect is rejected.
|
||||
|
||||
Reference: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
|
||||
-->
|
||||
|
||||
<!-- Whether the app collects data that has been linked to the
|
||||
user's identity. Chanora never gathers third-party analytics,
|
||||
user accounts, ad identifiers, or device identifiers. The
|
||||
only "data" leaving the device is the voice + chat content
|
||||
the user explicitly sends to a TeamSpeak server they have
|
||||
chosen. Per Apple's definition that is "not collected by
|
||||
the app" because the destination is user-selected, not us.
|
||||
-->
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<!-- Microphone audio that is transmitted to the user's
|
||||
chosen voice server while the user is connected and
|
||||
unmuted. Apple's data-type taxonomy classifies this
|
||||
as "Audio Data". The audio is not linked to the user
|
||||
(no Apple ID, no IDFA tied to the stream) and not
|
||||
used for tracking. -->
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeAudioData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<!-- Communications: the user is talking to other
|
||||
people on their chosen server. -->
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<!-- The app does not track users across other apps + websites
|
||||
owned by other companies. -->
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
|
||||
<!-- "Required reason" APIs Chanora uses. Apple maintains a list
|
||||
of system APIs that need a declared reason because they
|
||||
historically were abused for fingerprinting. Chanora uses
|
||||
the file-timestamp APIs (via tokio file I/O for storing the
|
||||
identity key + bookmark db + audio_meta + chanora.log) and
|
||||
the user-defaults API (via Flutter's path_provider plugin
|
||||
which queries NSUserDefaults to resolve Application Support
|
||||
paths). Documented reasons below match Apple's published
|
||||
allow-list. -->
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<!-- File-timestamp APIs used by tokio + rusqlite when
|
||||
reading / writing identity.tskey, chanora.db, and
|
||||
chanora.log. Reason `C617.1`: app-internal,
|
||||
timestamps of files inside the app's container. -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<!-- UserDefaults read indirectly via path_provider's
|
||||
query for the app's documents directory. Reason
|
||||
`CA92.1`: access user defaults to read information
|
||||
only accessible to the app itself. -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<!-- System boot time read by tracing-subscriber for
|
||||
timestamping log records. Reason `35F9.1`: measure
|
||||
elapsed time between events that occur within the
|
||||
app. -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<!-- Disk space queried by rusqlite when checking sqlite
|
||||
page capacity. Reason `E174.1`: display disk space
|
||||
to the user. Actually we don't display it; we just
|
||||
read it. The next-closest reason is `85F4.1`:
|
||||
ensure disk space available before writes. -->
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>85F4.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user