Assuming a permission granted at app install remains in effect for the lifetime of the app is incorrect on both iOS and Android. Users can revoke permissions at any time in system Settings — the next API call after revocation will fail with an authorization error. OWASP A01 (Broken Access Control) includes improper assumption of access grants as a sub-class. CWE-272 (Least Privilege Violation) applies when code accesses hardware without verifying current authorization state. Beyond correctness, skip-the-check patterns are fragile across OS upgrades: iOS 15 introduced 'last week you used' notifications that prompt users to reconsider permissions, and Android 11 introduced auto-reset for apps unused for months — both of which silently revoke permissions that code was assuming were permanent.
Low because the failure mode — accessing a revoked permission — produces a permission-denied error rather than data exposure, but uncaught errors cascade into crashes that are the top user complaint for permission-gated features.
Call check() before every protected API access — not just before the initial request. Cache the result for the duration of a single user interaction, not across sessions.
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions'
async function getContacts(): Promise<Contact[]> {
// Check current status — do not assume granted from a previous session
let status = await check(PERMISSIONS.IOS.CONTACTS)
if (status === RESULTS.DENIED) {
status = await request(PERMISSIONS.IOS.CONTACTS)
}
if (status !== RESULTS.GRANTED) {
return [] // Degrade gracefully — empty list, not a crash
}
return ContactsModule.getAll()
}
For frequent calls (e.g., GPS polling), check permission status at the start of each polling cycle rather than assuming it is still valid from the previous one. On Android, permission revocation takes effect at the process level — the app does not need to restart for the revocation to apply.
ID: mobile-permissions-privacy.graceful-degradation.permission-status-check
Severity: low
What to look for: Count all protected API calls (camera, location, contacts, microphone, etc.). For each, check whether a check() or equivalent permission status call precedes the API access. Report: "X of Y protected API calls check permission status first."
Pass criteria: At least 100% of protected API calls are preceded by a permission status check (check(PERMISSION), Permissions.check(), or equivalent). Denied/pending states are handled before the API is accessed.
Fail criteria: Any code calls protected APIs without checking permission status first (assumes permissions are already granted). Should not pass when only the initial request checks status but subsequent calls assume the permission persists without re-checking.
Skip (N/A) when: App uses no permissions (no protected API calls found).
Detail on fail: Quote the unguarded API call. Example: "Geolocation.getCurrentPosition called without checking location permission status first"
Remediation: Always check permission status before accessing protected APIs:
import { check, PERMISSIONS, RESULTS, request } from 'react-native-permissions'
async function getDeviceContacts() {
const status = await check(PERMISSIONS.IOS.CONTACTS)
if (status === RESULTS.GRANTED) {
return ContactsModule.getAll()
} else if (status === RESULTS.DENIED) {
// Request permission
const result = await request(PERMISSIONS.IOS.CONTACTS)
if (result === RESULTS.GRANTED) {
return ContactsModule.getAll()
}
}
// Return empty list or null if denied
return []
}