Generated banner for Android Permissions Security
Generated logo for Android Permissions Security

Android Permissions Security

Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)

  • Skill
  • Guidelines
  • Security
  • Apache-2.0
  • No issues found

From the author

Audits, detects gaps, and remediates Android permissions and IPC component security vulnerabilities. Use when reviewing AndroidManifest.xml, defining custom permissions, securing Services, Receivers, and Providers, verifying caller signatures and UID using Binder, or implementing runtime permission flows.
— the description by Google LLC

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a systematic approach to identifying and fixing security gaps in Android applications. It covers the secure configuration of AndroidManifest.xml, the implementation of robust IPC (Inter-Process Communication) boundaries, and the correct handling of runtime permissions. It emphasizes moving away from insecure practices like spoofable intent-based identity checks toward cryptographically secure UID verification using Binder. The guide also details how to properly define custom permissions, manage granular URI grants, and implement safe, non-blocking runtime permission request flows.

What it does

  • Audits AndroidManifest.xml for weak custom permission protection levels.
  • Provides patterns for cryptographically verifying caller identity via Binder.
  • Guides the implementation of granular URI permission management for ContentProviders.
  • Defines secure practices for protecting exported Services, Receivers, and Providers.
  • Outlines a 3-state runtime permission request flow to prevent infinite loops.
  • Recommends zero-permission alternatives like the Photo Picker for media access.

When to use it

  • During security audits of Android application codebases.
  • When configuring custom permissions for inter-app communication.
  • When securing AIDL-based bound services against Confused Deputy attacks.
  • When refactoring legacy code that relies on spoofable intent extras for identity.
  • When implementing or updating runtime permission request logic.

Advantages

  • Prevents common IPC vulnerabilities like package name spoofing.
  • Enforces the principle of least privilege through granular URI grants.
  • Reduces user friction by implementing standard, non-looping permission flows.
  • Eliminates Confused Deputy risks by using proper Binder permission enforcement.
  • Provides clear, actionable code snippets for modern Android security APIs.

Usage examples

“How do I securely verify that a caller is an authorized partner app?”
The agent provides a CallerVerifier implementation that uses Binder.getCallingUid() and PackageManager.hasSigningCertificate() to cryptographically verify the caller's signature.
“My app is vulnerable to intent spoofing. How do I fix it?”
The agent instructs you to remove all string-based package checks and replace them with Binder-based UID authentication to ensure the caller's identity is genuine.
“How should I handle runtime permissions without creating an infinite loop?”
The agent provides a 3-state flow pattern: checking grant status, showing rationale if needed, and launching the request, with a fallback to Settings for permanent denials.
“How do I restrict access to my ContentProvider to only specific partner apps?”
The agent guides you to define a custom permission with signature|knownSigner protection level and apply it to the provider, ensuring only apps with matching certificate digests can access it.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/android/android-permissions-security/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip android-permissions-security.zip -d .agents/skills

Documentation by Google LLC

Guidelines for auditing permission vulnerabilities, enforcing least privilege, securing IPC boundaries, and handling runtime permissions across the Android ecosystem.

Glossary

  • Runtime Permission: Dangerous permission (e.g., Camera, Location) that must be granted by the user at runtime on Android 6.0+ (API 23+).

  • Custom Permission: A permission defined by an app (<permission>) to restrict access to its exported components.

  • Protection Level: Attribute defining how the OS grants the permission (normal, dangerous, signature, knownSigner).

  • knownSigner: Protection flag (API 31+) granting signature-level access to partner apps matching trusted certificate digests.

  • SigningInfo: Object (API 28+) providing signing history and multiple signer certificates for an application.

  • Binder Calling Identity: Reliable UID representing the calling process obtained using Binder.getCallingUid().

  • URI Permission: Temporary, granular access granted to a content URI using grantUriPermissions and intent flags.

Prerequisites

  • Add androidx.core:core-ktx and androidx.activity:activity-ktx (or androidx.fragment:fragment-ktx) dependencies to the app-level build.gradle file for ContextCompat and ActivityResultContracts.

  • For knownSigner partner permissions (android:knownCerts), ensure the project compileSdk and targetSdk are at least 31 (Android 12).

  • For broadcast identity sharing (BroadcastOptions.setShareIdentityEnabled), ensure compileSdk and targetSdk are at least 34 (Android 14).


1. Security audit and permission gap detection playbook

When auditing an Android project for permission and IPC security vulnerabilities, systematically inspect each area:

Vulnerability AreaSearch In / PatternRiskRemediation Action
Weak Custom Permission ProtectionAndroidManifest.xml with android:protectionLevel="normal", "dangerous", or "signatureOrSystem"Unauthorized apps request or claim permission without restrictionUpgrade to signature or signature with knownSigner and android:knownCerts.
Overly Broad URI Permission Grants<grant-uri-permission android:pathPrefix="/" /> or pathPrefix="" in <provider>Leaks all provider data to third partiesReplace with explicit scoped subpath (e.g. android:pathPrefix="/shared/"). Retain android:grantUriPermissions="true". Remove any wildcard pathPrefix="/".
Missing ContentProvider Split Permissions<provider> with only generic android:permission or missing android:readPermission and android:writePermissionRead-only callers execute write operations or write-only callers access read operationsConfigure granular android:readPermission="...READ_DATA" and android:writePermission="...WRITE_DATA", AND declare the custom <permission> tags with android:protectionLevel="signature" (or signature with knownSigner) at the <manifest> root.
Spoofable Caller Identity Checksintent.getStringExtra("calling_package"), intent.getStringExtra("sender"), callingPackage == "..." in ServicesMalicious callers forge intent extras or stringsRemove string checks; authenticate caller UID cryptographically using Binder.getCallingUid() and pm.hasSigningCertificate().
Bound Service Permission BypasscheckCallingOrSelfPermission() or enforceCallingOrSelfPermission() in Binder AIDL stubsFalls back to host app UID outside IPC or when identity is cleared, causing Confused Deputy bypassReplace with enforceCallingPermission("...WRITE_DATA", ...) or checkCallingPermission("...WRITE_DATA").
Missing Method-Level Checks in Bound ServicesBound Service AIDL methods mutating state without enforcing write permissionsCallers with read-only binding access invoke mutating write operationsAdd enforceCallingPermission("...WRITE_DATA", "Caller lacks WRITE_DATA permission") inside mutating AIDL methods.
Flawed Runtime Permission FlowMissing shouldShowRequestPermissionRationale(), missing permanent denial handling, or re-requesting in loopsPoor user experience, infinite loops, or inability to recover permissionsImplement 3-state flow: check grant -> check rationale dialog -> launch contract. On permanent denial, redirect to Settings.ACTION_APPLICATION_DETAILS_SETTINGS.
Simultaneous Location RequestsarrayOf(ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION) in one requestRejected by Android 11+ (API 30+)Request foreground location (ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) first. Request ACCESS_BACKGROUND_LOCATION only after foreground is granted in a separate user step.
Broad Storage Permissions for MediaRequesting READ_EXTERNAL_STORAGE or READ_MEDIA_IMAGES for user image selectionOver-privilege, user privacy violationMigrate to zero-permission Photo Picker (ActivityResultContracts.PickVisualMedia).
Cached Permission StateStoring permission status in var cachedLocationPermissionGranted = ...Fails when permission is revoked or expiresQuery ContextCompat.checkSelfPermission() dynamically at invocation time and wrap protected calls in try-catch blocks catching SecurityException.
Insecure BroadcastssendBroadcast(intent) without package target or receiver permission; receiver without sender authenticationData interception or command spoofingSet explicit package (intent.setPackage(...)), pass receiver permission, and enable BroadcastOptions.setShareIdentityEnabled(true) (API 34+) to verify sentFromPackage.

2. Custom permissions and protection levels

When declaring custom permissions or partner certificates in the manifest, you must follow the instructions in the <permission> element guide.

  • signature: Restricts access to apps signed with the same developer key.

  • signature|knownSigner (API 31+): Restricts access to partner apps whose signing certificate digests match declared hashes in android:knownCerts.

  • normal or dangerous : Do not use for private inter-app IPC.

  • signatureOrSystem : Deprecated (API 23). Upgrade deprecated signatureOrSystem permissions to signature. Avoid in non-system apps.

Manifest snippets
Signature-level custom permission

Use signature permissions when restricting component access exclusively to apps signed with the same developer certificate by declaring the <permission> in the manifest and applying android:permission to the target component.

<permission
    android:name="com.example.snippets.permission.ACCESS_SECURE_API"
    android:protectionLevel="signature" />

<br />

KnownSigner partner permission (API 31+)

Use signature|knownSigner permissions when granting access to specific external partner apps by declaring the <permission> in the manifest with android:knownCerts referencing a resource array of trusted SHA-256 certificate digests.

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- SHA-256 fingerprints of trusted partner certificates (lowercase, no colons) -->
    <string-array name="trusted_partner_certs" tools:ignore="Typos">
        <item>103938ee4537e59e8ee792f654504fb8346fc6b346d0bbc4415fc339fcfc8ec1</item>
    </string-array>
</resources>

<br />

<permission
    android:name="com.example.snippets.permission.PARTNER_API"
    android:protectionLevel="signature|knownSigner"
    android:knownCerts="@array/trusted_partner_certs" />

<br />


3. Exported component protection and URI permissions

  • Private components : Always set android:exported="false".

  • Exported components : Require permissions (android:permission, android:readPermission, android:writePermission).

  • Granular URI Grants : Retain android:grantUriPermissions="true" for secure sharing, but scope grants to specific sub-paths. Never use wildcard pathPrefix="/".

  • Custom Permission Declarations : When splitting read and write permissions on a provider, you MUST declare the <permission> tags with android:protectionLevel="signature" (or signature|knownSigner) at the <manifest> root level.

  • Remediating Existing Wildcard Grants : You MUST find and remove any existing <grant-uri-permission android:pathPrefix="/" /> or pathPrefix="" and replace it with an explicit scoped subpath (e.g. android:pathPrefix="/shared/").

  • Granular UriPermission Management : Instead of broad manifest grants, use runtime android.content.UriPermission tokens for least-privilege per-URI sharing:

    • Grant temporary access : context.grantUriPermission(targetPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) or attach Intent.FLAG_GRANT_READ_URI_PERMISSION to outgoing intents.

    • Persist and release access : The receiving app calls contentResolver.takePersistableUriPermission(uri, modeFlags) for durable access across reboots, and contentResolver.releasePersistableUriPermission(uri, modeFlags) when durable access is no longer needed.

    • Audit active grants : Inspect contentResolver.persistedUriPermissions (List<UriPermission>) to verify granted URIs (uriPermission.uri, uriPermission.isReadPermission(), uriPermission.isWritePermission()).

    • Revoke temporary access : Call context.revokeUriPermission(targetPackage, uri, modeFlags) (API 26+), falling back to context.revokeUriPermission(uri, modeFlags) on API 25 and lower so access fails closed as soon as operations conclude.

Manifest snippet: Complete secure ContentProvider declaration

Declare the split read and write permissions at the <manifest> root:

<permission
    android:name="com.example.snippets.permission.READ_DATA"
    android:protectionLevel="signature" />
<permission
    android:name="com.example.snippets.permission.WRITE_DATA"
    android:protectionLevel="signature" />

<br />

Then configure the provider with those permissions and a scoped URI grant:

<provider
    android:name=".permissions.SecureDataProvider"
    android:authorities="com.example.snippets.partnerprovider"
    android:exported="true"
    android:readPermission="com.example.snippets.permission.READ_DATA"
    android:writePermission="com.example.snippets.permission.WRITE_DATA"
    android:grantUriPermissions="true">
    <grant-uri-permission android:pathPrefix="/shared/" />
</provider>

<br />

Runtime granular UriPermission management snippet
object UriPermissionManager {

    /** Grants temporary read access to a specific URI for a partner package. */
    fun grantScopedUriAccess(context: Context, targetPackage: String, uri: Uri) {
        context.grantUriPermission(
            targetPackage,
            uri,
            Intent.FLAG_GRANT_READ_URI_PERMISSION
        )
    }

    /** Audits active persisted URI permissions held by the application. */
    fun getActivePersistedPermissions(context: Context): List<UriPermission> {
        return context.contentResolver.persistedUriPermissions
    }

    /** Revokes temporary URI access for a specific partner package when data transfer completes. */
    fun revokeScopedUriAccess(context: Context, targetPackage: String, uri: Uri) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.revokeUriPermission(
                targetPackage,
                uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            )
        } else {
            context.revokeUriPermission(
                uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            )
        }
    }
}

<br />


4. Cryptographic caller identity verification

Never trust caller package names provided in Intents or string arguments (getCallingPackage(), intent.getStringExtra("calling_package"), intent.getStringExtra("sender"), intent.getStringExtra("package")). Intent extras and string package names are forged by malicious applications.

Critical vulnerability: Package name spoofing using intent extras
// VULNERABLE PATTERN: DO NOT DO THIS
val callingPackage = intent?.getStringExtra("calling_package")
if (callingPackage != null && SignatureUtils.verifyPartnerPackage(this, callingPackage)) {
    // A malicious app passes "com.example.partner" in the extra.
    // The signature check verifies the installed partner on disk, but the CALLER was malicious!
}

<br />

Secure pattern: Authenticate calling UID cryptographically
  1. Completely remove all intent.getStringExtra("calling_package"), intent.getStringExtra("sender"), and callingPackage == "..." checks.

  2. Obtain caller identity using Binder.getCallingUid() inside an active Binder IPC transaction (such as an AIDL Stub method or ContentProvider method).

  3. Verify caller certificate using PackageManager.hasSigningCertificate() (API 28+) or PackageManager.getPackagesForUid().

Complete CallerVerifier implementation

Apply this to the service that currently performs the spoofable check.

  • Placement : declare CallerVerifier in the same file as the service you are fixing (for example inside SecureDataService.kt). Do not move the verification into a new file and leave the original service untouched.

  • Active Binder IPC context : Binder.getCallingUid() only returns the remote caller's UID during an active Binder transaction (never inside Service.onStartCommand() or Service.onBind(), where it returns Process.myUid()). Reject callingUid == Process.myUid() so calls outside an active IPC transaction or after Binder.clearCallingIdentity() fail closed instead of bypassing verification.

  • Nullability : packageInfo.signatures is Array<Signature>?. Copy the ?: return false guard exactly as written below. Iterating packageInfo.signatures directly fails to compile with "Non-nullable value required to call an 'iterator()' method in a for-loop."

object CallerVerifier {
    private const val TRUSTED_PARTNER_SHA256 =
        "A1B2C3D4E5F60708090A0B0C0D0E0F1011121314151617181920212223242526"

    fun isCallerAuthorized(context: Context): Boolean {
        val callingUid = Binder.getCallingUid()
        if (callingUid == android.os.Process.myUid()) return false

        val pm = context.packageManager
        // Modern API 28+ check by UID:
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            val certBytes = hexStringToByteArray(TRUSTED_PARTNER_SHA256)
            return pm.hasSigningCertificate(callingUid, certBytes, PackageManager.CERT_INPUT_SHA256)
        }

        // Fallback for legacy APIs:
        val callingPackages = pm.getPackagesForUid(callingUid) ?: return false
        for (pkg in callingPackages) {
            if (verifyPackageSignature(pm, pkg)) {
                return true
            }
        }
        return false
    }

    @Suppress("DEPRECATION")
    private fun verifyPackageSignature(pm: PackageManager, packageName: String): Boolean {
        return try {
            val packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
            val signatures = packageInfo.signatures ?: return false
            for (sig in signatures) {
                val digest = java.security.MessageDigest.getInstance("SHA-256").digest(sig.toByteArray())
                val hex = digest.joinToString("") { "%02X".format(it) }
                if (hex.equals(TRUSTED_PARTNER_SHA256, ignoreCase = true)) return true
            }
            false
        } catch (e: PackageManager.NameNotFoundException) {
            false
        }
    }

    private fun hexStringToByteArray(s: String): ByteArray {
        val len = s.length
        val data = ByteArray(len / 2)
        for (i in 0 until len step 2) {
            data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
        }
        return data
    }
}

<br />


5. Method-level Binder enforcement in bound services (SecureBoundService.kt)

In bound services exposing AIDL Binder stubs, protect state-modifying methods using enforceCallingPermission().

Critical security rule: Never use CallingOrSelfPermission in services
  • NEVER use checkCallingOrSelfPermission() or enforceCallingOrSelfPermission().

  • Why: During an active Binder transaction, this method checks the caller's permission. However, if the method is invoked locally (outside of an active IPC) or if the IPC calling identity is dropped, it falls back to checking the host app's permissions. Because the host app holds the permission, this fallback grants access and bypasses the security boundary, causing a Confused Deputy vulnerability.

  • MUST USE: enforceCallingPermission("com.example.snippets.permission.WRITE_DATA", "Caller lacks WRITE_DATA permission") or checkCallingPermission("com.example.snippets.permission.WRITE_DATA").

Complete SecureBoundService implementation
class SecureBoundService : Service() {

    private val binder = object : IMyService.Stub() {
        override fun getData(): String {
            // Read-only operation guarded by manifest-level permission
            return "Confidential Data"
        }

        override fun modifyData(newData: String) {
            // MUST use enforceCallingPermission or checkCallingPermission.
            // NEVER use checkCallingOrSelfPermission or enforceCallingOrSelfPermission.
            [email protected](
                "com.example.snippets.permission.WRITE_DATA",
                "Caller lacks WRITE_DATA permission"
            )
            updateInternalState(newData)
            Log.d("SecureBoundService", "Data modified to: $newData with proper WRITE_DATA permission check")
        }
    }

    override fun onBind(intent: Intent?): IBinder = binder

    private fun updateInternalState(data: String) {
        // Internal state update logic
    }
}

<br />


6. Safe runtime permission workflow (RuntimePermissionsActivity.kt)

When implementing runtime permission requests or handling permission denial states, you must follow the instructions in the Request runtime permissions guide and Permissions overview. Implement a 3-state flow without caching states or creating infinite retry loops:

  1. Check Granted State : ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED.

  2. Check Rationale : When shouldShowRequestPermissionRationale(permission) returns true, show a rationale dialog and launch the permission request only from its Continue button.

  3. Launch Contract : ActivityResultContracts.RequestPermission().

  4. Handle Permanent Denial : In the callback, if permission was denied and !shouldShowRequestPermissionRationale(permission), guide the user to Settings.ACTION_APPLICATION_DETAILS_SETTINGS.

Complete RuntimePermissionsActivity.kt implementation
class RuntimePermissionsActivity : ComponentActivity() {

    private val cameraLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            if (isGranted) {
                startCameraPreview()
            } else {
                if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                    // User selected 'Don't ask again' or permanently denied.
                    // Direct user to Application Details Settings.
                    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                        data = Uri.fromParts("package", packageName, null)
                    }
                    startActivity(intent)
                } else {
                    showSnackbar("Camera permission is required to preview camera feed.")
                }
            }
        }

    fun requestCameraPermissionSafely() {
        val permission = Manifest.permission.CAMERA
        when {
            ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> {
                startCameraPreview()
            }
            shouldShowRequestPermissionRationale(permission) -> {
                showRationaleDialog(permission)
            }
            else -> {
                cameraLauncher.launch(permission)
            }
        }
    }

    private fun showRationaleDialog(permission: String) {
        AlertDialog.Builder(this)
            .setTitle("Camera permission required")
            .setMessage("Camera permission is needed to preview the camera feed.")
            .setPositiveButton("Continue") { _, _ ->
                cameraLauncher.launch(permission)
            }
            .setNegativeButton("Cancel", null)
            .show()
    }

    private fun startCameraPreview() {
        Log.d("RuntimePermissionsActivity", "Camera preview started")
    }

    private fun showSnackbar(msg: String) {
        Log.i("RuntimePermissionsActivity", msg)
    }
}

<br />


7. Zero-permission alternatives and sequential flows

When implementing photo or media selection, you must follow the instructions in the Photo Picker API guide to avoid requesting broad storage permissions.

  • Photo Picker : Prefer ActivityResultContracts.PickVisualMedia() over requesting wide storage permissions (READ_MEDIA_IMAGES, READ_EXTERNAL_STORAGE).

  • Sequential Location Flow : Request ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION first. Never request ACCESS_BACKGROUND_LOCATION simultaneously. Only request background location in a separate step after foreground is granted.

Photo picker implementation
private val photoPickerLauncher =
    registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
        if (uri != null) {
            handleImageUri(uri)
        }
    }

fun selectPhoto() {
    photoPickerLauncher.launch(
        PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
    )
}

<br />

Sequential location request implementation
private val foregroundLocationLauncher =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        val fineGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
        val coarseGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: false
        if (fineGranted || coarseGranted) {
            startForegroundLocationUpdates()
        }
    }

fun requestForegroundLocation() {
    foregroundLocationLauncher.launch(
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
        )
    )
}

// Background location requested only AFTER foreground is granted and user explicitly opts in:
private val backgroundLocationLauncher =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        if (isGranted) {
            startBackgroundTracking()
        }
    }

<br />


8. Stateless checks and error boundaries

  • Never cache permission states in in-memory variables (e.g. cachedLocationPermissionGranted). Check ContextCompat.checkSelfPermission() dynamically before each operation.

  • Wrap API calls in try-catch : Catch SecurityException around permission-protected framework calls (e.g., location queries) to handle one-time permission revocation gracefully.

fun performLocationAccess() {
    val fine = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
    val coarse = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
    if (fine != PackageManager.PERMISSION_GRANTED && coarse != PackageManager.PERMISSION_GRANTED) {
        requestForegroundLocation()
        return
    }

    val provider = if (fine == PackageManager.PERMISSION_GRANTED) {
        LocationManager.GPS_PROVIDER
    } else {
        LocationManager.NETWORK_PROVIDER
    }

    try {
        val location = locationManager.getLastKnownLocation(provider)
        processLocation(location)
    } catch (e: SecurityException) {
        Log.e("LocationAccess", "Permission revoked at runtime", e)
    }
}

<br />


9. Secure broadcasts (API 34+ share identity)

Use explicit intents, required receiver permissions, or Android 14+ broadcast options:

// Sender: Enforce permission and share identity
val intent = Intent("com.example.snippets.permission.ACTION_SECRET_UPDATE").apply {
    setPackage("com.example.partner") // Explicit target
}
val isUdc = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
val options = if (isUdc) {
    BroadcastOptions.makeBasic().apply {
        setShareIdentityEnabled(true)
    }.toBundle()
} else null

sendBroadcast(intent, "com.example.snippets.permission.RECEIVE_SECRET_UPDATE", options)

<br />

// Receiver: Validate sender on Android 14+
class ProtectedReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == "com.example.snippets.permission.ACTION_SECRET_UPDATE") {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                val sender = sentFromPackage
                if (sender != "com.example.trusted_sender") {
                    return // Reject unauthorized sender
                }
            }
            processUpdate(intent)
        }
    }

    private fun processUpdate(intent: Intent) {}
}

<br />

Legacy compatibility (Pre-API 34) : On API 33 and lower, never authenticate a broadcast sender using PendingIntent.getCreatorPackage() or PendingIntent.getCreatorUid(), because any app that acquires a trusted app's PendingIntent can attach and send it (see Sender of Pending Intents). Instead, restrict the <receiver> with a signature or signature|knownSigner (API 31+) permission in the manifest; if minSdkVersion is lower than 31, use a bound Service or ContentProvider and verify Binder.getCallingUid().


Quality checklist

DO
  • MUST declare <permission android:name="..." android:protectionLevel="signature" /> (or signature|knownSigner) at the <manifest> root for custom permissions and ContentProvider split permissions.

  • MUST explicitly set android:exported="false" for all private components to remove the need for custom permission management.

  • MUST authenticate callers cryptographically using Binder.getCallingUid() and certificate hashes (PackageManager.hasSigningCertificate or getPackagesForUid) rather than checking spoofable intent extras.

  • MUST check shouldShowRequestPermissionRationale() before runtime prompts and during denial handling.

  • MUST direct user to Settings.ACTION_APPLICATION_DETAILS_SETTINGS when permission is permanently denied.

  • MUST enforce method-level checks with enforceCallingPermission() or checkCallingPermission() on modifying AIDL methods.

  • MUST scope ContentProvider URI permissions to explicit sub-paths (e.g. android:pathPrefix="/shared/") while retaining android:grantUriPermissions="true".

  • MUST catch SecurityException around protected API calls.

  • MUST request foreground and background location permissions sequentially, never simultaneously.

NEVER
  • NEVER extract or verify package names from Intent extras (intent.getStringExtra("calling_package"), intent.getStringExtra("sender"), intent.getStringExtra("package")). Always authenticate using Binder.getCallingUid() and certificates.

  • NEVER authenticate a broadcast or IPC sender using PendingIntent.getCreatorPackage() or PendingIntent.getCreatorUid().

  • NEVER remediate caller spoofing by merely adding manifest permissions while keeping spoofable intent extra checks.

  • NEVER use checkCallingOrSelfPermission() or enforceCallingOrSelfPermission() to guard external caller methods. Always use enforceCallingPermission() or checkCallingPermission().

  • NEVER use deprecated signatureOrSystem.

  • NEVER declare or retain <grant-uri-permission android:pathPrefix="/" /> or pathPrefix="". Always replace with a scoped sub-path.

  • NEVER create infinite permission request loops on denial.

  • NEVER request foreground and background location permissions in the same request.

  • NEVER cache permission grant states in in-memory fields.

Files

references/android/guide/topics/permissions/overview.md

App permissions help support user privacy by protecting access to the following:

  • Restricted data, such as system state and users' contact information

  • Restricted actions, such as connecting to a paired device and recording audio

Video

This page provides an overview to how Android permissions work, including a high-level workflow for using permissions, descriptions of different types of permissions, and some best practices for using permissions in your app. Other pages explain how to minimize your app's requests for permissions, declare permissions, request runtime permissions, and restrict how other apps can interact with your app's components.

To view a complete list of Android app permissions, visit the permissions API reference page.

To view some sample apps that demonstrate the permissions workflow, visit the Android permissions samples repository on GitHub.

Workflow for using permissions

If your app offers functionality that might require access to restricted data or restricted actions, determine whether you can get the information or perform the actions without needing to declare permissions. You can fulfill many use cases in your app, such as taking photos, pausing media playback, and displaying relevant ads, without needing to declare any permissions.

If you decide that your app must access restricted data or perform restricted actions to fulfill a use case, declare the appropriate permissions. Some permissions, known as install-time permissions, are automatically granted when your app is installed. Other permissions, known as runtime permissions, require your app to go a step further and request the permission at runtime.

Figure 1 illustrates the workflow for using app permissions: Image: untitled Figure 1. High-level workflow for using permissions on Android.

Types of permissions

Android categorizes permissions into different types, including install-time permissions, runtime permissions, and special permissions. Each permission's type indicates the scope of restricted data that your app can access, and the scope of restricted actions that your app can perform, when the system grants your app that permission. The protection level for each permission is based on its type and is shown on the permissions API reference page.

Install-time permissions

Image: The left image shows a list of an app's install-time permissions. The right image shows a pop-up dialog that contains 2 options: allow and deny. Figure 2. The list of an app's install-time permissions, which appears in an app store.

Install-time permissions give your app limited access to restricted data or let your app perform restricted actions that minimally affect the system or other apps. When you declare install-time permissions in your app, an app store presents an install-time permission notice to the user when they view an app's details page, as shown in figure 2. The system automatically grants your app the permissions when the user installs your app.

Android includes several sub-types of install-time permissions, including normal permissions and signature permissions.

Normal permissions

These permissions allow access to data and actions that extend beyond your app's sandbox but present very little risk to the user's privacy and the operation of other apps.

The system assigns the normal protection level to normal permissions.

Signature permissions

The system grants a signature permission to an app only when the app is signed by the same certificate as the app or the OS that defines the permission.

Applications that implement privileged services, such as autofill or VPN services, also make use of signature permissions. These apps require service-binding signature permissions so that only the system can bind to the services.

[!NOTE] Note: Some signature permissions aren't for use by third-party apps.

The system assigns the signature protection level to signature permissions.

Runtime permissions

Image: A pop-up dialog that contains 2 options: allow and deny. Figure 3. The system permission prompt that appears when your app requests a runtime permission.

Runtime permissions, also known as dangerous permissions, give your app additional access to restricted data or let your app perform restricted actions that more substantially affect the system and other apps. Therefore, you need to request runtime permissions in your app before you can access the restricted data or perform restricted actions. Don't assume that these permissions have been previously granted---check them and, if needed, request them before each access.

When your app requests a runtime permission, the system presents a runtime permission prompt, as shown in figure 3.

Many runtime permissions access private user data, a special type of restricted data that includes potentially sensitive information. Examples of private user data include location and contact information.

The microphone and camera provide access to particularly sensitive information. Therefore, the system helps you explain why your app accesses this information.

The system assigns the dangerous protection level to runtime permissions.

Special permissions

Special permissions correspond to particular app operations. Only the platform and OEMs can define special permissions. Additionally, the platform and OEMs usually define special permissions when they want to protect access to particularly powerful actions, such as drawing over other apps.

The Special app access page in system settings contains a set of user-toggleable operations. Many of these operations are implemented as special permissions.

Learn more about how to request special permissions.

The system assigns the appop protection level to special permissions.

Permission groups

Permissions can belong to permission groups. Permission groups consist of a set of logically related permissions. For example, permissions to send and receive SMS messages might belong to the same group, as they both relate to the application's interaction with SMS.

Permission groups help the system minimize the number of system dialogs that are presented to the user when an app requests closely related permissions. When a user is presented with a prompt to grant permissions for an application, permissions belonging to the same group are presented in the same interface. However, permissions can change groups without notice, so don't assume that a particular permission is grouped with any other permission.

Best practices

App permissions build on system security features and help Android support the following goals related to user privacy:

  • Control: The user has control over the data that they share with apps.

  • Transparency: The user understands what data an app uses and why the app accesses this data.

  • Data minimization: An app accesses and uses only the data that's required for a specific task or action that the user invokes.

This section presents a set of core best practices for using permissions effectively in your app. For more details on how you can work with permissions on Android, visit the app permissions best practices page.

Request a minimal number of permissions

When the user requests a particular action in your app, your app should request only the permissions that it needs to complete that action. Depending on how you are using the permissions, there might be an alternative way to fulfill your app's use case without relying on access to sensitive information.

Associate runtime permissions with specific actions

Request permissions as late into the flow of your app's use cases as possible. For example, if your app lets users send audio messages to others, wait until the user has navigated to the messaging screen and has pressed the Send audio message button. After the user presses the button, your app can then request access to the microphone.

Consider your app's dependencies

When you include a library, you also inherit its permission requirements. Be aware of the permissions that each dependency requires and what those permissions are used for.

Be transparent

When you make a permissions request, be clear about what you're accessing, why, and what functionalities are affected if permissions are denied, so users can make informed decisions.

Make system accesses explicit

When you access sensitive data or hardware, such as the camera or microphone, provide a continuous indication in your app if the system doesn't already provide these indicators. This reminder helps users understand exactly when your app accesses restricted data or performs restricted actions.

Permissions in system components

Permissions aren't only for requesting system functionality. Your app's system components can restrict which other apps can interact with your app, as described on the page about how to restrict interactions with other apps.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Google LLC
Publisher
Android (Google) (Official)
Distributed by
Android (Google)
Source
android/skills · security/android-permissions-security · commit bf68e2d
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.txt)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download android-permissions-security.zip