Skip to main content

android-intent-security

Best practices for Android Intent security. Use this skill when auditing component configurations in AndroidManifest.xml activities, services, receivers) or source code handling incoming Intents (getIntent, getParcelableExtra) to prevent Intent Redirection and unauthorized access.

Informations de source

Dépôt
android/skills
Dernière activité de la source
25 septembre 2026 à 14:03
Langue détectée de SKILL.md
anglais
Étoiles
7 763
Forks
511

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
android-intent-security
description
Best practices for Android Intent security. Use this skill when auditing component configurations in AndroidManifest.xml activities, services, receivers) or source code handling incoming Intents (getIntent, getParcelableExtra) to prevent Intent Redirection and unauthorized access.
license
Complete terms in LICENSE.txt
metadata
{"author":"Google LLC","last-updated":"2026-09-25","keywords":["recipe","Android","Security","Intent","Redirection","PendingIntent","ContentProvider","Service","Signature","Verification","sanitizer","Vulnerability","Best Practices"]}
This skill provides guidelines and patterns to secure Android components (Activities, Services, Broadcast Receivers, Content Providers) and handle Intents safely, preventing privilege escalation and unauthorized access. ## Glossary - **Intent:** An asynchronous messaging object used to request an action from another app component. - **Exported component:** A component (`android:exported="true"`) that can be launched by other apps on the device. - **Sticky intent:** A broadcast intent that remains in the system cache after it's sent, allowing any app to retrieve its contents. - **Signature permission:** A permission whose protection level is set to `signature`, granted only to apps signed with the same developer key. - **onNewIntent:** An activity lifecycle callback invoked when an activity is launched with `FLAG_ACTIVITY_SINGLE_TOP` and is already running at the top of the history stack. - **PendingIntent:** A token granted to a foreign application (for example, system services) allowing it to execute a predefined Intent with the creator's permissions. - **Mutable PendingIntent:** A PendingIntent whose underlying Intent parameters can be modified by the receiving application. - **ContentProvider:** A component that encapsulates data and provides it to other applications via standard query/insert interfaces. - **IntentSanitizer:** A utility class in AndroidX Core used to build a safe, sanitized copy of an incoming Intent by filtering out unauthorized components, actions, or extras. - **Intent redirection (forwarding):** A vulnerability where an application receives an intent from an untrusted source and uses it to launch a private, non-exported component. ## Prerequisites - The agent **MUST** be able to describe the function and security implications of `onCreate`, `onNewIntent`, and the `singleTop` launch mode. - The agent **MUST** be able to declare `<activity>`, `<service>`, `<receiver>`, and `<provider>` tags in `AndroidManifest.xml` and define their `android:exported` and `android:permission` attributes. - The agent **MUST** be able to implement signature verification checks using `PackageManager`. ## Limitations - This skill focuses on local inter-component and inter-app communication security on the Android platform. - This skill **doesn't** cover network security, web integration, or host-to-server security. ## Setup and dependencies - **Android SDK:** Minimum API Level 23 (Android 6.0) is required for standard hardware-backed keystore operations and component validation. - **AndroidX Core Library:** `androidx.core:core:1.9.0` or higher is **mandatory** to leverage `IntentSanitizer`. - **Standard API access:** Standard Android `PackageManager` APIs are required for runtime component verification. *** ** * ** *** ## Intent security logic and decisions ### 1. Intent routing comparison Evaluate the security features of different intent delivery methods: | Intent Delivery Method | Scope | Recommended Use Case | |---|---|---| | Explicit Intent (Internal) | App Private | Launching internal activities/services | | Implicit Intent | System Wide | Launching system camera, dialer, or sharing | | Local Broadcasts (LocalBroadcastManager) (DEPRECATED) | App Private | Internal asynchronous event routing. **Deprecated**: Use in-app observers like Kotlin Flows/SharedFlow, LiveData, or reactive patterns instead. | | System Broadcasts | System Wide | Receiving system events (NFC, Bluetooth) | ### 2. PendingIntent mutability flag options Evaluate the security implications of PendingIntent mutability flags: | Flag Name | Mutability | Recommended Use Case | |---|---|---| | `PendingIntent.FLAG_IMMUTABLE` | Immutable | Default for almost all PendingIntents, such as alarms and notifications | | `PendingIntent.FLAG_MUTABLE` | Mutable | Inline notifications replies, slice actions (requires explicit target intent) | ### 3. Intent handling and redirection logic IF (the component receives a nested Intent as an extra) { IF (AndroidX Core 1.9.0+ and higher is available) { MUST construct an `IntentSanitizer` to explicitly allowlist components, actions, data, and extras. MUST call `sanitizeByThrowing()` or `sanitizeByFiltering()` before launching. } ELSE { MUST verify that the nested Intent's target package matches the current application package. MUST verify that the target component of the nested Intent is publicly exported. } NEVER launch the nested Intent directly without validation. } ELSE IF (the component handles broadcasts) { MUST rely on the system's Protected Broadcast mechanism for system events (which guarantees the sender is the system framework). MUST protect custom receivers with signature-level permissions or use `RECEIVER_NOT_EXPORTED` for dynamic receivers to restrict the sender. IF (using `ContextCompat.registerReceiver` with `RECEIVER_NOT_EXPORTED` AND `minSdk < 33`) { MUST verify `<permission android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" android:protectionLevel="signature" />` and `<uses-permission android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" />` are declared in `AndroidManifest.xml`. } } ### 4. PendingIntent security logic IF (a PendingIntent is created for delivery to another application) { MUST use `PendingIntent.FLAG_IMMUTABLE` by default. IF (the PendingIntent must be mutable) { MUST set the explicit target component or package name on the base `Intent`. NEVER create an implicit, mutable `PendingIntent`. } } ### 5. ContentProvider security logic IF (the ContentProvider is only for internal app use) { MUST set `android:exported="false"`. } ELSE { MUST protect it with `android:readPermission` and `android:writePermission`. MUST set `android:grantUriPermissions="false"` unless temporary URL access is strictly required. } ### 6. Service caller verification logic IF (an exported service communicates with trusted sister/partner apps) { MUST retrieve the calling UID using `Binder.getCallingUid()` and resolve it to package names using `PackageManager.getPackagesForUid()`. MUST verify that the calling package signature fingerprint matches your trusted certificate hash. } *** ** * ** *** ## Code and configuration patterns ### 1. Safe intent redirection (manual verification) Validate the target of a nested intent before launching it when modern sanitization libraries are unavailable. - **Expected Inputs:** - An incoming `Intent` containing a nested `Intent` extra named `EXTRA_NESTED_INTENT`. - **Expected Outputs:** - Launches the target component if safe; throws `SecurityException` if validation fails. ```kotlin fun safeIntentRedirectionManual() { val nestedIntent = IntentCompat.getParcelableExtra(intent, "EXTRA_NESTED_INTENT", Intent::class.java) if (nestedIntent != null) { // 1. Check for URI permission grants to prevent URI permission bypass val hasUriPermissionGrants = ( nestedIntent.flags and ( Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION ) ) != 0 if (hasUriPermissionGrants) { throw SecurityException("Nested intent contains forbidden URI permission grant flags!") } val pm = packageManager val target = nestedIntent.resolveActivity(pm) if (target != null) { // 2. Verify target is within the same package if (target.packageName != packageName) { throw SecurityException("Cross-app intent redirection is forbidden!") } try { // 3. Verify target activity is exported val info = pm.getActivityInfo(target, 0) if (!info.exported) { throw SecurityException("Target activity is private: ${target.className}") } // 4. Explicitly set the component to prevent intent interception nestedIntent.component = target // Safe to launch startActivity(nestedIntent) } catch (e: PackageManager.NameNotFoundException) { Log.e("Security", "Failed to resolve target activity", e) } } } } ``` <br /> ### 2. Safe intent redirection using IntentSanitizer Filter or reject dynamic intents using AndroidX `IntentSanitizer` (AndroidX Core 1.9.0+). - **Expected Inputs:** - An untrusted incoming `Intent`. - **Expected Outputs:** - `Intent`: A sanitized copy containing only allowlisted components, categories, and actions. Throws `SecurityException` on violations if using `sanitizeByThrowing()`. ```kotlin fun safeIntentRedirectionSanitizer() { val untrustedIntent = IntentCompat.getParcelableExtra(intent, "EXTRA_NESTED_INTENT", Intent::class.java) if (untrustedIntent != null) { // Define the strict boundaries for allowed redirection target val sanitizer = IntentSanitizer.Builder() .allowComponent(ComponentName("com.example.app", "com.example.app.SafeTargetActivity")) // Explicitly allowed target .allowAction(Intent.ACTION_VIEW) // Explicitly allowed actions .allowDataWithAuthority("com.example.app.provider") // Allowed URI authority .allowType("text/plain") // Allowed mime type .allowExtra("user_display_name", String::class.java) // Safe type-enforced extras // Note: URI permission flags are NOT allowed, so the sanitizer will automatically strip or throw on them .build() try { // Option A: Throws SecurityException if the intent violates policies val safeIntent = sanitizer.sanitizeByThrowing(untrustedIntent) startActivity(safeIntent) } catch (e: SecurityException) { Log.e("SECURITY_ALERT", "Attempted launch of non-allowlisted intent blocked", e) } // Option B: Silently filter and launch only the authorized parts (no exception thrown) // val filteredIntent = sanitizer.sanitizeByFiltering(untrustedIntent) // startActivity(filteredIntent) } } ``` <br /> ### 3. Custom signature permission protection Declare a custom signature-level permission in the manifest to secure family app communication. - **Expected Inputs:** Manifest configuration. - **Expected Outputs:** An activity that can only be launched by apps signed with the same developer certificate. ```xml <permission android:name="com.example.snippets.permission.INTERNAL_COMMUNICATION" android:protectionLevel="signature" /> ``` <br /> ```xml <activity android:name=".intents.InternalSharingActivity" android:exported="true" android:permission="com.example.snippets.permission.INTERNAL_COMMUNICATION"> <intent-filter> <action android:name="com.example.snippets.ACTION_SHARE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> ``` <br /> ### 4. Safe onNewIntent lifecycle verification (warm boot protection) Ensure that activities reusing dynamic intents (for example, in background launch paths) apply the same strict security filters inside `onNewIntent`. - **Expected Inputs:** - `newIntent` (`Intent`): The newly delivered intent. - **Expected Outputs:** - Executes processing logic only if the new intent passes security validation. ```kotlin override fun onNewIntent(newIntent: Intent) { super.onNewIntent(newIntent) // Set the intent to ensure intent returns the new one intent = newIntent // Validate the intent payload if (validateIntent(newIntent)) { processIntentPayload(newIntent) } else { Log.w("SECURITY_ALERT", "Received invalid or insecure intent during warm boot") } } private fun validateIntent(intent: Intent): Boolean { return intent.hasExtra("VALID_PAYLOAD_MARKER") } ``` <br /> ### 5. Secure PendingIntent creation Enforce immutability unless mutability is explicitly required. - **Expected Inputs (Immutable):** An intent target. - **Expected Outputs (Immutable):** A `PendingIntent` that cannot be altered by the receiver. - **Expected Inputs (Mutable):** An intent with an explicit component set. - **Expected Outputs (Mutable):** A mutable `PendingIntent` locked to a specific receiver component to prevent hijacking. ```kotlin fun createPendingIntents(context: Context) { // 1. Secure Immutable PendingIntent (Default) val intent = Intent(context, TargetActivity::class.java) val pendingIntent = PendingIntent.getActivity( context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) // 2. Secure Mutable PendingIntent (e.g., Notification Direct Reply) val mutableIntent = Intent().apply { // MUST set explicit target component to prevent redirection hijacking component = ComponentName(context, ReplyReceiver::class.java) } val mutablePendingIntent = PendingIntent.getBroadcast( context, 0, mutableIntent, PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) } ``` <br /> ### 6. Secure ContentProvider configuration and queries Expose a ContentProvider securely and parameterize queries to prevent SQL injection. - **Expected Inputs:** - `uri` (`Uri`): The query URI. - `projection` (`String[]`): Columns to retrieve. - `selection` (`String`): Query criteria. - `selectionArgs` (`String[]`): Values mapping to selection placeholders (`?`). - **Expected Outputs:** - `Cursor`: Filtered query results, strictly bound to projection maps. ```xml <provider android:name=".intents.SecureDataProvider" android:authorities="com.example.snippets.provider" android:exported="true" android:readPermission="com.example.snippets.permission.READ_DATA" android:writePermission="com.example.snippets.permission.WRITE_DATA" android:grantUriPermissions="false" /> ``` <br /> ```kotlin override fun query( uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String? ): Cursor? { val queryBuilder = SQLiteQueryBuilder() queryBuilder.tables = tableName // Strict projection map to prevent querying unauthorized columns queryBuilder.projectionMap = mapOf( "_id" to "_id", "display_name" to "display_name" ) // Enable strict validation (always available since minSdk is 36) queryBuilder.setStrict(true) queryBuilder.setStrictColumns(true) queryBuilder.setStrictGrammar(true) // MUST parameterize selection criteria; NEVER append selection strings directly val db = dbHelper.readableDatabase return queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder) } ``` <br /> ### 7. Service caller signature verification Verify the calling application's signature before binding to a service. - **Expected Inputs:** - `intent` (`Intent`): The binding request intent. - **Expected Outputs:** - `IBinder`: Local binder instance if caller signature matches trusted partner; throws `SecurityException` otherwise. ```kotlin class SecureBoundService : Service() { companion object { // Expected SHA-256 hash of the trusted app's signing certificate (Base64 encoded) private const val TRUSTED_PARTNER_SHA256 = "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V=" } override fun onBind(intent: Intent): IBinder { // Return the binder. Do NOT perform signature verification in onBind() because // the binder connection is cached by Android, which can bypass checks on subsequent binds. return LocalBinder() } private fun enforceTrustedCaller() { val callingUid = Binder.getCallingUid() // Allow calls from the same application if (callingUid == Process.myUid()) { return } val pm = packageManager val packages = pm.getPackagesForUid(callingUid) if (packages.isNullOrEmpty() || !verifySignature(pm, packages[0])) { throw SecurityException("Access Denied: Caller signature is untrusted.") } } private fun verifySignature(pm: PackageManager, packageName: String): Boolean { try { val trustedSha256Raw = Base64.decode(TRUSTED_PARTNER_SHA256, Base64.DEFAULT) // API 28+ handles rotated certificates and avoids manual hashing. // Since minSdk is 36, this is always available. return pm.hasSigningCertificate(packageName, trustedSha256Raw, PackageManager.CERT_INPUT_SHA256) } catch (e: Exception) { Log.e("SECURITY_ERROR", "Verification failed for package: $packageName", e) } return false }
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub