跳到主要内容
知仓学习社ZHICANG

android-intent-security

Best practices for Android Intent security. Use this skill when auditing

不碰外部(只输出文字)无严重或高危命中android/skills

它会碰到什么

扫了多少1 个文本文件,23 KB
它会碰到什么不碰外部(只输出文字)
命中总数0 处
命中统计严重 0 · 高 0 · 中 0 · 低 0

这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。

技能内容

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.

}

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.
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().
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.
<permission
    android:name="com.example.snippets.permission.INTERNAL_COMMUNICATION"
    android:protectionLevel="signature" />

<br />

<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.
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.
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.
<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 />

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.
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
    }

    inner class LocalBinder : Binder() {
        fun doSecureWork() {
            // Verify caller identity on every transaction method call
            enforceTrustedCaller()
            // Safe to proceed with sensitive operations
        }
    }
}

<br />

*

Error handling

Handle component binding, database queries, and intent redirection failures

securely to avoid exposing internal structures.

fun safeErrorHandling(callingPackage: String?) {
    try {
        val payload = intent.getStringExtra("DATA_EXTRA") ?: throw IllegalArgumentException("Payload parameter missing.")
        // Create a specific target intent using the validated payload
        val targetIntent = Intent(this, TargetActivity::class.java).apply {
            putExtra("SECURE_PAYLOAD", payload)
        }
        startActivity(targetIntent)
    } catch (e: SecurityException) {
        // MUST log security violations for audit, but NEVER expose exception details to the user.
        Log.e("SECURITY_ERROR", "Unauthorized component transition blocked. Calling Package: ${callingPackage ?: "Unknown"}", e)
        // MUST provide generic user feedback.
        showFeedbackToUser("Process request failed: Access Denied.")
    } catch (e: IllegalArgumentException) {
        Log.w("INTEGRITY_WARNING", "Missing intent parameter", e)
    }
}

<br />

// Secure handling of ContentProvider queries on the client side:

try {

val cursor = contentResolver.query(providerUri, projection, selection, selectionArgs, null)

} catch (e: SQLiteException) {

Log.e("PROVIDER_ERROR", "ContentProvider database query failed", e)

// Secure handling: prevent raw query syntax details from leaking to UI

}

*

Reporting guidelines

When this skill is executed to apply security hardening updates to a codebase,

the agent MUST generate a structured "Best Practices and Security Alignment

Update" report for the developer. The report must be written to the session

artifact folder (or printed in the final response) and include:

  1. Security alignment area: The category of improvement applied (for example, Safe Intent Redirection, Secure PendingIntent Configuration, ContentProvider Data Guarding).
  2. Impact and priority: The potential safety risk addressed by the update (for example, Component Hijacking Prevention, Private Data Isolation).
  3. Scope of changes: A list of all modified classes, XML files, and dependencies.
  4. Implementation summary: Concrete details of the solution (for example, "Updated nested intent parsing to use the IntentSanitizer API with a strict component allowlist").
  5. Code diff: Standard unified diffs showing the exact modifications.

Best practices and security alignment update template

Use the following markdown template when reporting changes to developers:

### Best practices and security alignment update: [Security Alignment Area]

  • Improvement Description: [Brief description of the hardening update and why it's recommended]
  • Priority Level: [High / Medium / Low]
  • Alignment Action: [Summary of updates, for example, converted to FLAG_IMMUTABLE]

#### Files modified

  • [Relative path to File 1]
  • [Relative path to File 2]

#### Implementation diff

    // Insert Unified Diff here

#### Testing and verification

1. \[Step 1 to verify the component behaves correctly, for example, run component unit test\]
2. \[Step 2 to verify regression safety\] \`\`\`

*** ** * ** ***

## Antipatterns

- **NEVER** launch a nested `Intent` received from an untrusted source without verifying its target package and exported status.
- **NEVER** use sticky broadcasts (`sendStickyBroadcast`).
- **NEVER** assume an exported component is safe because it runs in a background thread or performs internal checks.
- **NEVER** expose sensitive functionalities (like SSO authentication or payment processors) to components without signature-level permission restrictions.
- **NEVER** process incoming intents in `onNewIntent` without applying the same security controls as `onCreate`.
- **NEVER** create a mutable `PendingIntent` without setting an explicit target component in the base `Intent`.
- **NEVER** use dynamic string concatenation to construct selection blocks inside a `ContentProvider` query.
- **NEVER** use `Binder.getCallingUid` inside a `BroadcastReceiver.onReceive` to identify the sender of a broadcast, as it returns the receiver's own UID, not the sender's.

## Best Practices

- **MUST** explicitly set `android:exported="false"` for all components that don't need external communication.
- **MUST** protect all exported components with custom permissions utilizing `android:protectionLevel="signature"` when communicating between family apps.
- **MUST** validate all incoming intent extras and handle missing parameters gracefully to prevent crashes.
- **MUST** rely on the system's **Protected Broadcast** mechanism for system events (for example, boot completed, package changes), as the system prevents untrusted apps from spoofing these actions.
- **MUST** protect custom broadcasts with signature-level permissions or use `RECEIVER_NOT_EXPORTED` for dynamic receivers to restrict the sender identity.
- **MUST** call `setIntent(newIntent)` inside `onNewIntent()` before processing payloads to keep active references updated.
- **MUST** use `PendingIntent.FLAG_IMMUTABLE` by default when constructing `PendingIntent` instances.
- **MUST** protect exported `ContentProviders` with `readPermission` and `writePermission`.
- **MUST** enforce parameterized selection structures in `ContentProvider` query/update methods.
- **MUST** verify the package signature fingerprint of binding applications at runtime inside exported services.
- **MUST** use `androidx.core.content.IntentSanitizer` to sanitize incoming dynamic intents before redirection, if AndroidX Core 1.9.0+ is imported in the project.

想直接用这个技能?

本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。

它属于哪个仓库

星标★ 7,406
本站分层T1
该仓技能数24
原文件路径security/android-intent-security/SKILL.md

同一个仓库里的其他技能

看这个仓库的全部 24 个技能