android-permissions-security
Skill2 starsChanged 17 days agoMITView on GitHub
Audits, detects gaps, and remediates Android permissions and IPC component
1 other project uses a version of this.
Adopted by
Most recently taken up by
- rosuH/EasyWatermarkword for word, first seen Oct 8, 2026
Other versions1 project uses a version of this.
Word for word · 1
- rosuH/EasyWatermark★ 1.9k100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository22 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- a11y-auditSkill
- adaptiveSkill
- add-a11y-ruleSkill
- add-exporterSkill
- android-intent-securitySkill
- crawl-appSkill
- design-systemSkill
- edge-to-edgeSkill
- extend-crawlerSkill
- ios-a11y-rulesSkill
- ios-simulatorSkill
- maintain-crawlerSkill
- play-policy-insightsSkill
- security-auditSkill
- security-rulesSkill
- stack-iosSkill
- stack-rulesSkill
- store-requirementsSkill
- tune-crawlerSkill
- wcag-mobile-rulesSkill
- web-uiSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
20 rules. About 0 are common, 5 are distinctive, and 15 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
15
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 50
Add androidx.core:core-ktx and androidx.activity:activity-ktx (or androidx.fragment:fragment-ktx) dependencies to the app-level …
- Distinctive: line 58
When auditing an Android project for permission and IPC security vulnerabilities, systematically inspect each area:
- Very specific: line 79
When declaring custom permissions or partner certificates in the manifest, you must follow the instructions in the <permission> element …
- Very specific: line 84
normalordangerous: : Do not use for private inter-app IPC. - Very specific: line 91
Use signature permissions when restricting component access exclusively to apps signed with the same developer certificate by declaring the …
- Very specific: line 106
Use signature|knownSigner permissions when granting access to specific external partner apps by declaring the <permission> in the manifest …
- Very specific: line 137
Private components: : Always set android:exported="false".
- Very specific: line 140
Custom Permission Declarations: : When splitting read and write permissions on a provider, you MUST declare the <permission> tags with …
- Very specific: line 142
Granular
UriPermissionManagement: : Instead of broad manifest grants, use runtime android.content.UriPermission tokens for … - Very specific: line 218
Never trust caller package names provided in Intents or string arguments (getCallingPackage(), intent.getStringExtra("calling_package"), …
- Very specific: line 241
Verify caller certificate using PackageManager.hasSigningCertificate() (API 28+) or PackageManager.getPackagesForUid().
- Very specific: line 317
NEVER: use checkCallingOrSelfPermission() or enforceCallingOrSelfPermission().
- Distinctive: line 360
When implementing runtime permission requests or handling permission denial states, you must follow the instructions in the Request runtime …
- Distinctive: line 427
When implementing photo or media selection, you must follow the instructions in the Photo Picker API guide to avoid requesting broad …
- Very specific: line 430
Photo Picker: : Prefer ActivityResultContracts.PickVisualMedia() over requesting wide storage permissions (READ_MEDIA_IMAGES, …
- Distinctive: line 524
Use explicit intents, required receiver permissions, or Android 14+ broadcast options:
- Very specific: line 581
MUST: check shouldShowRequestPermissionRationale() before runtime prompts and during denial handling.
- Very specific: line 592
NEVER: use checkCallingOrSelfPermission() or enforceCallingOrSelfPermission() to guard external caller methods.
- Very specific: line 593
NEVER: use deprecated signatureOrSystem.
- Distinctive: line 596
NEVER: create infinite permission request loops on denial.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Guidelines for auditing permission vulnerabilities, enforcing least privilege, securing IPC boundaries, and handling runtime permissions across the Android ecosystem.
Glossary
- Runtime Permission: Dangerous permission (e.g., Camera, Location) that must be granted by the user at runtime on Android 6.0+ (API 23+).
- Custom Permission: A permission defined by an app (
<permission>) to restrict access to its exported components. - Protection Level: Attribute defining how the OS grants the permission (
normal,dangerous,signature,knownSigner). - knownSigner: Protection flag (API 31+) granting signature-level access to partner apps matching trusted certificate digests.
- SigningInfo: Object (API 28+) providing signing history and multiple signer certificates for an application.
- Binder Calling Identity: Reliable UID representing the calling process obtained using
Binder.getCallingUid(). - URI Permission: Temporary, granular access granted to a content URI using
grantUriPermissionsand intent flags.
Prerequisites
- Add
androidx.core:core-ktxandandroidx.activity:activity-ktx(orandroidx.fragment:fragment-ktx) dependencies to the app-levelbuild.gradlefile forContextCompatandActivityResultContracts. - For
knownSignerpartner permissions (android:knownCerts), ensure the projectcompileSdkandtargetSdkare at least 31 (Android 12). - For broadcast identity sharing (
BroadcastOptions.setShareIdentityEnabled), ensurecompileSdkandtargetSdkare at least 34 (Android 14).
1. Security audit and permission gap detection playbook
When auditing an Android project for permission and IPC security vulnerabilities, systematically inspect each area:
| Vulnerability Area | Search In / Pattern | Risk | Remediation Action | |
| Weak Custom Permission Protection | AndroidManifest.xml with android:protectionLevel="normal", "dangerous", or "signatureOrSystem" | Unauthorized apps request or claim permission without restriction | Upgrade to signature or `signature | knownSignerwithandroid:knownCerts`. |
| Overly Broad URI Permission Grants | <grant-uri-permission android:pathPrefix="/" /> or pathPrefix="" in <provider> | Leaks all provider data to third parties | Replace with explicit scoped subpath (e.g. android:pathPrefix="/shared/"). Retain android:grantUriPermissions="true". Remove any wildcard pathPrefix="/". | |
| Missing ContentProvider Split Permissions | <provider> with only generic android:permission or missing android:readPermission and android:writePermission | Read-only callers execute write operations or write-only callers access read operations | Configure granular android:readPermission="...READ_DATA" and android:writePermission="...WRITE_DATA", AND declare the custom <permission> tags with android:protectionLevel="signature" (or `signature | knownSigner) at the <manifest>` root. |
| Spoofable Caller Identity Checks | intent.getStringExtra("calling_package"), intent.getStringExtra("sender"), callingPackage == "..." in Services | Malicious callers forge intent extras or strings | Remove string checks; authenticate caller UID cryptographically using Binder.getCallingUid() and pm.hasSigningCertificate(). | |
| Bound Service Permission Bypass | checkCallingOrSelfPermission() or enforceCallingOrSelfPermission() in Binder AIDL stubs | Falls back to host app UID outside IPC or when identity is cleared, causing Confused Deputy bypass | Replace with enforceCallingPermission("...WRITE_DATA", ...) or checkCallingPermission("...WRITE_DATA"). | |
| Missing Method-Level Checks in Bound Services | Bound Service AIDL methods mutating state without enforcing write permissions | Callers with read-only binding access invoke mutating write operations | Add enforceCallingPermission("...WRITE_DATA", "Caller lacks WRITE_DATA permission") inside mutating AIDL methods. | |
| Flawed Runtime Permission Flow | Missing shouldShowRequestPermissionRationale(), missing permanent denial handling, or re-requesting in loops | Poor user experience, infinite loops, or inability to recover permissions | Implement 3-state flow: check grant -> check rationale dialog -> launch contract. On permanent denial, redirect to Settings.ACTION_APPLICATION_DETAILS_SETTINGS. | |
| Simultaneous Location Requests | arrayOf(ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION) in one request | Rejected by Android 11+ (API 30+) | Request foreground location (ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) first. Request ACCESS_BACKGROUND_LOCATION only after foreground is granted in a separate user step. | |
| Broad Storage Permissions for Media | Requesting READ_EXTERNAL_STORAGE or READ_MEDIA_IMAGES for user image selection | Over-privilege, user privacy violation | Migrate to zero-permission Photo Picker (ActivityResultContracts.PickVisualMedia). | |
| Cached Permission State | Storing permission status in var cachedLocationPermissionGranted = ... | Fails when permission is revoked or expires | Query ContextCompat.checkSelfPermission() dynamically at invocation time and wrap protected calls in try-catch blocks catching SecurityException. | |
| Insecure Broadcasts | sendBroadcast(intent) without package target or receiver permission; receiver without sender authentication | Data interception or command spoofing | Set explicit package (intent.setPackage(...)), pass receiver permission, and enable BroadcastOptions.setShareIdentityEnabled(true) (API 34+) to verify sentFromPackage. |
2. Custom permissions and protection levels
When declaring custom permissions or partner certificates in the manifest, you
must follow the instructions in the <permission> element guide.
signature: Restricts access to apps signed with the same developer key.signature|knownSigner(API 31+): Restricts access to partner apps whose signing certificate digests match declared hashes inandroid:knownCerts.normalordangerous: Do not use for private inter-app IPC.signatureOrSystem: Deprecated (API 23). Upgrade deprecatedsignatureOrSystempermissions tosignature. Avoid in non-system apps.
Manifest snippets
Signature-level custom permission
Use signature permissions when restricting component access exclusively to
apps signed with the same developer certificate by declaring the <permission>
in the manifest and applying android:permission to the target component.
<permission
android:name="com.example.snippets.permission.ACCESS_SECURE_API"
android:protectionLevel="signature" /><br />KnownSigner partner permission (API 31+)
Use signature|knownSigner permissions when granting access to specific
external partner apps by declaring the <permission> in the manifest with
android:knownCerts referencing a resource array of trusted SHA-256
certificate digests.
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- SHA-256 fingerprints of trusted partner certificates (lowercase, no colons) -->
<string-array name="trusted_partner_certs" tools:ignore="Typos">
<item>103938ee4537e59e8ee792f654504fb8346fc6b346d0bbc4415fc339fcfc8ec1</item>
</string-array>
</resources><br /><permission
android:name="com.example.snippets.permission.PARTNER_API"
android:protectionLevel="signature|knownSigner"
android:knownCerts="@array/trusted_partner_certs" /><br />3. Exported component protection and URI permissions
- Private components : Always set
android:exported="false". - Exported components : Require permissions (
android:permission,android:readPermission,android:writePermission). - Granular URI Grants : Retain
android:grantUriPermissions="true"for secure sharing, but scope grants to specific sub-paths. Never use wildcardpathPrefix="/". - Custom Permission Declarations : When splitting read and write permissions on a provider, you MUST declare the
<permission>tags withandroid:protectionLevel="signature"(orsignature|knownSigner) at the<manifest>root level. - Remediating Existing Wildcard Grants : You MUST find and remove any existing
<grant-uri-permission android:pathPrefix="/" />orpathPrefix=""and replace it with an explicit scoped subpath (e.g.android:pathPrefix="/shared/"). - Granular
UriPermissionManagement : Instead of broad manifest grants, use runtimeandroid.content.UriPermissiontokens for least-privilege per-URI sharing:- Grant temporary access :
context.grantUriPermission(targetPackage, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)or attachIntent.FLAG_GRANT_READ_URI_PERMISSIONto outgoing intents. - Persist access : The receiving app calls
contentResolver.takePersistableUriPermission(uri, modeFlags)for durable access across reboots. - Audit active grants : Inspect
contentResolver.persistedUriPermissions(List<UriPermission>) to verify granted URIs (uriPermission.uri,uriPermission.isReadPermission(),uriPermission.isWritePermission()). - Revoke access : Call
context.revokeUriPermission(uri, modeFlags)orcontentResolver.releasePersistableUriPermission(uri, modeFlags)as soon as operations conclude.
- Grant temporary access :
Manifest snippet: Complete secure ContentProvider declaration
Declare the split read and write permissions at the <manifest> root:
<permission
android:name="com.example.snippets.permission.READ_DATA"
android:protectionLevel="signature" />
<permission
android:name="com.example.snippets.permission.WRITE_DATA"
android:protectionLevel="signature" /><br />Then configure the provider with those permissions and a scoped URI grant:
<provider
android:name=".permissions.SecureDataProvider"
android:authorities="com.example.snippets.partnerprovider"
android:exported="true"
android:readPermission="com.example.snippets.permission.READ_DATA"
android:writePermission="com.example.snippets.permission.WRITE_DATA"
android:grantUriPermissions="true">
<grant-uri-permission android:pathPrefix="/shared/" />
</provider><br />Runtime granular UriPermission management snippet
object UriPermissionManager {
/** Grants temporary read access to a specific URI for a partner package. */
fun grantScopedUriAccess(context: Context, targetPackage: String, uri: Uri) {
context.grantUriPermission(
targetPackage,
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
/** Audits active persisted URI permissions held by the application. */
fun getActivePersistedPermissions(context: Context): List<UriPermission> {
return context.contentResolver.persistedUriPermissions
}
/** Revokes temporary URI access when data transfer completes. */
fun revokeScopedUriAccess(context: Context, uri: Uri) {
context.revokeUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
}
}<br />4. Cryptographic caller identity verification
Never trust caller package names provided in Intents or string arguments
(getCallingPackage(), intent.getStringExtra("calling_package"),
intent.getStringExtra("sender"), intent.getStringExtra("package")). Intent
extras and string package names are forged by malicious applications.
Critical vulnerability: Package name spoofing using intent extras
// VULNERABLE PATTERN: DO NOT DO THIS
val callingPackage = intent?.getStringExtra("calling_package")
if (callingPackage != null && SignatureUtils.verifyPartnerPackage(this, callingPackage)) {
// A malicious app passes "com.example.partner" in the extra.
// The signature check verifies the installed partner on disk, but the CALLER was malicious!
}<br />Secure pattern: Authenticate calling UID cryptographically
- Completely remove all
intent.getStringExtra("calling_package"),intent.getStringExtra("sender"), andcallingPackage == "..."checks. - Obtain caller identity using
Binder.getCallingUid(). - Verify caller certificate using
PackageManager.hasSigningCertificate()(API 28+) orPackageManager.getPackagesForUid().
Complete CallerVerifier implementation
Apply this to the service that currently performs the spoofable check.
- Placement : declare
CallerVerifierin the same file as the service you are fixing (for example insideSecureDataService.kt). Do not move the verification into a new file and leave the original service untouched. - Nullability :
packageInfo.signaturesisArray<Signature>?. Copy the?: return falseguard exactly as written below. IteratingpackageInfo.signaturesdirectly fails to compile with "Non-nullable value required to call an 'iterator()' method in a for-loop."
object CallerVerifier {
private const val TRUSTED_PARTNER_SHA256 =
"A1B2C3D4E5F60708090A0B0C0D0E0F1011121314151617181920212223242526"
fun isCallerAuthorized(context: Context): Boolean {
val callingUid = Binder.getCallingUid()
if (callingUid == android.os.Process.myUid()) return true
val pm = context.packageManager
// Modern API 28+ check by UID:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val certBytes = hexStringToByteArray(TRUSTED_PARTNER_SHA256)
if (pm.hasSigningCertificate(callingUid, certBytes, PackageManager.CERT_INPUT_SHA256)) {
return true
}
}
// Fallback for legacy APIs:
val callingPackages = pm.getPackagesForUid(callingUid) ?: return false
for (pkg in callingPackages) {
if (verifyPackageSignature(pm, pkg)) {
return true
}
}
return false
}
@Suppress("DEPRECATION")
private fun verifyPackageSignature(pm: PackageManager, packageName: String): Boolean {
return try {
val packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val signatures = packageInfo.signatures ?: return false
for (sig in signatures) {
val digest = java.security.MessageDigest.getInstance("SHA-256").digest(sig.toByteArray())
val hex = digest.joinToString("") { "%02X".format(it) }
if (hex.equals(TRUSTED_PARTNER_SHA256, ignoreCase = true)) return true
}
false
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
private fun hexStringToByteArray(s: String): ByteArray {
val len = s.length
val data = ByteArray(len / 2)
for (i in 0 until len step 2) {
data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
}
return data
}
}<br />5. Method-level Binder enforcement in bound services (SecureBoundService.kt)
In bound services exposing AIDL Binder stubs, protect state-modifying methods
using enforceCallingPermission().
Critical security rule: Never use CallingOrSelfPermission in services
- NEVER use
checkCallingOrSelfPermission()orenforceCallingOrSelfPermission(). - Why: During an active Binder transaction, this method checks the caller's permission. However, if the method is invoked locally (outside of an active IPC) or if the IPC calling identity is dropped, it falls back to checking the host app's permissions. Because the host app holds the permission, this fallback grants access and bypasses the security boundary, causing a Confused Deputy vulnerability.
- MUST USE:
enforceCallingPermission("com.example.snippets.permission.WRITE_DATA", "Caller lacks WRITE_DATA permission")orcheckCallingPermission("com.example.snippets.permission.WRITE_DATA").
Complete SecureBoundService implementation
class SecureBoundService : Service() {
private val binder = object : IMyService.Stub() {
override fun getData(): String {
// Read-only operation guarded by manifest-level permission
return "Confidential Data"
}
override fun modifyData(newData: String) {
// MUST use enforceCallingPermission or checkCallingPermission.
// NEVER use checkCallingOrSelfPermission or enforceCallingOrSelfPermission.
this@SecureBoundService.enforceCallingPermission(
"com.example.snippets.permission.WRITE_DATA",
"Caller lacks WRITE_DATA permission"
)
updateInternalState(newData)
Log.d("SecureBoundService", "Data modified to: $newData with proper WRITE_DATA permission check")
}
}
override fun onBind(intent: Intent?): IBinder = binder
private fun updateInternalState(data: String) {
// Internal state update logic
}
}<br />6. Safe runtime permission workflow (RuntimePermissionsActivity.kt)
When implementing runtime permission requests or handling permission denial states, you must follow the instructions in the Request runtime permissions guide and Permissions overview. Implement a 3-state flow without caching states or creating infinite retry loops:
- Check Granted State :
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED. - Check Rationale :
shouldShowRequestPermissionRationale(permission)to present educational context before requesting. - Launch Contract :
ActivityResultContracts.RequestPermission(). - Handle Permanent Denial : In the callback, if permission was denied and
!shouldShowRequestPermissionRationale(permission), guide the user toSettings.ACTION_APPLICATION_DETAILS_SETTINGS.
Complete RuntimePermissionsActivity.kt implementation
class RuntimePermissionsActivity : ComponentActivity() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
startCameraPreview()
} else {
if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
// User selected 'Don't ask again' or permanently denied.
// Direct user to Application Details Settings.
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
} else {
showSnackbar("Camera permission is required to preview camera feed.")
}
}
}
fun requestCameraPermissionSafely() {
val permission = Manifest.permission.CAMERA
when {
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> {
startCameraPreview()
}
shouldShowRequestPermissionRationale(permission) -> {
showSnackbar("Camera permission is needed to preview camera feed.")
cameraLauncher.launch(permission)
}
else -> {
cameraLauncher.launch(permission)
}
}
}
private fun startCameraPreview() {
Log.d("RuntimePermissionsActivity", "Camera preview started")
}
private fun showSnackbar(msg: String) {
Log.i("RuntimePermissionsActivity", msg)
}
}<br />7. Zero-permission alternatives and sequential flows
When implementing photo or media selection, you must follow the instructions in the Photo Picker API guide to avoid requesting broad storage permissions.
- Photo Picker : Prefer
ActivityResultContracts.PickVisualMedia()over requesting wide storage permissions (READ_MEDIA_IMAGES,READ_EXTERNAL_STORAGE). - Sequential Location Flow : Request
ACCESS_FINE_LOCATIONandACCESS_COARSE_LOCATIONfirst. Never requestACCESS_BACKGROUND_LOCATIONsimultaneously. Only request background location in a separate step after foreground is granted.
Photo picker implementation
private val photoPickerLauncher =
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
handleImageUri(uri)
}
}
fun selectPhoto() {
photoPickerLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
}<br />Sequential location request implementation
private val foregroundLocationLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val fineGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
val coarseGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: false
if (fineGranted || coarseGranted) {
startForegroundLocationUpdates()
}
}
fun requestForegroundLocation() {
foregroundLocationLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
// Background location requested only AFTER foreground is granted and user explicitly opts in:
private val backgroundLocationLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
startBackgroundTracking()
}
}<br />8. Stateless checks and error boundaries
- Never cache permission states in in-memory variables (e.g.
cachedLocationPermissionGranted). CheckContextCompat.checkSelfPermission()dynamically before each operation. - Wrap API calls in try-catch : Catch
SecurityExceptionaround permission-protected framework calls (e.g., location queries) to handle one-time permission revocation gracefully.
fun performLocationAccess() {
val fine = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
val coarse = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
if (fine != PackageManager.PERMISSION_GRANTED && coarse != PackageManager.PERMISSION_GRANTED) {
requestForegroundLocation()
return
}
val provider = if (fine == PackageManager.PERMISSION_GRANTED) {
LocationManager.GPS_PROVIDER
} else {
LocationManager.NETWORK_PROVIDER
}
try {
val location = locationManager.getLastKnownLocation(provider)
processLocation(location)
} catch (e: SecurityException) {
Log.e("LocationAccess", "Permission revoked at runtime", e)
}
}<br />9. Secure broadcasts (API 34+ share identity)
Use explicit intents, required receiver permissions, or Android 14+ broadcast options:
// Sender: Enforce permission and share identity
val intent = Intent("com.example.snippets.permission.ACTION_SECRET_UPDATE").apply {
setPackage("com.example.partner") // Explicit target
}
val isUdc = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
val options = if (isUdc) {
BroadcastOptions.makeBasic().apply {
setShareIdentityEnabled(true)
}.toBundle()
} else null
sendBroadcast(intent, "com.example.snippets.permission.RECEIVE_SECRET_UPDATE", options)<br />// Receiver: Validate sender on Android 14+
class ProtectedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "com.example.snippets.permission.ACTION_SECRET_UPDATE") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val sender = sentFromPackage
if (sender != "com.example.trusted_sender") {
return // Reject unauthorized sender
}
}
processUpdate(intent)
}
}
private fun processUpdate(intent: Intent) {}
}<br />Legacy compatibility (Pre-API 34) : For apps targeting API 33 or lower,
attach an immutable PendingIntent extra (FLAG_IMMUTABLE) to the broadcast
and authenticate the sender using pendingIntent.creatorPackage.
Quality checklist
DO
- MUST declare
<permission android:name="..." android:protectionLevel="signature" />(orsignature|knownSigner) at the<manifest>root for custom permissions and ContentProvider split permissions. - MUST explicitly set
android:exported="false"for all private components to remove the need for custom permission management. - MUST authenticate callers cryptographically using
Binder.getCallingUid()and certificate hashes (PackageManager.hasSigningCertificateorgetPackagesForUid) rather than checking spoofable intent extras. - MUST check
shouldShowRequestPermissionRationale()before runtime prompts and during denial handling. - MUST direct user to
Settings.ACTION_APPLICATION_DETAILS_SETTINGSwhen permission is permanently denied. - MUST enforce method-level checks with
enforceCallingPermission()orcheckCallingPermission()on modifying AIDL methods. - MUST scope ContentProvider URI permissions to explicit sub-paths (e.g.
android:pathPrefix="/shared/") while retainingandroid:grantUriPermissions="true". - MUST catch
SecurityExceptionaround protected API calls. - MUST request foreground and background location permissions sequentially, never simultaneously.
NEVER
- NEVER extract or verify package names from Intent extras (
intent.getStringExtra("calling_package"),intent.getStringExtra("sender"),intent.getStringExtra("package")). Always authenticate usingBinder.getCallingUid()and certificates. - NEVER remediate caller spoofing by merely adding manifest permissions while keeping spoofable intent extra checks.
- NEVER use
checkCallingOrSelfPermission()orenforceCallingOrSelfPermission()to guard external caller methods. Always useenforceCallingPermission()orcheckCallingPermission(). - NEVER use deprecated
signatureOrSystem. - NEVER declare or retain
<grant-uri-permission android:pathPrefix="/" />orpathPrefix="". Always replace with a scoped sub-path. - NEVER create infinite permission request loops on denial.
- NEVER request foreground and background location permissions in the same request.
- NEVER cache permission grant states in in-memory fields.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

