Files
chanora/apps/chanora_flutter/ios/Runner/PrivacyInfo.xcprivacy
EdisonJwa f5d3810f5f 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).
2026-05-16 16:03:39 +09:00

115 lines
5.0 KiB
XML

<?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>