"Detects missing or insufficiently descriptive NS*UsageDescription keys in Info.plist that cause guaranteed App Store rejection under Guideline 5.1.1(ii)."
Install
npx skillscat add cruisediary/apple-app-review-skills/usage-description-audit Install via the SkillsCat registry.
Skill: Usage Description Audit
Purpose
Detects missing or insufficiently descriptive NS*UsageDescription keys in Info.plist that cause guaranteed App Store rejection under Guideline 5.1.1(ii).
Apple Guideline
- Primary: 5.1.1(ii) — Data Collection and Storage: Purpose Strings
- Related: 5.1.1(i), 5.1.2
- Reference:
references/guidelines/5-legal.md
Real-World Rejection Cases
Case: OneSignal SDK silently added
NSLocationAlwaysUsageDescriptiondependency, app rejected for missing key even though developer never used location
Source: https://github.com/OneSignal/OneSignal-iOS-SDK/issues/490
Root cause: Third-party SDK references location APIs, requiring NSUsageDescription even if app doesn't call them directly.
Note:NSLocationAlwaysUsageDescriptionis deprecated since iOS 11. The current required key isNSLocationAlwaysAndWhenInUseUsageDescription. Some older SDKs may still inject the deprecated key — add both keys if using Always authorization to cover all iOS versions.Case: Urban Airship library caused rejection for
NSLocationAlwaysUsageDescriptionnot set by developer
Source: https://github.com/urbanairship/ios-library/issues/205
Root cause: SDK dependency chain included location APIs without developer's knowledge. Always auditPods/and.build/dependency trees for injectedNSUsageDescriptionkeys after adding a new SDK.Case: Generic string "App needs access" rejected — reviewer flagged as insufficient justification
Source: Apple Developer Forums
Root cause: Purpose strings must clearly describe the specific use case, not generic access reasonsCase: App declared NSCameraUsageDescription in Info.plist but had no camera feature in the UI — reviewer could not find any camera usage — rejected for requesting unnecessary permission under Guideline 5.1.1(ii)
Source: Apple Developer Forums (Guideline 5.1.1 threads on unnecessary permission requests)
Root cause: Requesting a permission not actually used in the app violates Guideline 5.1.1(ii); remove any NS*UsageDescription key for APIs the app does not actually call
Trigger
Invoke on any iOS/macOS project before App Store submission to verify all required NS*UsageDescription keys are present and sufficiently descriptive.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
project_root |
path | cwd | iOS/macOS project root |
shared_context |
object | nil | Pre-collected context from appstore-full-audit Phase 1 |
Actions
Phase 1: Context Collection
Skip this phase if shared_context is provided.
Glob**/Info.plist— locate the app's Info.plist file(s).Glob**/*.swift— collect Swift source files for API usage cross-reference.Glob**/*.m— collect Objective-C source files.Readthe primary Info.plist to extract all NS*UsageDescription keys and their values.
Phase 2: Checks
NSCameraUsageDescription
GreppatternAVCaptureDevice|AVFoundationin**/*.swiftand**/*.m.
If found, verifyNSCameraUsageDescriptionexists in Info.plist with value length > 20 characters. Missing key → 🔴 CRITICAL. Value ≤ 20 chars → 🟠 HIGH.NSMicrophoneUsageDescription
GreppatternAVAudioSession|AVAudioEngine|AVAudioRecorderin**/*.swiftand**/*.m.
If found, verifyNSMicrophoneUsageDescriptionexists in Info.plist with value length > 20 characters. Missing key → 🔴 CRITICAL. Value ≤ 20 chars → 🟠 HIGH.NSLocationWhenInUseUsageDescription
GreppatternCLLocationManager|requestWhenInUseAuthorizationin**/*.swiftand**/*.m.
If found, verifyNSLocationWhenInUseUsageDescriptionexists in Info.plist with value length > 20 characters. Missing key → 🔴 CRITICAL. Value ≤ 20 chars → 🟠 HIGH.NSLocationAlwaysAndWhenInUseUsageDescription
GreppatternrequestAlwaysAuthorizationin**/*.swiftand**/*.m.
If found, verifyNSLocationAlwaysAndWhenInUseUsageDescriptionexists in Info.plist with value length > 20 characters. Missing key → 🔴 CRITICAL. Value ≤ 20 chars → 🟠 HIGH.NSContactsUsageDescription
GreppatternCNContactStore|Contacts.frameworkin**/*.swiftand**/*.m.
If found, verifyNSContactsUsageDescriptionexists in Info.plist with value length > 20 characters. Missing key → 🔴 CRITICAL. Value ≤ 20 chars → 🟠 HIGH.NSPhotoLibraryUsageDescription
GreppatternPHPhotoLibrary|Photos.frameworkin**/*.swiftand**/*.m.
If found, verifyNSPhotoLibraryUsageDescriptionexists in Info.plist with value length > 20 characters. Missing key → 🔴 CRITICAL. Value ≤ 20 chars → 🟠 HIGH.NSUserTrackingUsageDescription
GreppatternFirebase|Amplitude|Mixpanel|Segment|Branch|AppsFlyer|Adjust|ATTrackingManagerin**/*.swiftand project files.
If any analytics/tracking SDK found, verifyNSUserTrackingUsageDescriptionexists in Info.plist with value length > 20 characters. Missing key → 🔴 CRITICAL. Value ≤ 20 chars → 🟠 HIGH.NSFaceIDUsageDescription
GreppatternLAContext|LocalAuthentication|evaluatePolicyin**/*.swiftand**/*.m.
If found, verifyNSFaceIDUsageDescriptionexists in Info.plist with value length > 20 characters. Missing key → 🔴 CRITICAL. Value ≤ 20 chars → 🟠 HIGH.Cross-reference requestXAuthorization calls
GreppatternrequestWhenInUseAuthorization|requestAlwaysAuthorization|requestAccess|requestAuthorization|requestTrackingAuthorizationin**/*.swift.
For each authorization call found, confirm corresponding NS*UsageDescription key exists in Info.plist. Any mismatch → 🔴 CRITICAL.
Phase 3: Output
Collect all findings from Phase 2 and build the prioritised findings list below. Include file paths and line numbers. Omit tiers with no findings.
Output Format
## Usage Description Audit — Findings
### 🔴 CRITICAL — Guaranteed rejection
- [ ] TODO: Add NSCameraUsageDescription to Info.plist — app uses AVCaptureDevice but key is missing — Guideline 5.1.1(ii)
- [ ] TODO: Add NSUserTrackingUsageDescription to Info.plist — Firebase SDK detected but tracking purpose string absent — Guideline 5.1.1(ii)
### 🟠 HIGH — Very likely rejection
- [ ] TODO: Expand NSMicrophoneUsageDescription to > 20 characters explaining why the microphone is needed — current value is too generic — Guideline 5.1.1(ii)
### 🟡 MEDIUM — Possible rejection
- [ ] TODO: Review third-party SDK dependencies for hidden permission requirements — e.g. analytics SDKs may pull in location APIs
### 🟢 LOW — Best practice
- [ ] TODO: Audit all NS*UsageDescription strings for specificity — avoid phrases like "required for functionality"Tools Used
Glob, Grep, Read
Constraints
- Read-only. No file edits.
- No network calls.
- Skip Phase 1 if
shared_contextis provided by orchestrating agent. - Works on Swift, Objective-C, React Native, Flutter projects.
Quick Commands
Run these in your project root to check manually:
# List all NS*UsageDescription keys in Info.plist
!find . -name "Info.plist" -not -path "*/Pods/*" | head -1 | xargs plutil -convert json -o - | python3 -c "import sys,json; [print(k,'=',v) for k,v in json.load(sys.stdin).items() if 'UsageDescription' in k]"
# Find permission request calls in code
!grep -rn "requestWhenInUseAuthorization\|requestAlwaysAuthorization\|requestAccess\|requestAuthorization\|requestTrackingAuthorization" . --include="*.swift"
# Check for any NS*UsageDescription in all plist files
!grep -rn "UsageDescription" . --include="*.plist"Swift Anti-Pattern Reference
examples/swift/PermissionPatterns.swift
Detection Steps
Find target files
- Glob:
**/Info.plist,**/*.swift,**/*.m,**/Podfile.lock
- Glob:
Search for rejection patterns
- Read
Info.plist→ extract allNS*UsageDescriptionkeys and their string values - Flag values shorter than 10 characters or matching
App needs access\|To improve your experience\|Required - Grep
requestAccess\|requestAuthorization\|requestWhenInUseAuthorization\|requestAlwaysAuthorizationin Swift — cross-reference with Info.plist keys - Grep
NSLocationAlwaysUsageDescription\|NSPhotoLibraryUsageDescriptioninPodfile.lockcomments (third-party SDK additions)
- Read
Determine verdict
- Permission requested in code + corresponding
NS*UsageDescriptionmissing from Info.plist → 🔴 CRITICAL (Guideline 5.1.1(ii)) NS*UsageDescriptionvalue is generic (< 10 chars or matches boilerplate) → 🟠 HIGH- All descriptions are specific and descriptive → 🟢 pass
- Permission requested in code + corresponding
Report
- Missing key name and the code location requesting that permission
- Exact value of any generic description
- Fix: Add specific purpose strings explaining exactly what the permission enables (e.g., "To attach photos to your messages" instead of "Photo access needed")