Over Declared Permissions Are a Common Rejection Reason
13 Mar 2026 · 6 min read · Erik van Kammen
Your app declares 12 permissions in its manifest. Your code uses 4. The other 8 came from SDKs you didn't audit, and two of them request access to the camera and microphone. You don't have a camera feature. But Apple and Google don't know that from looking at your binary.
Over-declared permissions are one of the most common compliance friction points. They trigger review questions, delay approvals, and in some cases lead to outright rejection. The gap between what your manifest declares and what your code actually uses is where the risk lives.
And the problem gets worse with every SDK you add.
On both platforms, permissions can enter your app through two paths: your own declarations and SDK-injected declarations.
On iOS, permission requests are declared in your Info.plist as usage description strings. Every permission your app requests needs a corresponding NS*UsageDescription key that explains to the user why the app needs access.
Common ones:
<key>NSCameraUsageDescription</key>
<string>We need camera access to scan QR codes</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to show nearby stores</string>
If a key is present in your Info.plist but your code never calls the corresponding API, Apple's review team may ask why. And if the usage description is vague or doesn't match the app's visible functionality, that's a rejection risk.
SDKs can inject these keys through their own Info.plist entries, which get merged during the build. A crash reporting SDK that includes NSPhotoLibraryUsageDescription for screenshot attachment will add that permission to your app, even if you never use the feature. If you're dealing with privacy manifest issues specifically, see how to fix NSPrivacyAccessedAPITypes violations.
On Android, the manifest merge process is more aggressive. Every library in your dependency tree can declare <uses-permission> entries in its own AndroidManifest.xml, and Gradle merges them all into the final manifest.
<!-- Your app's manifest -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- An analytics SDK's manifest (merged automatically) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
The merged manifest is what ships in your APK or AAB. You can inspect it with:
# View merged manifest
./gradlew app:processDebugManifest
# Then check: app/build/intermediates/merged_manifests/debug/AndroidManifest.xml
Google's Data Safety section needs to align with the permissions in your merged manifest. If your manifest declares ACCESS_FINE_LOCATION but your Data Safety section doesn't mention location data collection, that's a mismatch Google will flag.
| Aspect | iOS (Info.plist) | Android (AndroidManifest.xml) |
|---|---|---|
| Declaration file | Info.plist usage descriptions | AndroidManifest.xml permissions |
| Merge behavior | No automatic merge | Manifest merger combines all sources |
| SDK-injected permissions | Rare, usually explicit | Common via transitive dependencies |
| Removal method | Don't declare unused keys | tools:node="remove" in manifest |
| Store review behavior | Rejected if no matching feature | Flagged in Data Safety mismatch |
A permission is over-declared when it appears in your manifest but your app's code paths never exercise it. This happens more often than teams realize.
Common scenarios:
NSCameraUsageDescription is still in Info.plist.Apple reviews the permissions your app declares against the functionality visible in your app. If your app requests camera access but the reviewer can't find a camera feature, they'll ask about it. If your response isn't convincing, the app gets rejected.
Apple's review guidelines (section 5.1.1) require that apps only request access to data relevant to the core functionality of the app. "We might use it later" isn't an acceptable justification.
Google cross-references your declared permissions against your Data Safety section. Inconsistencies between what your manifest says and what your Data Safety disclosure says can trigger automated warnings or manual review.
Google also enforces specific permission policies. Apps declaring SCHEDULE_EXACT_ALARM need to justify its usage under Google Play policy, and apps targeting API level 33 or higher have it denied by default. Apps that declare READ_PHONE_STATE without a clear phone-related feature will get flagged.
Both platforms are tightening enforcement. The trend is toward fewer permissions declared more precisely, with stronger justification required for sensitive ones.
Don't audit your source manifest. Audit what actually ships.
For iOS, build your app and check the compiled Info.plist:
# Extract Info.plist from IPA
unzip -o MyApp.ipa -d extracted
plutil -p extracted/Payload/MyApp.app/Info.plist | grep -i usage
For Android, check the merged manifest after build:
# Check merged manifest
aapt2 dump xmltree app-release.apk AndroidManifest.xml | grep permission
For each permission in your manifest, find the code that uses it. On iOS, search for the framework calls:
NSCameraUsageDescription → look for AVCaptureSession, UIImagePickerController with camera sourceNSLocationWhenInUseUsageDescription → look for CLLocationManagerNSPhotoLibraryUsageDescription → look for PHPhotoLibrary, UIImagePickerControllerOn Android, search for the runtime permission request:
ACCESS_FINE_LOCATION → look for LocationManager, FusedLocationProviderClientCAMERA → look for CameraManager, CameraXREAD_CONTACTS → look for ContactsContractIf you can't find the corresponding code, the permission is likely SDK-injected or leftover.
On iOS, remove the unused NS*UsageDescription keys from your Info.plist. If an SDK added them, check if the SDK has a configuration option to disable the feature that requires the permission.
On Android, use manifest merger rules to block unwanted permissions:
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
tools:node="remove" />
This explicitly removes the permission even if an SDK declares it. Add xmlns:tools="http://schemas.android.com/tools" to your manifest's root element.
Rebuild and re-inspect the merged output. Confirm the permission is gone from the compiled artifact, not just from your source files.
Manual permission audits are tedious and easy to forget. They also break every time you add or update an SDK.
AppCompliance scans compiled IPA, APK, and AAB files and identifies declared permissions, entitlements, and capabilities. It flags permissions that don't have corresponding code usage patterns in the binary, helping you catch over-declarations before submission. For a full picture of what binary scanning reveals, see what's actually inside your app binary. No tool can guarantee store approval, but identifying permission mismatches before you upload saves review cycles.
The practical approach is to run a permission audit as part of your CI/CD pipeline. Every build should produce a manifest diff that shows what changed since the last release. New permissions showing up unexpectedly is a signal that something needs investigation.
Permissions are one of the easiest compliance issues to prevent. The fix is usually removing a line from a manifest or adding a merger rule. The cost of not catching it is a review rejection and a lost release cycle.
Everything technical in this post is checkable from the build itself, and that is exactly what AppCompliance does: upload an iOS or Android build, and minutes later you have every issue that would get it rejected, with the exact fix next to it.
One email when we open the doors. No spam, just the launch.