Metadata-Version: 2.4
Name: xcprivacy
Version: 0.1.0
Summary: Pre-flight validator for Apple privacy manifests. Catch ITMS-91053/91055/91056 before you upload — on any OS, with no Xcode.
Author: xcprivacy contributors
License: MIT
Project-URL: Source, https://github.com/Ax1zz/xcprivacy
Project-URL: Issues, https://github.com/Ax1zz/xcprivacy/issues
Keywords: ios,macos,apple,app-store,privacy-manifest,PrivacyInfo,xcprivacy,ITMS-91053,ITMS-91055,ITMS-91061,linter,ci
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# xcprivacy

**Find out your `PrivacyInfo.xcprivacy` is wrong now — not after the upload, from a rejection email.**

Apple rejects App Store builds over one wrong string in `PrivacyInfo.xcprivacy`.
`plutil -lint` passes it. Xcode says nothing. You find out after building and
uploading, usually on release day.

`xcprivacy` catches it first — with **no Xcode, no macOS, and zero dependencies**,
so it runs on a Linux CI runner.

```console
$ pip install xcprivacy
$ xcprivacy
```

```
PhotoVault/PrivacyInfo.xcprivacy
  error XCP103  tracking domain 'https://sdk.adnetwork.io/v2/track' includes a scheme
      predicts ITMS-91056
      Apple requires a bare host such as 'analytics.example.com' - no scheme, no path, no
      query, no trailing slash.
  error XCP208  NSPrivacyAccessedAPITypes[0] declares reason 35F9.1 under
      NSPrivacyAccessedAPICategoryFileTimestamp, but 35F9.1 belongs to
      NSPrivacyAccessedAPICategorySystemBootTime
      predicts ITMS-91055
      Allowed reasons for NSPrivacyAccessedAPICategoryFileTimestamp: DDA9.1, C617.1, 3B52.1,
      0A2A.1. If you meant to declare 35F9.1, move it to a
      NSPrivacyAccessedAPICategorySystemBootTime entry.
  error XCP209  NSPrivacyAccessedAPITypes[1] declares C56D.1, which Apple restricts to third-party SDKs
      predicts ITMS-91055
      Apple: "Third-party SDK wrapper around user defaults APIs. SDKs only." If this manifest
      belongs to an app rather than an SDK, choose one of: CA92.1, 1C8F.1, AC6B.1
  error XCP303  NSPrivacyCollectedDataTypes[0] has unknown NSPrivacyCollectedDataType value
      'NSPrivacyCollectedDataTypePhotosOrVideos'
      predicts ITMS-91056
      Did you mean 'NSPrivacyCollectedDataTypePhotosorVideos'? (note Apple's spelling:
      lowercase 'o' in 'or')
  error XCP501  Disk space is used in 1 place(s) but is not declared: PhotoVault/LibraryScanner.swift:9
      volumeAvailableCapacityForImportantUsageKey
      predicts ITMS-91053
      Add an NSPrivacyAccessedAPITypes entry with NSPrivacyAccessedAPIType =
      NSPrivacyAccessedAPICategoryDiskSpace and a reason from: 85F4.1, E174.1, 7D9E.1, B728.1

Pods/FirebaseCrashlytics/FirebaseCrashlytics.framework
  error XCP601  FirebaseCrashlytics is on Apple's required list but no PrivacyInfo.xcprivacy was found
      inside FirebaseCrashlytics.framework
      predicts ITMS-91061
      Update this dependency to a version that ships a manifest. Apple has required one for
      every listed SDK since 12 February 2025.

FAIL  6 error(s), 0 warning(s)  (1 manifest(s), 1 source file(s))
       likely App Store Connect rejections: ITMS-91053, ITMS-91055, ITMS-91056, ITMS-91061
```

## The bug nobody catches

Reason codes are **scoped per category**. `35F9.1` is legal under
`NSPrivacyAccessedAPICategorySystemBootTime` and **illegal** under
`NSPrivacyAccessedAPICategoryFileTimestamp`.

A manifest with that mistake is a perfectly valid property list, so nothing on your
machine complains — and App Store Connect returns `ITMS-91055`.

It is not hypothetical: this exact bug shipped in Microsoft's App Center SDK
(`microsoft/appcenter-sdk-apple` issue 2543).

**No actively maintained tool validates per-category reason scoping.** This one does.

## The five rejection codes it predicts

| Code | Meaning |
| --- | --- |
| `ITMS-91053` | You use a required-reason API you did not declare |
| `ITMS-91054` | Invalid API category declaration |
| `ITMS-91055` | Invalid API reason declaration |
| `ITMS-91056` | Valid plist, invalid keys or values |
| `ITMS-91061` | A third-party SDK on Apple's list has no manifest |

## What it checks

**Manifest contents**

- All four top-level keys, their types, and rejection of unknown keys
- Category values against Apple's 5-category enumeration
- Every reason code against the enumeration for **its own category**
- SDK-only reason codes (`0A2A.1`, `C56D.1`) misused in an app manifest
- All 35 data types and 6 purposes, with did-you-mean for near misses
- Tracking domains per Apple TN3181: no scheme, path, query or trailing slash
- Reads **binary plists as well as XML** — compiled frameworks ship binary, and
  those are exactly the manifests most likely to be broken

**Your source**

- Scans Swift / Objective-C / C for required-reason API usage and cross-checks it
  against what you declared, with `file:line` evidence
- Comments and string literals are blanked before matching, so a symbol named in a
  comment is never treated as evidence
- Generic names such as `creationDate` carry low confidence and warn rather than
  error, because a linter that cries wolf gets uninstalled
- Ambiguous symbols are reported, never guessed: Apple lists `getattrlist`,
  `fgetattrlist` and `getattrlistat` under *both* FileTimestamp and DiskSpace

**Your dependencies**

- Detects SDKs from `Pods/`, `Carthage/`, `*.xcframework`, `Podfile`,
  `Package.resolved` and matches them against Apple's published 86-SDK list
- Flags any listed SDK bundled without a manifest

## Usage

```console
xcprivacy                                   # check the current directory
xcprivacy path/to/MyApp                     # check a project
xcprivacy MyApp.app/PrivacyInfo.xcprivacy   # check a single manifest
xcprivacy --strict                          # warnings become errors
xcprivacy --format sarif -o out.sarif       # for GitHub code scanning
xcprivacy --format json                     # for scripting
xcprivacy rules                             # list all 42 rules
xcprivacy explain ITMS-91055                # you got the email; what now?
```

Exit codes: `0` clean, `1` errors found, `2` bad usage or I/O.

### Adopting it on a legacy project

```console
xcprivacy --write-baseline .xcprivacy-baseline.json   # accept today's reality
xcprivacy --baseline .xcprivacy-baseline.json         # fail only on new findings
```

Fingerprints deliberately exclude line numbers, so editing code above a finding
does not resurrect it.

## Why Python

The developers hit hardest by this are React Native, Flutter, Cordova and MAUI
teams whose CI runs on Linux — and every other tool in this space requires macOS,
`xcrun`, or CocoaPods. On a Linux runner they currently have nothing.

Python's standard-library `plistlib` reads binary and XML plists with no
dependencies, so this installs anywhere in one command and has an **empty
dependency tree**, which matters for a tool you run in CI over a compliance
artifact. CI asserts that the dependency list stays empty.

## What it deliberately does not do

- **Mach-O binary symbol analysis** — the right v2 feature, but doing it properly
  means walking symbol tables and dyld bind opcodes across slices. Source scanning
  covers first-party code today, which is where you can actually act.
- **Generating manifests** — five tools already generate. None validate.
- **Auto-fixing** — a tool that silently edits your compliance artifact is a
  liability. It reports; you decide.
- **Signature verification** — needs `codesign`, therefore macOS.
- **Anything involving an LLM** — the rule set is a finite enumeration.
  Deterministic means unit-testable, and being provably right is the whole product.

## Trusting the rule data

Every table is transcribed from Apple's documentation, retrieved 2026-08-03, with
the source recorded per table in `src/xcprivacy/rules/PROVENANCE.md`.

The test suite pins the counts — 5 categories, 17 reason codes, 35 data types,
6 purposes, 86 SDKs — and exhaustively asserts that **all 68 illegal
category/reason pairings are caught and all 17 legal ones are accepted**, so the
flagship check cannot silently regress.

It also preserves Apple's genuine oddity: `NSPrivacyCollectedDataTypePhotosorVideos`
has a **lowercase `o`** in "or". "Correcting" it would make the validator reject the
only value App Store Connect accepts.

## License

MIT.

Not affiliated with or endorsed by Apple Inc. `ITMS-*` codes and privacy manifest
key names are Apple's; this tool only checks your files against Apple's published
rules.
