android-intent-security
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseThis 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 () that can be launched by other apps on the device.
android:exported="true" - 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 , granted only to apps signed with the same developer key.
signature - onNewIntent: An activity lifecycle callback invoked when an activity is launched with and is already running at the top of the history stack.
FLAG_ACTIVITY_SINGLE_TOP - 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以模式启动且已位于历史堆栈顶部时,调用的Activity生命周期回调方法。
FLAG_ACTIVITY_SINGLE_TOP - 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, and theonNewIntentlaunch mode.singleTop - The agent MUST be able to declare ,
<activity>,<service>, and<receiver>tags in<provider>and define theirAndroidManifest.xmlandandroid:exportedattributes.android:permission - 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: or higher is mandatory to leverage
androidx.core:core:1.9.0.IntentSanitizer - Standard API access: Standard Android APIs are required for runtime component verification.
PackageManager
- Android SDK:标准硬件支持的密钥库操作和组件验证需要最低API级别23(Android 6.0)。
- AndroidX Core库:必须使用或更高版本,以利用
androidx.core:core:1.9.0。IntentSanitizer - 标准API访问:运行时组件验证需要标准Android API。
PackageManager
Intent security logic and decisions
Intent安全逻辑与决策
1. Intent routing comparison
1. Intent路由对比
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) |
评估不同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 Name | Mutability | Recommended Use Case |
|---|---|---|
| Immutable | Default for almost all PendingIntents, such as alarms and notifications |
| Mutable | Inline notifications replies, slice actions (requires explicit target intent) |
评估PendingIntent可变性标志的安全影响:
| 标志名称 | 可变性 | 推荐使用场景 |
|---|---|---|
| 不可变 | 几乎所有PendingIntent的默认选项,如闹钟和通知 |
| 可变 | 通知内联回复、切片操作(需要显式目标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 to explicitly allowlist components, actions, data, and extras. MUST call or 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 for dynamic receivers to restrict the sender. }
IntentSanitizersanitizeByThrowing()sanitizeByFiltering()RECEIVER_NOT_EXPORTED如果(组件接收嵌套Intent作为附加数据){ 如果(AndroidX Core 1.9.0及以上版本可用){ 必须构建,显式允许组件、操作、数据和附加数据的白名单。必须在启动前调用或。 } 否则 { 必须验证嵌套Intent的目标包与当前应用包匹配。必须验证嵌套Intent的目标组件是公开导出的。 } 绝不能在未验证的情况下直接启动嵌套Intent。 } 否则如果(组件处理广播){ 必须依赖系统的Protected Broadcast机制处理系统事件(该机制保证发送者是系统框架)。必须使用签名级权限保护自定义接收器,或对动态接收器使用以限制发送者。 }
IntentSanitizersanitizeByThrowing()sanitizeByFiltering()RECEIVER_NOT_EXPORTED4. PendingIntent security logic
4. PendingIntent安全逻辑
IF (a PendingIntent is created for delivery to another application) { MUST use by default. IF (the PendingIntent must be mutable) { MUST set the explicit target component or package name on the base . NEVER create an implicit, mutable . } }
PendingIntent.FLAG_IMMUTABLEIntentPendingIntent如果(创建PendingIntent以传递给其他应用){ 默认必须使用。如果(PendingIntent必须是可变的){ 必须在基础上设置显式目标组件或包名。绝不能创建隐式的可变。 } }
PendingIntent.FLAG_IMMUTABLEIntentPendingIntent5. ContentProvider security logic
5. ContentProvider安全逻辑
IF (the ContentProvider is only for internal app use) { MUST set . } ELSE { MUST protect it with and . MUST set unless temporary URL access is strictly required. }
android:exported="false"android:readPermissionandroid:writePermissionandroid:grantUriPermissions="false"如果(ContentProvider仅用于应用内部){ 必须设置。 } 否则 { 必须使用和保护它。除非严格需要临时URL访问,否则必须设置。 }
android:exported="false"android:readPermissionandroid:writePermissionandroid: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 and resolve it to package names using . MUST verify that the calling package signature fingerprint matches your trusted certificate hash. }
Binder.getCallingUid()PackageManager.getPackagesForUid()如果(导出的Service与可信的关联/合作伙伴应用通信){ 必须使用获取调用者UID,并使用将其解析为包名。必须验证调用包的签名指纹与可信证书哈希匹配。 }
Binder.getCallingUid()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 containing a nested
Intentextra namedIntent.EXTRA_NESTED_INTENT
- An incoming
- Expected Outputs:
- Launches the target component if safe; throws if validation fails.
SecurityException
- Launches the target component if safe; throws
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)
}
}
}
}
当现代净化库不可用时,在启动嵌套Intent前验证其目标。
- 预期输入:
- 包含名为的嵌套Intent附加数据的传入
EXTRA_NESTED_INTENT。Intent
- 包含名为
- 预期输出:
- 如果安全则启动目标组件;如果验证失败则抛出。
SecurityException
- 如果安全则启动目标组件;如果验证失败则抛出
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)
}
}
}
}
2. Safe intent redirection using IntentSanitizer
2. 使用IntentSanitizer实现安全Intent重定向
Filter or reject dynamic intents using AndroidX (AndroidX Core 1.9.0+).
IntentSanitizer- Expected Inputs:
- An untrusted incoming .
Intent
- An untrusted incoming
- Expected Outputs:
- : A sanitized copy containing only allowlisted components, categories, and actions. Throws
Intenton violations if usingSecurityException.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)
}
}
使用AndroidX (AndroidX Core 1.9.0+)过滤或拒绝动态Intent。
IntentSanitizer- 预期输入:
- 不可信的传入。
Intent
- 不可信的传入
- 预期输出:
- :仅包含白名单组件、类别和操作的净化副本。如果使用
Intent,违反规则时会抛出sanitizeByThrowing()。SecurityException
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)
}
}
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.
xml
<permission
android:name="com.example.snippets.permission.INTERNAL_COMMUNICATION"
android:protectionLevel="signature" />
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>
在清单中声明自定义签名级权限,以保护关联应用间的通信。
- 预期输入: 清单配置。
- 预期输出: 仅能由使用同一开发者证书签名的应用启动的Activity。
xml
<permission
android:name="com.example.snippets.permission.INTERNAL_COMMUNICATION"
android:protectionLevel="signature" />
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>
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): The newly delivered intent.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")
}
确保重用动态Intent的Activity(例如后台启动路径)在中应用相同严格的安全过滤。
onNewIntent- 预期输入:
- (
newIntent):新传递的Intent。Intent
- 预期输出:
- 仅当新Intent通过安全验证时才执行处理逻辑。
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")
}
5. Secure PendingIntent creation
5. 安全创建PendingIntent
Enforce immutability unless mutability is explicitly required.
- Expected Inputs (Immutable): An intent target.
- Expected Outputs (Immutable): A that cannot be altered by the receiver.
PendingIntent - Expected Inputs (Mutable): An intent with an explicit component set.
- Expected Outputs (Mutable): A mutable locked to a specific receiver component to prevent hijacking.
PendingIntent
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
)
}
除非明确需要可变性,否则强制使用不可变选项。
- 预期输入(不可变): Intent目标。
- 预期输出(不可变): 接收者无法修改的。
PendingIntent - 预期输入(可变): 设置了显式组件的Intent。
- 预期输出(可变): 锁定到特定接收者组件的可变,以防止劫持。
PendingIntent
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
)
}
6. Secure ContentProvider configuration and queries
6. 安全的ContentProvider配置与查询
Expose a ContentProvider securely and parameterize queries to prevent SQL injection.
- Expected Inputs:
- (
uri): The query URI.Uri - (
projection): Columns to retrieve.String[] - (
selection): Query criteria.String - (
selectionArgs): Values mapping to selection placeholders (String[]).?
- Expected Outputs:
- : Filtered query results, strictly bound to projection maps.
Cursor
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" />
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)
}
安全地暴露ContentProvider,并参数化查询以防止SQL注入。
- 预期输入:
- (
uri):查询URI。Uri - (
projection):要检索的列。String[] - (
selection):查询条件。String - (
selectionArgs):与选择占位符(String[])映射的值。?
- 预期输出:
- :过滤后的查询结果,严格绑定到投影映射。
Cursor
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" />
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)
}
7. Service caller signature verification
7. Service调用者签名验证
Verify the calling application's signature before binding to a service.
- Expected Inputs:
- (
intent): The binding request intent.Intent
- Expected Outputs:
- : Local binder instance if caller signature matches trusted partner; throws
IBinderotherwise.SecurityException
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
}
}
}
在绑定到Service前验证调用应用的签名。
- 预期输入:
- (
intent):绑定请求Intent。Intent
- 预期输出:
- :如果调用者签名匹配可信合作伙伴,则返回本地binder实例;否则抛出
IBinder。SecurityException
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
}
}
}
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)
}
}
// 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)
}
}
// 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:
- Security alignment area: The category of improvement applied (for example, Safe Intent Redirection, Secure PendingIntent Configuration, ContentProvider Data Guarding).
- Impact and priority: The potential safety risk addressed by the update (for example, Component Hijacking Prevention, Private Data Isolation).
- Scope of changes: A list of all modified classes, XML files, and dependencies.
- Implementation summary: Concrete details of the solution (for example, "Updated nested intent parsing to use the API with a strict component allowlist").
IntentSanitizer - Code diff: Standard unified diffs showing the exact modifications.
当执行此技能对代码库应用安全加固更新时,智能体必须为开发者生成结构化的“最佳实践与安全合规更新”报告。报告必须写入会话工件文件夹(或在最终响应中打印),并包含:
- 安全合规领域:所应用改进的类别(例如,安全Intent重定向、安全PendingIntent配置、ContentProvider数据防护)。
- 影响与优先级:更新解决的潜在安全风险(例如,组件劫持预防、私有数据隔离)。
- 变更范围:所有修改的类、XML文件和依赖项列表。
- 实现摘要:解决方案的具体细节(例如,“更新嵌套Intent解析以使用带有严格组件白名单的API”)。
IntentSanitizer - 代码差异:显示确切修改的标准统一差异。
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 hereTesting and verification
Testing and verification
- [Step 1 to verify the component behaves correctly, for example, run component unit test]
- [Step 2 to verify regression safety] ```
- [Step 1 to verify the component behaves correctly, for example, run component unit test]
- [Step 2 to verify regression safety] ```
Antipatterns
反模式
- NEVER launch a nested received from an untrusted source without verifying its target package and exported status.
Intent - 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 without applying the same security controls as
onNewIntent.onCreate - NEVER create a mutable without setting an explicit target component in the base
PendingIntent.Intent - NEVER use dynamic string concatenation to construct selection blocks inside a query.
ContentProvider - NEVER use inside a
Binder.getCallingUidto identify the sender of a broadcast, as it returns the receiver's own UID, not the sender's.BroadcastReceiver.onReceive
- 绝不能在未验证目标包和导出状态的情况下,启动来自不可信来源的嵌套。
Intent - 绝不能使用粘性广播()。
sendStickyBroadcast - 绝不能假设导出组件是安全的,因为它在后台线程运行或执行内部检查。
- 绝不能向无签名级权限限制的组件暴露敏感功能(如SSO认证或支付处理器)。
- 绝不能在中处理传入Intent时,不应用与
onNewIntent相同的安全控制。onCreate - 绝不能在基础中未设置显式目标组件的情况下创建可变
Intent。PendingIntent - 绝不能使用动态字符串拼接在查询中构建选择块。
ContentProvider - 绝不能在中使用
BroadcastReceiver.onReceive识别广播发送者,因为它返回的是接收器自身的UID,而非发送者的UID。Binder.getCallingUid
Best Practices
最佳实践
- MUST explicitly set for all components that don't need external communication.
android:exported="false" - MUST protect all exported components with custom permissions utilizing when communicating between family apps.
android:protectionLevel="signature" - 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 for dynamic receivers to restrict the sender identity.
RECEIVER_NOT_EXPORTED - MUST call inside
setIntent(newIntent)before processing payloads to keep active references updated.onNewIntent() - MUST use by default when constructing
PendingIntent.FLAG_IMMUTABLEinstances.PendingIntent - MUST protect exported with
ContentProvidersandreadPermission.writePermission - MUST enforce parameterized selection structures in query/update methods.
ContentProvider - MUST verify the package signature fingerprint of binding applications at runtime inside exported services.
- MUST use to sanitize incoming dynamic intents before redirection, if AndroidX Core 1.9.0+ is imported in the project.
androidx.core.content.IntentSanitizer
- 必须为所有不需要外部通信的组件显式设置。
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+,必须使用在重定向前净化传入的动态Intent。
androidx.core.content.IntentSanitizer