Skip to content

Commit 4194da9

Browse files
Add tests for manifest analysis and update roadmap
1 parent c6d8fd4 commit 4194da9

4 files changed

Lines changed: 490 additions & 6 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dependencies {
1717
implementation("com.android.tools.build:gradle:8.2.2")
1818
implementation(gradleApi())
1919
implementation(localGroovy())
20+
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
2021
}
2122

2223
java {

docs/README.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,39 @@ Analyzes your built APK to understand its composition and identify optimization
3434
### 3. Security Check
3535
Identifies common security vulnerabilities in your Android project.
3636

37-
**Checks:**
37+
**Build Configuration Checks:**
3838
| Issue | Severity | Description |
3939
|-------|----------|-------------|
4040
| Debug Enabled in Release | HIGH | debuggable=true in release build |
4141
| ProGuard/R8 Disabled | HIGH | minifyEnabled=false in release |
4242
| Debug App ID | MEDIUM | Application ID contains .debug or .test |
43+
44+
**Manifest Security Checks:**
45+
| Issue | Severity | Description |
46+
|-------|----------|-------------|
4347
| Manifest Debuggable | HIGH | android:debuggable="true" in manifest |
4448
| Allow Backup | MEDIUM | android:allowBackup="true" |
4549
| Cleartext Traffic | MEDIUM | android:usesCleartextTraffic="true" |
4650
| Exported Component | LOW | Exported without permission |
4751

52+
**Permission Analysis:**
53+
| Issue | Severity | Description |
54+
|-------|----------|-------------|
55+
| Dangerous Permission | HIGH/MEDIUM | Dangerous permissions (READ_SMS, CAMERA, etc.) |
56+
| Permission Not Defined | MEDIUM | Uses undefined custom permission |
57+
58+
**Component Security:**
59+
| Issue | Severity | Description |
60+
|-------|----------|-------------|
61+
| Exported Service | MEDIUM | Service exported without permission |
62+
| Exported Receiver | MEDIUM | Broadcast receiver exported without permission |
63+
| Exported Provider | HIGH | Content provider exported without permission |
64+
65+
**Intent Filter Security:**
66+
| Issue | Severity | Description |
67+
|-------|----------|-------------|
68+
| Intent Filter Data Exposure | LOW | Intent filter may expose data |
69+
4870
### 4. Resource Analysis
4971
Optimizes your app's resources to reduce APK size.
5072

@@ -349,10 +371,10 @@ Check the `reportPath` configuration and ensure the directory is writable.
349371
- **HTTP URL detection**: Find cleartext HTTP URLs in code
350372
- **Certificate pinning**: Check for certificate pinning implementation
351373

352-
#### 6. Enhanced Manifest Analysis
353-
- **Permission analysis**: Review permission usage
354-
- **Component security**: Detailed exported component analysis
355-
- **Intent filter security**: Check for intent filter vulnerabilities
374+
#### 6. Enhanced Manifest Analysis ✅ IMPLEMENTED
375+
- **Permission analysis**: Review permission usage
376+
- **Component security**: Detailed exported component analysis
377+
- **Intent filter security**: Check for intent filter vulnerabilities
356378

357379
#### 7. CI/CD Integration
358380
- **JSON/XML export**: Machine-readable report formats

src/main/kotlin/com/davideagostini/analyzer/tasks/SecurityCheckTask.kt

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)