An unhandled promise rejection from a permission-gated API call causes a visible red screen in development and a silent crash in production — neither outcome is acceptable. CWE-391 (Unchecked Error Condition) and CWE-755 (Improper Handling of Exceptional Conditions) directly describe this pattern. Apps that call Geolocation.getCurrentPosition(), Contacts.getAll(), or Camera.takePicture() without a preceding permission check assume the permission is permanently granted — but users can revoke permissions mid-session in iOS and Android Settings. The revocation takes effect immediately: the next call to the native API will fail with an authorization error, and if the call is not wrapped in a try-catch, the app crashes. This is the leading cause of crash loops reported after users update their privacy settings.
Medium because a crash on permission denial is reproducible by any user who revokes a permission mid-session — a common action — causing complete feature unavailability and crash reports that surface in App Store reviews.
Wrap every protected API call in a try-catch and check permission status before the call. Never assume a previously granted permission is still in effect.
import { check, PERMISSIONS, RESULTS } from 'react-native-permissions'
import Geolocation from '@react-native-community/geolocation'
async function getCurrentLocation(): Promise<{ lat: number; lng: number } | null> {
try {
const status = await check(
Platform.OS === 'ios'
? PERMISSIONS.IOS.LOCATION_WHEN_IN_USE
: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
)
if (status !== RESULTS.GRANTED) {
return null // Feature degrades gracefully
}
return await new Promise((resolve, reject) =>
Geolocation.getCurrentPosition(
pos => resolve({ lat: pos.coords.latitude, lng: pos.coords.longitude }),
reject,
{ timeout: 5000 }
)
)
} catch (error) {
console.warn('Location unavailable:', error)
return null // Never throw — callers receive null and handle accordingly
}
}
ID: mobile-permissions-privacy.graceful-degradation.no-crash-on-denial
Severity: medium
What to look for: Enumerate all permission-gated API calls (Geolocation, Camera, Contacts, etc.). For each, check whether a try-catch or error handler wraps the call. Count how many have defensive permission checks before the API call.
Pass criteria: Zero permission-gated features crash when permissions are denied. At least 100% of protected API calls have try-catch or error handling that prevents crashes. App either disables features or shows clear error messages.
Fail criteria: Any feature crashes or becomes unresponsive when a permission is denied. Any protected API call lacks error handling.
Skip (N/A) when: App requests no permissions (no permission-gated API calls found).
Detail on fail: Quote the unprotected API call. Example: "App crashes with unhandled promise rejection when location permission denied in location tracking screen"
Remediation: Check permissions before accessing protected APIs:
import { check, PERMISSIONS, RESULTS } from 'react-native-permissions'
async function getCurrentLocation() {
try {
const status = await check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE)
if (status === RESULTS.GRANTED) {
return Geolocation.getCurrentPosition(/* callback */)
} else {
throw new Error('Location permission not granted')
}
} catch (error) {
console.warn('Location unavailable:', error.message)
return null
}
}