android-intent-security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
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.
本技能提供了保护Android组件(Activity、Service、Broadcast Receiver、Content Provider)并安全处理Intent的指南和模式,可防止权限提升和未授权访问。

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.
  • Intent:一种异步消息对象,用于请求其他应用组件执行操作。
  • 导出组件:指
    android:exported="true"
    的组件,设备上的其他应用可启动该组件。
  • 粘性Intent:发送后仍保留在系统缓存中的广播Intent,任何应用都可获取其内容。
  • 签名权限:保护级别设为
    signature
    的权限,仅授予使用同一开发者密钥签名的应用。
  • onNewIntent:当Activity以
    FLAG_ACTIVITY_SINGLE_TOP
    模式启动且已位于历史堆栈顶部时,调用的Activity生命周期回调方法。
  • PendingIntent:授予外部应用(如系统服务)的令牌,允许其以创建者的权限执行预定义的Intent。
  • 可变PendingIntent:底层Intent参数可被接收应用修改的PendingIntent。
  • ContentProvider:封装数据并通过标准查询/插入接口向其他应用提供数据的组件。
  • IntentSanitizer:AndroidX Core中的工具类,用于通过过滤未授权组件、操作或附加数据,构建传入Intent的安全净化副本。
  • Intent重定向(转发):一种漏洞,指应用接收来自不可信来源的Intent,并使用它启动私有、未导出的组件。

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
    .
  • 智能体必须能够描述
    onCreate
    onNewIntent
    singleTop
    启动模式的功能及安全影响。
  • 智能体必须能够在
    AndroidManifest.xml
    中声明
    <activity>
    <service>
    <receiver>
    <provider>
    标签,并定义它们的
    android:exported
    android:permission
    属性。
  • 智能体必须能够使用
    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.
  • 本技能聚焦于Android平台上的本地组件间和应用间通信安全。
  • 本技能涵盖网络安全、Web集成或主机到服务器的安全。

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.

  • Android SDK:标准硬件支持的密钥库操作和组件验证需要最低API级别23(Android 6.0)。
  • AndroidX Core库:必须使用
    androidx.core:core:1.9.0
    或更高版本,以利用
    IntentSanitizer
  • 标准API访问:运行时组件验证需要标准Android
    PackageManager
    API。

Intent security logic and decisions

Intent安全逻辑与决策

1. Intent routing comparison

1. Intent路由对比

Evaluate the security features of different intent delivery methods:
Intent Delivery MethodScopeRecommended Use Case
Explicit Intent (Internal)App PrivateLaunching internal activities/services
Implicit IntentSystem WideLaunching system camera, dialer, or sharing
Local Broadcasts (LocalBroadcastManager) (DEPRECATED)App PrivateInternal asynchronous event routing. Deprecated: Use in-app observers like Kotlin Flows/SharedFlow, LiveData, or reactive patterns instead.
System BroadcastsSystem WideReceiving system events (NFC, Bluetooth)
评估不同Intent传递方式的安全特性:
Intent传递方式范围推荐使用场景
显式Intent(内部)应用私有启动内部Activity/Service
隐式Intent系统全局启动系统相机、拨号器或分享功能
本地广播(LocalBroadcastManager)(已废弃)应用私有内部异步事件路由。已废弃:改用应用内观察者,如Kotlin Flows/SharedFlow、LiveData或响应式模式。
系统广播系统全局接收系统事件(NFC、蓝牙)

2. PendingIntent mutability flag options

2. PendingIntent可变性选项

Evaluate the security implications of PendingIntent mutability flags:
Flag NameMutabilityRecommended Use Case
PendingIntent.FLAG_IMMUTABLE
ImmutableDefault for almost all PendingIntents, such as alarms and notifications
PendingIntent.FLAG_MUTABLE
MutableInline notifications replies, slice actions (requires explicit target intent)
评估PendingIntent可变性标志的安全影响:
标志名称可变性推荐使用场景
PendingIntent.FLAG_IMMUTABLE
不可变几乎所有PendingIntent的默认选项,如闹钟和通知
PendingIntent.FLAG_MUTABLE
可变通知内联回复、切片操作(需要显式目标Intent)

3. Intent handling and redirection logic

3. Intent处理与重定向逻辑

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. }
如果(组件接收嵌套Intent作为附加数据){ 如果(AndroidX Core 1.9.0及以上版本可用){ 必须构建
IntentSanitizer
,显式允许组件、操作、数据和附加数据的白名单。必须在启动前调用
sanitizeByThrowing()
sanitizeByFiltering()
。 } 否则 { 必须验证嵌套Intent的目标包与当前应用包匹配。必须验证嵌套Intent的目标组件是公开导出的。 } 绝不能在未验证的情况下直接启动嵌套Intent。 } 否则如果(组件处理广播){ 必须依赖系统的Protected Broadcast机制处理系统事件(该机制保证发送者是系统框架)。必须使用签名级权限保护自定义接收器,或对动态接收器使用
RECEIVER_NOT_EXPORTED
以限制发送者。 }

4. PendingIntent security logic

4. PendingIntent安全逻辑

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
. } }
如果(创建PendingIntent以传递给其他应用){ 默认必须使用
PendingIntent.FLAG_IMMUTABLE
。如果(PendingIntent必须是可变的){ 必须在基础
Intent
上设置显式目标组件或包名。绝不能创建隐式的可变
PendingIntent
。 } }

5. ContentProvider security logic

5. ContentProvider安全逻辑

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. }
如果(ContentProvider仅用于应用内部){ 必须设置
android:exported="false"
。 } 否则 { 必须使用
android:readPermission
android:writePermission
保护它。除非严格需要临时URL访问,否则必须设置
android:grantUriPermissions="false"
。 }

6. Service caller verification logic

6. Service调用者验证逻辑

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

如果(导出的Service与可信的关联/合作伙伴应用通信){ 必须使用
Binder.getCallingUid()
获取调用者UID,并使用
PackageManager.getPackagesForUid()
将其解析为包名。必须验证调用包的签名指纹与可信证书哈希匹配。 }

Code and configuration patterns

代码与配置模式

1. Safe intent redirection (manual verification)

1. 安全Intent重定向(手动验证)

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.
<br />
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 />
当现代净化库不可用时,在启动嵌套Intent前验证其目标。
  • 预期输入:
    • 包含名为
      EXTRA_NESTED_INTENT
      的嵌套Intent附加数据的传入
      Intent
  • 预期输出:
    • 如果安全则启动目标组件;如果验证失败则抛出
      SecurityException
<br />
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

2. 使用IntentSanitizer实现安全Intent重定向

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()
      .
<br />
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 />
使用AndroidX
IntentSanitizer
(AndroidX Core 1.9.0+)过滤或拒绝动态Intent。
  • 预期输入:
    • 不可信的传入
      Intent
  • 预期输出:
    • Intent
      :仅包含白名单组件、类别和操作的净化副本。如果使用
      sanitizeByThrowing()
      ,违反规则时会抛出
      SecurityException
<br />
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

3. 自定义签名权限保护

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.
<br />
xml
<permission
    android:name="com.example.snippets.permission.INTERNAL_COMMUNICATION"
    android:protectionLevel="signature" />
   
<br /> <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 />
在清单中声明自定义签名级权限,以保护关联应用间的通信。
  • 预期输入: 清单配置。
  • 预期输出: 仅能由使用同一开发者证书签名的应用启动的Activity。
<br />
xml
<permission
    android:name="com.example.snippets.permission.INTERNAL_COMMUNICATION"
    android:protectionLevel="signature" />
   
<br /> <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)

4. 安全的onNewIntent生命周期验证(热启动保护)

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.
<br />
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 />
确保重用动态Intent的Activity(例如后台启动路径)在
onNewIntent
中应用相同严格的安全过滤。
  • 预期输入:
    • newIntent
      Intent
      ):新传递的Intent。
  • 预期输出:
    • 仅当新Intent通过安全验证时才执行处理逻辑。
<br />
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

5. 安全创建PendingIntent

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.
<br />
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 />
除非明确需要可变性,否则强制使用不可变选项。
  • 预期输入(不可变): Intent目标。
  • 预期输出(不可变): 接收者无法修改的
    PendingIntent
  • 预期输入(可变): 设置了显式组件的Intent。
  • 预期输出(可变): 锁定到特定接收者组件的可变
    PendingIntent
    ,以防止劫持。
<br />
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

6. 安全的ContentProvider配置与查询

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.
<br />
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 /> <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 />
安全地暴露ContentProvider,并参数化查询以防止SQL注入。
  • 预期输入:
    • uri
      Uri
      ):查询URI。
    • projection
      String[]
      ):要检索的列。
    • selection
      String
      ):查询条件。
    • selectionArgs
      String[]
      ):与选择占位符(
      ?
      )映射的值。
  • 预期输出:
    • Cursor
      :过滤后的查询结果,严格绑定到投影映射。
<br />
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 /> <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

7. Service调用者签名验证

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

    inner class LocalBinder : Binder() {
        fun doSecureWork() {
            // Verify caller identity on every transaction method call
            enforceTrustedCaller()
            // Safe to proceed with sensitive operations
        }
    }
}
   
<br />
在绑定到Service前验证调用应用的签名。
  • 预期输入:
    • intent
      Intent
      ):绑定请求Intent。
  • 预期输出:
    • IBinder
      :如果调用者签名匹配可信合作伙伴,则返回本地binder实例;否则抛出
      SecurityException
<br />
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
    }

    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.
<br />
kotlin
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
}

安全处理组件绑定、数据库查询和Intent重定向失败,避免暴露内部结构。
<br />
kotlin
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.
当执行此技能对代码库应用安全加固更新时,智能体必须为开发者生成结构化的“最佳实践与安全合规更新”报告。报告必须写入会话工件文件夹(或在最终响应中打印),并包含:
  1. 安全合规领域:所应用改进的类别(例如,安全Intent重定向、安全PendingIntent配置、ContentProvider数据防护)。
  2. 影响与优先级:更新解决的潜在安全风险(例如,组件劫持预防、私有数据隔离)。
  3. 变更范围:所有修改的类、XML文件和依赖项列表。
  4. 实现摘要:解决方案的具体细节(例如,“更新嵌套Intent解析以使用带有严格组件白名单的
    IntentSanitizer
    API”)。
  5. 代码差异:显示确切修改的标准统一差异。

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
```diff
// Insert Unified Diff here
向开发者报告变更时使用以下Markdown模板:
### 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
```diff
// Insert Unified Diff here

Testing and verification

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] ```

  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.
  • 绝不能在未验证目标包和导出状态的情况下,启动来自不可信来源的嵌套
    Intent
  • 绝不能使用粘性广播(
    sendStickyBroadcast
    )。
  • 绝不能假设导出组件是安全的,因为它在后台线程运行或执行内部检查。
  • 绝不能向无签名级权限限制的组件暴露敏感功能(如SSO认证或支付处理器)。
  • 绝不能
    onNewIntent
    中处理传入Intent时,不应用与
    onCreate
    相同的安全控制。
  • 绝不能在基础
    Intent
    中未设置显式目标组件的情况下创建可变
    PendingIntent
  • 绝不能使用动态字符串拼接在
    ContentProvider
    查询中构建选择块。
  • 绝不能
    BroadcastReceiver.onReceive
    中使用
    Binder.getCallingUid
    识别广播发送者,因为它返回的是接收器自身的UID,而非发送者的UID。

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.
  • 必须为所有不需要外部通信的组件显式设置
    android:exported="false"
  • 必须在关联应用间通信时,使用
    android:protectionLevel="signature"
    的自定义权限保护所有导出组件。
  • 必须验证所有传入Intent的附加数据,并优雅处理缺失参数以防止崩溃。
  • 必须依赖系统的Protected Broadcast机制处理系统事件(例如,开机完成、包变更),因为系统会阻止不可信应用伪造这些操作。
  • 必须使用签名级权限保护自定义广播,或对动态接收器使用
    RECEIVER_NOT_EXPORTED
    以限制发送者身份。
  • 必须
    onNewIntent()
    中处理负载前调用
    setIntent(newIntent)
    ,以保持活动引用更新。
  • 必须在构建
    PendingIntent
    实例时默认使用
    PendingIntent.FLAG_IMMUTABLE
  • 必须使用
    readPermission
    writePermission
    保护导出的
    ContentProviders
  • 必须
    ContentProvider
    的查询/更新方法中强制使用参数化选择结构。
  • 必须在导出的Service中运行时验证绑定应用的包签名指纹。
  • 如果项目中导入了AndroidX Core 1.9.0+,必须使用
    androidx.core.content.IntentSanitizer
    在重定向前净化传入的动态Intent。