@@ -17,9 +17,41 @@ enum class SecurityIssueType(val displayName: String) {
1717 MANIFEST_DEBUGGABLE (" Manifest Debuggable Flag" ),
1818 ALLOW_BACKUP_ENABLED (" Backup Enabled" ),
1919 CLEARTEXT_TRAFFIC (" Cleartext Traffic Allowed" ),
20- EXPORTED_COMPONENT (" Exported Component Without Permission" )
20+ EXPORTED_COMPONENT (" Exported Component Without Permission" ),
21+ // New: Permission Analysis
22+ DANGEROUS_PERMISSION (" Dangerous Permission Usage" ),
23+ PERMISSION_NOT_DEFINED (" Permission Not Defined" ),
24+ // New: Component Security
25+ EXPORTED_SERVICE (" Exported Service Without Permission" ),
26+ EXPORTED_RECEIVER (" Exported Broadcast Receiver Without Permission" ),
27+ EXPORTED_PROVIDER (" Exported Content Provider Without Permission" ),
28+ // New: Intent Filter Security
29+ INTENT_FILTER_DATA_EXPOSURE (" Intent Filter May Expose Data" )
2130}
2231
32+ /* *
33+ * Dangerous permissions that should be reviewed.
34+ */
35+ private val DANGEROUS_PERMISSIONS = listOf (
36+ " READ_CALENDAR" , " WRITE_CALENDAR" ,
37+ " CAMERA" , " READ_CONTACTS" , " WRITE_CONTACTS" ,
38+ " GET_ACCOUNTS" , " ACCESS_FINE_LOCATION" , " ACCESS_COARSE_LOCATION" ,
39+ " RECORD_AUDIO" , " READ_PHONE_STATE" , " READ_PHONE_NUMBERS" ,
40+ " CALL_PHONE" , " READ_CALL_LOG" , " WRITE_CALL_LOG" ,
41+ " SEND_SMS" , " RECEIVE_SMS" , " READ_SMS" ,
42+ " WRITE_EXTERNAL_STORAGE" , " READ_EXTERNAL_STORAGE" ,
43+ " BODY_SENSORS" , " ACCESS_BACKGROUND_LOCATION"
44+ )
45+
46+ /* *
47+ * Permissions that should always require explicit declaration.
48+ */
49+ private val HIGH_RISK_PERMISSIONS = listOf (
50+ " READ_CALL_LOG" , " WRITE_CALL_LOG" , " PROCESS_OUTGOING_CALLS" ,
51+ " READ_SMS" , " RECEIVE_SMS" , " SEND_SMS" ,
52+ " WRITE_SETTINGS" , " SYSTEM_ALERT_WINDOW"
53+ )
54+
2355/* *
2456 * Task that checks for security best practices.
2557 */
@@ -174,11 +206,141 @@ open class SecurityCheckTask : DefaultTask() {
174206 }
175207 }
176208
209+ // Enhanced Manifest Analysis
210+ checkPermissions(content)
211+ checkComponentSecurity(content)
212+ checkIntentFilterSecurity(content)
213+
177214 } catch (e: Exception ) {
178215 logger.warn(" Could not analyze manifest: ${e.message} " )
179216 }
180217 }
181218
219+ private fun checkPermissions (content : String ) {
220+ // Check for dangerous permissions
221+ DANGEROUS_PERMISSIONS .forEach { permission ->
222+ if (content.contains(" android.permission.$permission " )) {
223+ val severity = if (permission in HIGH_RISK_PERMISSIONS ) Severity .HIGH else Severity .MEDIUM
224+ findings.add(
225+ SecurityFinding (
226+ type = SecurityIssueType .DANGEROUS_PERMISSION ,
227+ severity = severity,
228+ message = " Uses dangerous permission: $permission - Review if absolutely necessary" ,
229+ location = " AndroidManifest.xml (uses-permission)" ,
230+ buildType = " all"
231+ )
232+ )
233+ }
234+ }
235+
236+ // Check for permission declarations
237+ val permissionDeclarations = Regex (""" <permission[^>]*android:name="([^"]+)"""" ).findAll(content)
238+ val declaredPermissions = permissionDeclarations.map { it.groupValues[1 ] }.toSet()
239+
240+ // Check for uses-permission with undefined permissions
241+ val usesPermissions = Regex (""" android:name="android\.permission\.([^"]+)"""" ).findAll(content)
242+ usesPermissions.forEach { match ->
243+ val permName = " android.permission.${match.groupValues[1 ]} "
244+ if (permName !in declaredPermissions && ! permName.startsWith(" android.permission.COMPANION_" )) {
245+ // Only warn for custom permissions, not system permissions
246+ if (! permName.startsWith(" android.permission." )) {
247+ findings.add(
248+ SecurityFinding (
249+ type = SecurityIssueType .PERMISSION_NOT_DEFINED ,
250+ severity = Severity .MEDIUM ,
251+ message = " Uses permission '$permName ' that is not explicitly declared" ,
252+ location = " AndroidManifest.xml (uses-permission)" ,
253+ buildType = " all"
254+ )
255+ )
256+ }
257+ }
258+ }
259+ }
260+
261+ private fun checkComponentSecurity (content : String ) {
262+ // Check exported services without permission
263+ val servicePattern = """ <service[^>]*android:exported="true"[^>]*>""" .toRegex()
264+ servicePattern.findAll(content).forEach { match ->
265+ val serviceContent = match.value
266+ if (! serviceContent.contains(" android:permission=" )) {
267+ val nameMatch = Regex (""" android:name="([^"]+)"""" ).find(serviceContent)
268+ val componentName = nameMatch?.groupValues?.get(1 ) ? : " Unknown Service"
269+ findings.add(
270+ SecurityFinding (
271+ type = SecurityIssueType .EXPORTED_SERVICE ,
272+ severity = Severity .MEDIUM ,
273+ message = " Exported service '$componentName ' has no permission protection" ,
274+ location = " AndroidManifest.xml ($componentName )" ,
275+ buildType = " all"
276+ )
277+ )
278+ }
279+ }
280+
281+ // Check exported broadcast receivers without permission
282+ val receiverPattern = """ <receiver[^>]*android:exported="true"[^>]*>""" .toRegex()
283+ receiverPattern.findAll(content).forEach { match ->
284+ val receiverContent = match.value
285+ if (! receiverContent.contains(" android:permission=" )) {
286+ val nameMatch = Regex (""" android:name="([^"]+)"""" ).find(receiverContent)
287+ val componentName = nameMatch?.groupValues?.get(1 ) ? : " Unknown Receiver"
288+ findings.add(
289+ SecurityFinding (
290+ type = SecurityIssueType .EXPORTED_RECEIVER ,
291+ severity = Severity .MEDIUM ,
292+ message = " Exported broadcast receiver '$componentName ' has no permission protection" ,
293+ location = " AndroidManifest.xml ($componentName )" ,
294+ buildType = " all"
295+ )
296+ )
297+ }
298+ }
299+
300+ // Check exported content providers without permission
301+ val providerPattern = """ <provider[^>]*android:exported="true"[^>]*>""" .toRegex()
302+ providerPattern.findAll(content).forEach { match ->
303+ val providerContent = match.value
304+ if (! providerContent.contains(" android:permission=" ) && ! providerContent.contains(" android:grantUriPermissions=" )) {
305+ val nameMatch = Regex (""" android:name="([^"]+)"""" ).find(providerContent)
306+ val componentName = nameMatch?.groupValues?.get(1 ) ? : " Unknown Provider"
307+ findings.add(
308+ SecurityFinding (
309+ type = SecurityIssueType .EXPORTED_PROVIDER ,
310+ severity = Severity .HIGH ,
311+ message = " Exported content provider '$componentName ' has no permission protection" ,
312+ location = " AndroidManifest.xml ($componentName )" ,
313+ buildType = " all"
314+ )
315+ )
316+ }
317+ }
318+ }
319+
320+ private fun checkIntentFilterSecurity (content : String ) {
321+ // Check for intent filters with data exposure risk
322+ val intentFilterPattern = """ <intent-filter[^>]*>[\s\S]*?</intent-filter>""" .toRegex()
323+ intentFilterPattern.findAll(content).forEach { match ->
324+ val intentFilter = match.value
325+
326+ // Check for implicit intents with data
327+ if (intentFilter.contains(" <data " ) && intentFilter.contains(" android:exported=\" true\" " )) {
328+ val actionMatch = Regex (""" <action[^>]*android:name="([^"]+)"""" ).find(intentFilter)
329+ val action = actionMatch?.groupValues?.get(1 ) ? : " Unknown"
330+
331+ findings.add(
332+ SecurityFinding (
333+ type = SecurityIssueType .INTENT_FILTER_DATA_EXPOSURE ,
334+ severity = Severity .LOW ,
335+ message = " Intent filter with action '$action ' may expose data - verify intent handling" ,
336+ location = " AndroidManifest.xml (intent-filter)" ,
337+ buildType = " all"
338+ )
339+ )
340+ }
341+ }
342+ }
343+
182344 private fun logFindings () {
183345 logger.quiet(" =" .repeat(50 ))
184346 logger.quiet(" Security Check Results" )
0 commit comments