@dailephd/my-dev-kit-lab 0.3.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/README.md +12 -287
  2. package/dist/scripts/security/validate.js +210 -70
  3. package/dist/src/audits/codeRot/detectors/architectureDriftDetector.js +5 -2
  4. package/dist/src/audits/codeRot/detectors/securityValidationAssumptionRotDetector.js +5 -2
  5. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/analyzeBackupConfiguration.js +300 -0
  6. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/checkResult.js +41 -0
  7. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/index.js +8 -0
  8. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/manifestEvidence.js +102 -0
  9. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/parseBackupRules.js +110 -0
  10. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/types.js +27 -0
  11. package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +50 -0
  12. package/dist/src/mobile/android/advancedSecurity/clipboard/analyzeClipboardSecurity.js +166 -0
  13. package/dist/src/mobile/android/advancedSecurity/clipboard/checkResult.js +74 -0
  14. package/dist/src/mobile/android/advancedSecurity/clipboard/collectClipboardEvidence.js +82 -0
  15. package/dist/src/mobile/android/advancedSecurity/clipboard/index.js +1 -0
  16. package/dist/src/mobile/android/advancedSecurity/clipboard/types.js +1 -0
  17. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/checkResult.js +255 -0
  18. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/index.js +1 -0
  19. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintSarif.js +37 -0
  20. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintXml.js +36 -0
  21. package/dist/src/mobile/android/advancedSecurity/externalTools/artifacts.js +95 -0
  22. package/dist/src/mobile/android/advancedSecurity/externalTools/boundedOutput.js +33 -0
  23. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/checkResult.js +173 -0
  24. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/command.js +33 -0
  25. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/index.js +1 -0
  26. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/parseDependencyCheckJson.js +133 -0
  27. package/dist/src/mobile/android/advancedSecurity/externalTools/discoverExecutable.js +13 -0
  28. package/dist/src/mobile/android/advancedSecurity/externalTools/index.js +8 -0
  29. package/dist/src/mobile/android/advancedSecurity/externalTools/minimalEnvironment.js +26 -0
  30. package/dist/src/mobile/android/advancedSecurity/externalTools/mutation.js +77 -0
  31. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/checkResult.js +161 -0
  32. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/command.js +25 -0
  33. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/index.js +1 -0
  34. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/parseOsvJson.js +136 -0
  35. package/dist/src/mobile/android/advancedSecurity/externalTools/runBoundedExternalTool.js +51 -0
  36. package/dist/src/mobile/android/advancedSecurity/externalTools/runRequestedAndroidExternalTools.js +54 -0
  37. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/checkResult.js +170 -0
  38. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/command.js +28 -0
  39. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/index.js +2 -0
  40. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/parseSemgrepJson.js +141 -0
  41. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/rules.js +105 -0
  42. package/dist/src/mobile/android/advancedSecurity/externalTools/types.js +107 -0
  43. package/dist/src/mobile/android/advancedSecurity/fileProvider/checkResult.js +188 -0
  44. package/dist/src/mobile/android/advancedSecurity/fileProvider/index.js +1 -0
  45. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/analyzeFirebaseGoogleServices.js +197 -0
  46. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/checkResult.js +77 -0
  47. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/collectFirebaseServiceEvidence.js +193 -0
  48. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/discoverFirebaseArtifacts.js +128 -0
  49. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/index.js +1 -0
  50. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseDatabaseRules.js +55 -0
  51. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirebaseConfig.js +75 -0
  52. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirestoreStorageRules.js +88 -0
  53. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseGoogleServicesConfig.js +55 -0
  54. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/types.js +6 -0
  55. package/dist/src/mobile/android/advancedSecurity/networkSecurity/analyzeNetworkSecurity.js +394 -0
  56. package/dist/src/mobile/android/advancedSecurity/networkSecurity/checkResult.js +47 -0
  57. package/dist/src/mobile/android/advancedSecurity/networkSecurity/deriveEffectiveNetworkPolicy.js +52 -0
  58. package/dist/src/mobile/android/advancedSecurity/networkSecurity/index.js +14 -0
  59. package/dist/src/mobile/android/advancedSecurity/networkSecurity/manifestEvidence.js +109 -0
  60. package/dist/src/mobile/android/advancedSecurity/networkSecurity/parseNetworkSecurityConfig.js +186 -0
  61. package/dist/src/mobile/android/advancedSecurity/networkSecurity/types.js +1 -0
  62. package/dist/src/mobile/android/advancedSecurity/ordering.js +44 -0
  63. package/dist/src/mobile/android/advancedSecurity/redaction.js +54 -0
  64. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/analyzeReleaseConfiguration.js +221 -0
  65. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/checkResult.js +39 -0
  66. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/index.js +7 -0
  67. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/manifestEvidence.js +19 -0
  68. package/dist/src/mobile/android/advancedSecurity/resourceReference.js +69 -0
  69. package/dist/src/mobile/android/advancedSecurity/resourceResolution.js +118 -0
  70. package/dist/src/mobile/android/advancedSecurity/ruleIds.js +141 -0
  71. package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +116 -0
  72. package/dist/src/mobile/android/advancedSecurity/secretCandidates/checkResult.js +80 -0
  73. package/dist/src/mobile/android/advancedSecurity/secretCandidates/discoverSecretSourceFiles.js +152 -0
  74. package/dist/src/mobile/android/advancedSecurity/secretCandidates/index.js +9 -0
  75. package/dist/src/mobile/android/advancedSecurity/secretCandidates/matchSecretCandidates.js +136 -0
  76. package/dist/src/mobile/android/advancedSecurity/secretCandidates/suppressValue.js +61 -0
  77. package/dist/src/mobile/android/advancedSecurity/secretCandidates/types.js +11 -0
  78. package/dist/src/mobile/android/advancedSecurity/sensitiveCategories.js +34 -0
  79. package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifyDirectExpression.js +99 -0
  80. package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifySensitiveIdentifier.js +111 -0
  81. package/dist/src/mobile/android/advancedSecurity/sensitiveData/index.js +3 -0
  82. package/dist/src/mobile/android/advancedSecurity/sensitiveData/localSourceContext.js +90 -0
  83. package/dist/src/mobile/android/advancedSecurity/sensitiveData/splitArguments.js +59 -0
  84. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/analyzeSensitiveLogging.js +117 -0
  85. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/checkResult.js +74 -0
  86. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/collectLoggingEvidence.js +87 -0
  87. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/index.js +1 -0
  88. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/types.js +1 -0
  89. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/analyzeSensitiveStorage.js +182 -0
  90. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/checkResult.js +74 -0
  91. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/collectStorageEvidence.js +109 -0
  92. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/index.js +1 -0
  93. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/types.js +4 -0
  94. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +270 -0
  95. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/checkResult.js +119 -0
  96. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/discoverKeystoreCandidates.js +55 -0
  97. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +81 -0
  98. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/index.js +8 -0
  99. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/types.js +20 -0
  100. package/dist/src/mobile/android/advancedSecurity/sourceLocation.js +15 -0
  101. package/dist/src/mobile/android/advancedSecurity/webview/checkResult.js +210 -0
  102. package/dist/src/mobile/android/advancedSecurity/webview/index.js +1 -0
  103. package/dist/src/mobile/android/audit/androidFinding.js +68 -0
  104. package/dist/src/mobile/android/audit/checkResultBuilder.js +97 -0
  105. package/dist/src/mobile/android/audit/deepLinkAudit.js +183 -0
  106. package/dist/src/mobile/android/audit/exportedComponentAudit.js +183 -0
  107. package/dist/src/mobile/android/audit/intentFilterAudit.js +128 -0
  108. package/dist/src/mobile/android/audit/permissionAudit.js +142 -0
  109. package/dist/src/mobile/android/detect/androidTargetDetection.js +18 -0
  110. package/dist/src/mobile/android/detect/buildFileEvidence.js +36 -0
  111. package/dist/src/mobile/android/detect/classify.js +150 -0
  112. package/dist/src/mobile/android/detect/detectAndroidProject.js +252 -0
  113. package/dist/src/mobile/android/detect/settingsModules.js +60 -0
  114. package/dist/src/mobile/android/detect/sourceEvidence.js +92 -0
  115. package/dist/src/mobile/android/detect/traversal.js +79 -0
  116. package/dist/src/mobile/android/detect/versionCatalogEvidence.js +37 -0
  117. package/dist/src/mobile/android/detection.js +28 -0
  118. package/dist/src/mobile/android/gradle/gradleMetadataCheckResult.js +120 -0
  119. package/dist/src/mobile/android/gradle/moduleMetadataExtractor.js +243 -0
  120. package/dist/src/mobile/android/gradle/pluginVersionExtractor.js +40 -0
  121. package/dist/src/mobile/android/gradle/readAndroidGradleMetadata.js +210 -0
  122. package/dist/src/mobile/android/gradle/releaseMetadataSummary.js +64 -0
  123. package/dist/src/mobile/android/gradle/settingsMetadata.js +20 -0
  124. package/dist/src/mobile/android/gradle/types.js +1 -0
  125. package/dist/src/mobile/android/gradle/validate/executor.js +9 -0
  126. package/dist/src/mobile/android/gradle/validate/operationCheckResult.js +99 -0
  127. package/dist/src/mobile/android/gradle/validate/operations.js +68 -0
  128. package/dist/src/mobile/android/gradle/validate/planner.js +49 -0
  129. package/dist/src/mobile/android/gradle/validate/runOptionalGradleValidation.js +113 -0
  130. package/dist/src/mobile/android/gradle/validate/targetMutation.js +45 -0
  131. package/dist/src/mobile/android/gradle/validate/taskListParser.js +24 -0
  132. package/dist/src/mobile/android/gradle/versionCatalogMetadata.js +94 -0
  133. package/dist/src/mobile/android/gradle/wrapperMetadata.js +32 -0
  134. package/dist/src/mobile/android/manifest/parseAndroidManifest.js +364 -0
  135. package/dist/src/mobile/android/manifest/types.js +29 -0
  136. package/dist/src/mobile/android/manifest/xml/parseXml.js +329 -0
  137. package/dist/src/mobile/android/profile.js +29 -0
  138. package/dist/src/mobile/android/report/model.js +135 -0
  139. package/dist/src/mobile/android/report/renderAndroidReport.js +286 -0
  140. package/dist/src/mobile/android/report/writeAndroidReportFiles.js +23 -0
  141. package/dist/src/mobile/android/targetMetadata.js +27 -0
  142. package/dist/src/mobile/android/validate/androidVerdict.js +248 -0
  143. package/dist/src/mobile/android/validate/detectionAndImmutabilityCheckResults.js +177 -0
  144. package/dist/src/mobile/android/validate/playReadinessChecklist.js +145 -0
  145. package/dist/src/mobile/android/validate/validateAndroidTarget.js +315 -0
  146. package/dist/src/mobile/android/validation/checkResult.js +58 -0
  147. package/dist/src/mobile/android/validation/result.js +25 -0
  148. package/dist/src/mobile/index.js +42 -0
  149. package/dist/src/mobile/types.js +27 -0
  150. package/dist/src/securityValidation/attackScenarios/attackProfile.js +2 -0
  151. package/dist/src/securityValidation/attackScenarios/profiles/androidProfile.js +20 -0
  152. package/dist/src/securityValidation/commandRunner.js +10 -1
  153. package/dist/src/securityValidation/validate/cliOptions.js +92 -2
  154. package/package.json +2 -1
  155. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/debug/res/xml/ambiguous_backup_rules.xml +4 -0
  156. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/ambiguous_backup_rules.xml +4 -0
  157. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/backup_rules.xml +5 -0
  158. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/broad_backup_rules.xml +4 -0
  159. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/data_extraction_rules.xml +10 -0
  160. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/unsupported_domain_rules.xml +4 -0
  161. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/malformed_backup_rules.xml +4 -0
  162. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/unsupported_root.xml +4 -0
  163. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/debug/res/xml/ambiguous_config.xml +4 -0
  164. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/ambiguous_config.xml +4 -0
  165. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/base_cleartext_false.xml +8 -0
  166. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/broad_and_duplicate_domains.xml +8 -0
  167. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/empty_domain.xml +6 -0
  168. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/malformed_pin.xml +11 -0
  169. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/network_security_config.xml +26 -0
  170. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/malformed_network_security_config.xml +6 -0
  171. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/unsupported_root.xml +4 -0
  172. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-dynamic.gradle +7 -0
  173. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-false.gradle +9 -0
  174. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-true.gradle +13 -0
  175. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-false.gradle.kts +7 -0
  176. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-true.gradle.kts +10 -0
  177. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/missing-release-build-type.gradle.kts +7 -0
  178. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/debug/res/xml/network_security_config.xml +8 -0
  179. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/backup_rules.xml +4 -0
  180. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/network_security_config.xml +8 -0
  181. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-b/src/main/res/xml/malformed_network_security_config.xml +7 -0
  182. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.java +8 -0
  183. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.kt +9 -0
  184. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle +11 -0
  185. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle.kts +11 -0
  186. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/google-services.json +16 -0
  187. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/local.properties +3 -0
  188. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/binary.properties +0 -0
  189. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/oversized.json +1 -0
  190. package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/app/fake-release-keystore.jks +1 -0
  191. package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/build/generated-fake.keystore +1 -0
  192. package/tests/fixtures/android/compose-app/app/build.gradle.kts +21 -0
  193. package/tests/fixtures/android/compose-app/app/src/main/AndroidManifest.xml +22 -0
  194. package/tests/fixtures/android/compose-app/app/src/main/java/com/example/composeapp/MainActivity.kt +11 -0
  195. package/tests/fixtures/android/compose-app/build.gradle.kts +4 -0
  196. package/tests/fixtures/android/compose-app/gradle/wrapper/gradle-wrapper.properties +4 -0
  197. package/tests/fixtures/android/compose-app/settings.gradle.kts +2 -0
  198. package/tests/fixtures/android/gradle-metadata-fixtures/catalog-only-plugin/build.gradle.kts +3 -0
  199. package/tests/fixtures/android/gradle-metadata-fixtures/catalog-only-plugin/gradle/libs.versions.toml +5 -0
  200. package/tests/fixtures/android/gradle-metadata-fixtures/comment-safety.gradle.kts +21 -0
  201. package/tests/fixtures/android/gradle-metadata-fixtures/dynamic-values.gradle.kts +17 -0
  202. package/tests/fixtures/android/gradle-metadata-fixtures/groovy-literal.gradle +23 -0
  203. package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-malformed.properties +4 -0
  204. package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-missing-url.properties +3 -0
  205. package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-with-checksum.properties +5 -0
  206. package/tests/fixtures/android/java-app/app/build.gradle +16 -0
  207. package/tests/fixtures/android/java-app/app/src/main/AndroidManifest.xml +19 -0
  208. package/tests/fixtures/android/java-app/app/src/main/java/com/example/javaapp/MainActivity.java +4 -0
  209. package/tests/fixtures/android/java-app/build.gradle +3 -0
  210. package/tests/fixtures/android/java-app/gradle/wrapper/gradle-wrapper.properties +4 -0
  211. package/tests/fixtures/android/java-app/settings.gradle +2 -0
  212. package/tests/fixtures/android/library/build.gradle.kts +3 -0
  213. package/tests/fixtures/android/library/gradle/wrapper/gradle-wrapper.properties +4 -0
  214. package/tests/fixtures/android/library/mylibrary/build.gradle.kts +13 -0
  215. package/tests/fixtures/android/library/mylibrary/src/main/AndroidManifest.xml +2 -0
  216. package/tests/fixtures/android/library/settings.gradle.kts +2 -0
  217. package/tests/fixtures/android/manifest-audit-fixtures/incomplete-intent-filter.xml +14 -0
  218. package/tests/fixtures/android/manifest-audit-fixtures/malformed-exported.xml +10 -0
  219. package/tests/fixtures/android/manifest-audit-fixtures/malformed.xml +5 -0
  220. package/tests/fixtures/android/manifest-audit-fixtures/missing-application.xml +7 -0
  221. package/tests/fixtures/android/manifest-audit-fixtures/missing-component-name.xml +11 -0
  222. package/tests/fixtures/android/manifest-audit-fixtures/permission-coverage.xml +29 -0
  223. package/tests/fixtures/android/manifest-audit-fixtures/risky-components.xml +62 -0
  224. package/tests/fixtures/android/manifest-audit-fixtures/risky-deep-links.xml +45 -0
  225. package/tests/fixtures/android/manifest-audit-fixtures/unresolved-placeholder.xml +14 -0
  226. package/tests/fixtures/android/manifest-audit-fixtures/unusual-namespace-prefix.xml +11 -0
  227. package/tests/fixtures/android/multi-module/app/build.gradle.kts +22 -0
  228. package/tests/fixtures/android/multi-module/app/src/androidTest/java/com/example/multimodule/app/ExampleInstrumentedTest.kt +3 -0
  229. package/tests/fixtures/android/multi-module/app/src/main/AndroidManifest.xml +11 -0
  230. package/tests/fixtures/android/multi-module/app/src/test/java/com/example/multimodule/app/ExampleUnitTest.kt +3 -0
  231. package/tests/fixtures/android/multi-module/build.gradle.kts +5 -0
  232. package/tests/fixtures/android/multi-module/core/build.gradle.kts +14 -0
  233. package/tests/fixtures/android/multi-module/core/src/main/AndroidManifest.xml +2 -0
  234. package/tests/fixtures/android/multi-module/feature-login/build.gradle.kts +18 -0
  235. package/tests/fixtures/android/multi-module/feature-login/src/main/AndroidManifest.xml +2 -0
  236. package/tests/fixtures/android/multi-module/gradle/libs.versions.toml +8 -0
  237. package/tests/fixtures/android/multi-module/gradle/wrapper/gradle-wrapper.properties +4 -0
  238. package/tests/fixtures/android/multi-module/settings.gradle.kts +4 -0
  239. package/tests/fixtures/android/non-android-gradle/build.gradle.kts +3 -0
  240. package/tests/fixtures/android/non-android-gradle/core/build.gradle.kts +3 -0
  241. package/tests/fixtures/android/non-android-gradle/core/src/main/java/com/example/core/Core.java +4 -0
  242. package/tests/fixtures/android/non-android-gradle/gradle/wrapper/gradle-wrapper.properties +4 -0
  243. package/tests/fixtures/android/non-android-gradle/settings.gradle.kts +2 -0
  244. package/tests/fixtures/android/partial/app/build.gradle.kts +7 -0
  245. package/tests/fixtures/android/partial/settings.gradle.kts +2 -0
  246. package/tests/fixtures/android/xml-view-app/app/build.gradle.kts +17 -0
  247. package/tests/fixtures/android/xml-view-app/app/src/main/AndroidManifest.xml +31 -0
  248. package/tests/fixtures/android/xml-view-app/app/src/main/kotlin/com/example/xmlviewapp/MainActivity.kt +7 -0
  249. package/tests/fixtures/android/xml-view-app/app/src/main/res/layout/activity_main.xml +4 -0
  250. package/tests/fixtures/android/xml-view-app/build.gradle.kts +4 -0
  251. package/tests/fixtures/android/xml-view-app/gradle/wrapper/gradle-wrapper.properties +4 -0
  252. package/tests/fixtures/android/xml-view-app/settings.gradle.kts +2 -0
@@ -0,0 +1,286 @@
1
+ import { stripUnsafeControlChars } from "../../../securityValidation/attackScenarios/exploitEvidence.js";
2
+ // ---------------------------------------------------------------------------
3
+ // v0.4.0 Batch 5 — Android text report rendering (agents.txt Batch 5 section
4
+ // 15.4). Mirrors the section structure and defensive text-sanitization
5
+ // convention of src/securityValidation/report/renderSecurityReport.ts
6
+ // (stripUnsafeControlChars guards against ANSI/control-byte report
7
+ // poisoning from scanned manifest/Gradle content).
8
+ // ---------------------------------------------------------------------------
9
+ function divider(char = "-", width = 72) {
10
+ return char.repeat(width);
11
+ }
12
+ function sanitize(raw) {
13
+ return stripUnsafeControlChars(raw);
14
+ }
15
+ function formatDuration(ms) {
16
+ if (ms < 1000)
17
+ return `${ms}ms`;
18
+ return `${(ms / 1000).toFixed(1)}s`;
19
+ }
20
+ // v0.4.1 Batch 8 — shared per-check rendering for the new ADVANCED SECURITY
21
+ // CHECKS and EXTERNAL TOOL RESULTS sections. Bounded: prints counts and
22
+ // identifiers, never raw stdout/stderr/environment/complete source (that
23
+ // bound is already enforced by each check builder before evidence reaches
24
+ // this report — this function does not itself receive raw tool output).
25
+ function renderCheckSummaryLines(lines, c) {
26
+ lines.push(` ${c.id} [${c.status}] — ${sanitize(c.title)}`);
27
+ lines.push(` category: ${c.category} findings: ${c.findings.length} candidates: ${c.candidateEvidence?.length ?? 0}`);
28
+ if (c.evidence.length > 0)
29
+ lines.push(` evidence: ${c.evidence.map(sanitize).join(" | ")}`);
30
+ if (c.durationMs !== undefined)
31
+ lines.push(` duration: ${formatDuration(c.durationMs)}`);
32
+ if (c.skipInfo)
33
+ lines.push(` skip reason: ${sanitize(c.skipInfo.reason)}`);
34
+ for (const w of c.warnings)
35
+ lines.push(` warning: ${sanitize(w)}`);
36
+ for (const e of c.errors)
37
+ lines.push(` error: ${sanitize(e)}`);
38
+ if (c.environmentRequirements.length > 0)
39
+ lines.push(` limitations: ${c.environmentRequirements.map(sanitize).join(" | ")}`);
40
+ }
41
+ export function renderAndroidTextReport(model) {
42
+ const lines = [];
43
+ // 1. Summary
44
+ lines.push(divider("="));
45
+ lines.push("ANDROID SECURITY-VALIDATION SUMMARY");
46
+ lines.push(divider("="));
47
+ lines.push(sanitize(model.executiveSummary));
48
+ lines.push(`Profile : ${model.metadata.profile}`);
49
+ lines.push(`Target : ${model.metadata.target.local.targetRoot}`);
50
+ lines.push(`Tool : ${model.metadata.tool.toolPackageName}@${model.metadata.tool.toolPackageVersion}`);
51
+ lines.push(`Generated : ${model.metadata.generatedAt}`);
52
+ lines.push(`Duration : ${formatDuration(model.metadata.totalDurationMs)}`);
53
+ lines.push("");
54
+ // 2-3. Verdict and reasons
55
+ lines.push(divider());
56
+ lines.push("VERDICT");
57
+ lines.push(divider());
58
+ lines.push(`Verdict: ${model.verdict} (${model.verdictHumanLabel})`);
59
+ lines.push(`Next step: ${sanitize(model.recommendedNextStep)}`);
60
+ if (model.verdictReasons.length > 0) {
61
+ lines.push("");
62
+ lines.push("Verdict reasons:");
63
+ for (const r of model.verdictReasons) {
64
+ lines.push(` [${r.impact}] ${r.code}: ${sanitize(r.summary)}`);
65
+ lines.push(` -> ${sanitize(r.recommendedAction)}`);
66
+ }
67
+ }
68
+ lines.push("");
69
+ // 4. Target identity
70
+ lines.push(divider());
71
+ lines.push("TARGET IDENTITY");
72
+ lines.push(divider());
73
+ lines.push(`Target root : ${model.metadata.target.local.targetRoot}`);
74
+ lines.push(`Tool root : ${model.metadata.target.local.toolRoot}`);
75
+ lines.push(`Package : ${model.metadata.target.local.packageName ?? "(unknown)"}`);
76
+ lines.push(`Git branch : ${model.metadata.target.local.branch ?? "(unavailable)"}`);
77
+ lines.push(`Git commit : ${model.metadata.target.local.commit ?? "(unavailable)"}`);
78
+ lines.push("");
79
+ // 5-6. Classification
80
+ lines.push(divider());
81
+ lines.push("ANDROID PROJECT CLASSIFICATION");
82
+ lines.push(divider());
83
+ lines.push(`${model.detectionSummary.title}: ${model.detectionSummary.summary}`);
84
+ lines.push(`${model.androidProjectSummary.title}: ${model.androidProjectSummary.summary}`);
85
+ lines.push(`${model.uiToolkitSummary.title}: ${model.uiToolkitSummary.summary}`);
86
+ lines.push("");
87
+ // 7. Module summary
88
+ lines.push(divider());
89
+ lines.push("MODULE SUMMARY");
90
+ lines.push(divider());
91
+ if (model.moduleSummary.length === 0) {
92
+ lines.push("(no modules detected)");
93
+ }
94
+ else {
95
+ for (const m of model.moduleSummary) {
96
+ lines.push(` ${m.path.padEnd(30)} kind=${m.kind.padEnd(12)} uiToolkit=${m.uiToolkit ?? "uncertain"}`);
97
+ }
98
+ }
99
+ lines.push("");
100
+ // 8. Environment and execution summary
101
+ lines.push(divider());
102
+ lines.push("ENVIRONMENT AND EXECUTION SUMMARY");
103
+ lines.push(divider());
104
+ lines.push(`Requested Gradle operations: ${model.metadata.requestedGradleOperations.length > 0 ? model.metadata.requestedGradleOperations.join(", ") : "(none — static-only validation, no Gradle process executed)"}`);
105
+ if (model.environmentLimitations.length > 0) {
106
+ for (const note of model.environmentLimitations)
107
+ lines.push(` - ${sanitize(note)}`);
108
+ }
109
+ lines.push("");
110
+ // 9-13. Manifest / permissions / components / intent-filters / deep-links
111
+ lines.push(divider());
112
+ lines.push("MANIFEST SUMMARY");
113
+ lines.push(divider());
114
+ lines.push(sanitize(model.manifestSummary.summary));
115
+ lines.push("");
116
+ lines.push(divider());
117
+ lines.push("PERMISSIONS SUMMARY");
118
+ lines.push(divider());
119
+ lines.push(sanitize(model.permissionsSummary.summary));
120
+ lines.push("");
121
+ lines.push(divider());
122
+ lines.push("EXPORTED COMPONENT SUMMARY");
123
+ lines.push(divider());
124
+ lines.push(sanitize(model.componentSummary.summary));
125
+ lines.push("");
126
+ lines.push(divider());
127
+ lines.push("INTENT-FILTER SUMMARY");
128
+ lines.push(divider());
129
+ lines.push(sanitize(model.intentFilterSummary.summary));
130
+ lines.push("");
131
+ lines.push(divider());
132
+ lines.push("DEEP-LINK SUMMARY");
133
+ lines.push(divider());
134
+ lines.push(sanitize(model.deepLinkSummary.summary));
135
+ lines.push("");
136
+ // 14. Static Gradle metadata
137
+ lines.push(divider());
138
+ lines.push("STATIC GRADLE METADATA");
139
+ lines.push(divider());
140
+ lines.push(sanitize(model.gradleMetadataSummary.summary));
141
+ if (model.gradleCheckSummary)
142
+ lines.push(sanitize(model.gradleCheckSummary.summary));
143
+ lines.push("");
144
+ // 15. Optional Gradle operation results
145
+ lines.push(divider());
146
+ lines.push("OPTIONAL GRADLE OPERATION RESULTS");
147
+ lines.push(divider());
148
+ if (model.gradleOperationResults.length === 0) {
149
+ lines.push("No optional Gradle operations were requested — zero Gradle processes executed.");
150
+ }
151
+ else {
152
+ for (const op of model.gradleOperationResults) {
153
+ lines.push(` ${op.id} — status=${op.status} ran=${op.ran}`);
154
+ if (op.command) {
155
+ lines.push(` command: ${sanitize(op.command.command)} ${op.command.args.map(sanitize).join(" ")}`);
156
+ lines.push(` cwd: ${op.command.cwd} exitCode: ${op.command.exitCode ?? "null"} timedOut: ${op.command.timedOut}`);
157
+ }
158
+ if (op.durationMs !== undefined)
159
+ lines.push(` duration: ${formatDuration(op.durationMs)}`);
160
+ if (op.skipInfo)
161
+ lines.push(` skip reason: ${sanitize(op.skipInfo.reason)}`);
162
+ for (const w of op.warnings)
163
+ lines.push(` warning: ${sanitize(w)}`);
164
+ }
165
+ }
166
+ lines.push("");
167
+ // 15b. Advanced security checks (v0.4.1 Batch 8)
168
+ lines.push(divider());
169
+ lines.push("ADVANCED SECURITY CHECKS");
170
+ lines.push(divider());
171
+ if (model.advancedSecurityChecks.length === 0) {
172
+ lines.push("(none ran — not applicable to this target)");
173
+ }
174
+ else {
175
+ for (const c of model.advancedSecurityChecks) {
176
+ renderCheckSummaryLines(lines, c);
177
+ }
178
+ }
179
+ lines.push("");
180
+ // 15c. External tool results (v0.4.1 Batch 8)
181
+ lines.push(divider());
182
+ lines.push("EXTERNAL TOOL RESULTS");
183
+ lines.push(divider());
184
+ lines.push(`Requested external tools: ${model.metadata.requestedExternalTools.length > 0 ? model.metadata.requestedExternalTools.join(", ") : "(none — zero external-tool process execution)"}`);
185
+ if (model.metadata.requestedExternalTools.length > 0) {
186
+ lines.push(`Effective network policy: ${model.metadata.externalNetworkPolicy}`);
187
+ }
188
+ if (model.externalToolChecks.length === 0) {
189
+ lines.push("No external tools were requested.");
190
+ }
191
+ else {
192
+ for (const c of model.externalToolChecks) {
193
+ renderCheckSummaryLines(lines, c);
194
+ }
195
+ }
196
+ lines.push("");
197
+ // 15d. Candidate evidence (review required, distinct from confirmed findings)
198
+ lines.push(divider());
199
+ lines.push("CANDIDATE EVIDENCE (REVIEW REQUIRED — NOT CONFIRMED FINDINGS)");
200
+ lines.push(divider());
201
+ lines.push(sanitize(model.candidateEvidenceSummary.summary));
202
+ for (const c of [...model.advancedSecurityChecks, ...model.externalToolChecks]) {
203
+ for (const candidate of c.candidateEvidence ?? []) {
204
+ lines.push(` [${candidate.confidence}/${candidate.resolutionState}] ${candidate.ruleId} — ${sanitize(candidate.summary)}`);
205
+ lines.push(` location: ${candidate.location.path}${candidate.location.line !== undefined ? `:${candidate.location.line}` : ""}`);
206
+ }
207
+ }
208
+ lines.push("");
209
+ // 16. Release metadata
210
+ lines.push(divider());
211
+ lines.push("RELEASE METADATA");
212
+ lines.push(divider());
213
+ lines.push(sanitize(model.releaseMetadataSummary.summary));
214
+ lines.push("");
215
+ // 17. Play-readiness checklist
216
+ lines.push(divider());
217
+ lines.push("PLAY-READINESS CHECKLIST PLACEHOLDERS");
218
+ lines.push(divider());
219
+ lines.push(sanitize(model.playReadinessSummary.summary));
220
+ for (const item of model.playReadinessItems) {
221
+ lines.push(` [${item.status}] ${item.title}: ${sanitize(item.detail)}`);
222
+ }
223
+ lines.push("");
224
+ // 18. Findings grouped by severity
225
+ lines.push(divider());
226
+ lines.push("FINDINGS BY SEVERITY");
227
+ lines.push(divider());
228
+ const severityOrder = ["blocker", "major", "minor", "informational"];
229
+ let anyFindings = false;
230
+ for (const severity of severityOrder) {
231
+ const findings = model.findingsBySeverity[severity];
232
+ if (!findings || findings.length === 0)
233
+ continue;
234
+ anyFindings = true;
235
+ lines.push(`${severity.toUpperCase()} (${findings.length}):`);
236
+ for (const f of findings) {
237
+ lines.push(` [${f.id}] ${sanitize(f.title)}`);
238
+ lines.push(` category: ${f.category}`);
239
+ if (f.affectedFiles && f.affectedFiles.length > 0)
240
+ lines.push(` path: ${f.affectedFiles.join(", ")}`);
241
+ lines.push(` evidence: ${sanitize(f.evidence ?? "(none)")}`);
242
+ lines.push(` ${sanitize(f.description)}`);
243
+ if (f.recommendation)
244
+ lines.push(` recommendation: ${sanitize(f.recommendation)}`);
245
+ }
246
+ }
247
+ if (!anyFindings)
248
+ lines.push("(no findings)");
249
+ lines.push("");
250
+ // 19. Skipped/unsupported/not-run/inconclusive checks
251
+ lines.push(divider());
252
+ lines.push("SKIPPED, UNSUPPORTED, NOT-RUN, AND INCONCLUSIVE CHECKS");
253
+ lines.push(divider());
254
+ const nonPassing = model.checks.filter((c) => c.status !== "passed");
255
+ if (nonPassing.length === 0) {
256
+ lines.push("(all checks passed)");
257
+ }
258
+ else {
259
+ for (const c of nonPassing) {
260
+ lines.push(` [${c.status}] ${c.id} — ${sanitize(c.title)}`);
261
+ if (c.skipInfo)
262
+ lines.push(` reason: ${sanitize(c.skipInfo.reason)}`);
263
+ }
264
+ }
265
+ lines.push("");
266
+ // 20. Target mutation evidence
267
+ lines.push(divider());
268
+ lines.push("TARGET MUTATION EVIDENCE");
269
+ lines.push(divider());
270
+ lines.push(sanitize(model.targetMutationSummary.summary));
271
+ lines.push("");
272
+ // 21. Static-analysis limitations
273
+ lines.push(divider());
274
+ lines.push("STATIC-ANALYSIS LIMITATIONS");
275
+ lines.push(divider());
276
+ for (const limitation of model.staticAnalysisLimitations) {
277
+ lines.push(` - ${limitation}`);
278
+ }
279
+ lines.push("");
280
+ // 22. Recommended next step
281
+ lines.push(divider());
282
+ lines.push("RECOMMENDED NEXT STEP");
283
+ lines.push(divider());
284
+ lines.push(sanitize(model.recommendedNextStep));
285
+ return lines.join("\n");
286
+ }
@@ -0,0 +1,23 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { renderAndroidTextReport } from "./renderAndroidReport.js";
4
+ import { serializeAndroidReportModel } from "./model.js";
5
+ export function writeAndroidReportFiles(options) {
6
+ const reportDir = options.outDir;
7
+ if (!fs.existsSync(reportDir)) {
8
+ fs.mkdirSync(reportDir, { recursive: true });
9
+ }
10
+ const basename = `${options.prefix}-android-security-validation`;
11
+ const textPath = options.formats.includes("text") ? path.join(reportDir, `${basename}.txt`) : null;
12
+ const jsonPath = options.formats.includes("json") ? path.join(reportDir, `${basename}.json`) : null;
13
+ const writtenPaths = [];
14
+ if (textPath) {
15
+ fs.writeFileSync(textPath, renderAndroidTextReport(options.report), "utf8");
16
+ writtenPaths.push(textPath);
17
+ }
18
+ if (jsonPath) {
19
+ fs.writeFileSync(jsonPath, serializeAndroidReportModel(options.report), "utf8");
20
+ writtenPaths.push(jsonPath);
21
+ }
22
+ return { textPath, jsonPath, writtenPaths };
23
+ }
@@ -0,0 +1,27 @@
1
+ export function createAndroidTargetMetadata(partial) {
2
+ const { local, androidProfile, detection } = partial;
3
+ return {
4
+ local,
5
+ androidProfile,
6
+ classification: {
7
+ projectKind: detection.projectKind,
8
+ uiToolkit: detection.uiToolkit,
9
+ },
10
+ modules: detection.modules.map((module) => module.path),
11
+ applicationModules: detection.applicationModules,
12
+ libraryModules: detection.libraryModules,
13
+ manifestPaths: detection.manifestPaths,
14
+ gradleWrapper: partial.gradleWrapper ?? { present: detection.hasGradleWrapper },
15
+ releaseMetadata: partial.releaseMetadata ?? {
16
+ available: false,
17
+ note: "Release metadata summary is not implemented in this batch.",
18
+ },
19
+ environmentCapabilities: partial.environmentCapabilities ?? {
20
+ gradleAvailable: "unknown",
21
+ androidSdkAvailable: "unknown",
22
+ notes: ["Environment capability detection is not implemented in this batch."],
23
+ },
24
+ detectionConfidence: detection.confidence,
25
+ detectionEvidence: detection.evidence,
26
+ };
27
+ }
@@ -0,0 +1,248 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.0 Batch 5 — initial Android verdict policy (agents.txt Batch 5
3
+ // section 12).
4
+ //
5
+ // Reuses the existing security-validation ReleaseVerdict vocabulary (all
6
+ // four machine values match or map directly — see result.ts's
7
+ // AndroidVerdictValue comment) rather than a parallel Android-only
8
+ // vocabulary. Precedence, evaluated in order, first match wins:
9
+ // 1. not-ready-security-blocker-remains
10
+ // 2. inconclusive-audit-environment-incomplete
11
+ // 3. ready-except-optional-manual-checks
12
+ // 4. ready-for-release-preparation
13
+ // A stronger negative verdict is never overwritten by a weaker positive one.
14
+ // ---------------------------------------------------------------------------
15
+ const AUDIT_FINDING_DRIVEN_CATEGORIES = new Set(["android-permissions", "android-components", "android-intent-filters", "android-deep-links"]);
16
+ const REQUIRED_CHECK_CATEGORIES = new Set([
17
+ "android-detection",
18
+ "android-manifest",
19
+ "android-permissions",
20
+ "android-components",
21
+ "android-intent-filters",
22
+ "android-deep-links",
23
+ "android-gradle",
24
+ ]);
25
+ export function androidVerdictToHumanLabel(verdict) {
26
+ switch (verdict) {
27
+ case "ready-for-release-preparation":
28
+ return "ready for release preparation";
29
+ case "not-ready-security-blocker-remains":
30
+ return "not ready: security blocker remains";
31
+ case "ready-except-optional-manual-checks":
32
+ return "ready except optional manual checks";
33
+ case "inconclusive-audit-environment-incomplete":
34
+ return "inconclusive: android environment incomplete";
35
+ }
36
+ }
37
+ export function calculateAndroidVerdict(input) {
38
+ const { checks, findings, detection, playReadiness, targetMutation, multipleApplicationModules } = input;
39
+ const reasons = [];
40
+ const blockerFindings = findings.filter((f) => f.severity === "blocker");
41
+ const majorFindings = findings.filter((f) => f.severity === "major");
42
+ const minorOrInformationalFindings = findings.filter((f) => f.severity === "minor" || f.severity === "informational");
43
+ // Filtered by requirementLevel as well as category: optional Gradle
44
+ // operation checks (e.g. android-gradle-unit-test-debug) share the
45
+ // "android-gradle" category with the required static metadata check
46
+ // (android-gradle-metadata) but must never be treated as a required check
47
+ // — an optional operation's failure belongs in the advisory tier (section
48
+ // 12.5), not the not-ready tier.
49
+ const requiredChecks = checks.filter((c) => REQUIRED_CHECK_CATEGORIES.has(c.category) && c.requirementLevel === "required");
50
+ // Audit-family checks (permissions/components/intent-filters/deep-links)
51
+ // report status "failed" whenever ANY non-informational finding exists,
52
+ // regardless of severity (see audit/checkResultBuilder.ts) — that binary
53
+ // status is deliberately NOT used for the not-ready decision here, since a
54
+ // single minor finding would otherwise be indistinguishable from a
55
+ // blocker. Finding *severity* (checked separately above/below) is the
56
+ // authoritative signal for those four categories; a "failed" status only
57
+ // blocks the verdict for the non-audit required checks (detection,
58
+ // manifest parsing, Gradle metadata), where it reflects a genuine
59
+ // orchestration/parsing failure rather than a graded finding.
60
+ const failedRequired = requiredChecks.filter((c) => c.status === "failed" && !AUDIT_FINDING_DRIVEN_CATEGORIES.has(c.category));
61
+ const inconclusiveRequired = requiredChecks.filter((c) => c.status === "inconclusive" || c.status === "error");
62
+ const unsupportedRequired = requiredChecks.filter((c) => c.status === "unsupported");
63
+ const skippedOptional = checks.filter((c) => c.requirementLevel === "optional" && (c.status === "skipped" || c.status === "not-run"));
64
+ const failedOptional = checks.filter((c) => c.requirementLevel === "optional" && c.status === "failed");
65
+ // ---- 1. not-ready-security-blocker-remains ---------------------------------
66
+ if (blockerFindings.length > 0) {
67
+ for (const f of blockerFindings) {
68
+ reasons.push({
69
+ code: "android-blocker-finding",
70
+ summary: `Blocker-severity finding: ${f.title}`,
71
+ relatedFindingIds: [f.id],
72
+ severityOrStatus: "blocker",
73
+ impact: "blocking",
74
+ recommendedAction: f.recommendation ?? "Resolve this finding before release preparation.",
75
+ });
76
+ }
77
+ return { verdict: "not-ready-security-blocker-remains", recommendedNextStep: `Fix ${blockerFindings.length} blocker finding(s) before release.`, reasons };
78
+ }
79
+ if (majorFindings.length > 0) {
80
+ for (const f of majorFindings) {
81
+ reasons.push({
82
+ code: "android-major-finding",
83
+ summary: `Major-severity finding: ${f.title}`,
84
+ relatedFindingIds: [f.id],
85
+ severityOrStatus: "major",
86
+ impact: "blocking",
87
+ recommendedAction: f.recommendation ?? "Resolve or explicitly accept this finding before release preparation.",
88
+ });
89
+ }
90
+ return { verdict: "not-ready-security-blocker-remains", recommendedNextStep: `Review ${majorFindings.length} major finding(s) before release.`, reasons };
91
+ }
92
+ if (targetMutation.comparable && targetMutation.unexpectedChanges.length > 0) {
93
+ reasons.push({
94
+ code: "android-unexpected-target-modification",
95
+ summary: `Unexpected target change(s) observed: ${targetMutation.unexpectedChanges.join(", ")}`,
96
+ severityOrStatus: "blocking",
97
+ impact: "blocking",
98
+ recommendedAction: "Investigate why validation modified tracked target files and re-run.",
99
+ });
100
+ return { verdict: "not-ready-security-blocker-remains", recommendedNextStep: "An unexpected tracked target-file modification occurred during validation. Investigate before release.", reasons };
101
+ }
102
+ if (failedRequired.length > 0) {
103
+ for (const c of failedRequired) {
104
+ reasons.push({
105
+ code: "android-required-check-failed",
106
+ summary: `Required check failed: ${c.title}`,
107
+ relatedCheckId: c.id,
108
+ severityOrStatus: c.status,
109
+ impact: "blocking",
110
+ recommendedAction: "Investigate the failure evidence for this check.",
111
+ });
112
+ }
113
+ return { verdict: "not-ready-security-blocker-remains", recommendedNextStep: `Required check(s) failed: ${failedRequired.map((c) => c.title).join(", ")}.`, reasons };
114
+ }
115
+ // ---- 2. inconclusive-audit-environment-incomplete ---------------------------
116
+ // Profile mismatch: the target was not detected as Android at all. This
117
+ // must never read as "ready" — every required check above reports
118
+ // "unsupported", not "passed" (agents.txt Batch 5 section 9.3).
119
+ if (detection.projectKind === "non-android") {
120
+ reasons.push({
121
+ code: "android-profile-mismatch",
122
+ summary: "The --profile android target was not detected as an Android Gradle project.",
123
+ impact: "inconclusive",
124
+ recommendedAction: "Re-run against an Android Gradle project, or select a different --profile for this target.",
125
+ });
126
+ return {
127
+ verdict: "inconclusive-audit-environment-incomplete",
128
+ recommendedNextStep: "This target does not appear to be an Android Gradle project. Android validation could not run.",
129
+ reasons,
130
+ };
131
+ }
132
+ if (detection.projectKind === "partial" || detection.projectKind === "unknown") {
133
+ reasons.push({
134
+ code: "android-partial-detection",
135
+ summary: `Android project detection is partial or ambiguous (projectKind=${detection.projectKind}).`,
136
+ severityOrStatus: detection.confidence,
137
+ impact: "inconclusive",
138
+ recommendedAction: "Confirm the target is a complete Android Gradle project and re-run detection.",
139
+ });
140
+ }
141
+ if (inconclusiveRequired.length > 0) {
142
+ for (const c of inconclusiveRequired) {
143
+ reasons.push({
144
+ code: "android-required-check-inconclusive",
145
+ summary: `Required check is ${c.status}: ${c.title}`,
146
+ relatedCheckId: c.id,
147
+ severityOrStatus: c.status,
148
+ impact: "inconclusive",
149
+ recommendedAction: c.skipInfo?.recommendedNextAction ?? "Provide the missing evidence and re-run.",
150
+ });
151
+ }
152
+ }
153
+ if (unsupportedRequired.length > 0) {
154
+ for (const c of unsupportedRequired) {
155
+ reasons.push({
156
+ code: "android-required-check-unsupported",
157
+ summary: `Required check is unsupported: ${c.title}`,
158
+ relatedCheckId: c.id,
159
+ severityOrStatus: c.status,
160
+ impact: "inconclusive",
161
+ recommendedAction: c.skipInfo?.recommendedNextAction ?? "Provide the missing capability and re-run.",
162
+ });
163
+ }
164
+ }
165
+ if (multipleApplicationModules) {
166
+ reasons.push({
167
+ code: "android-multiple-application-modules",
168
+ summary: "Multiple application modules were detected; a single release target could not be selected.",
169
+ impact: "inconclusive",
170
+ recommendedAction: "Validate each application module individually, or confirm which module is the release target.",
171
+ });
172
+ }
173
+ if ((detection.projectKind === "partial" || detection.projectKind === "unknown") ||
174
+ inconclusiveRequired.length > 0 ||
175
+ unsupportedRequired.length > 0 ||
176
+ multipleApplicationModules) {
177
+ return {
178
+ verdict: "inconclusive-audit-environment-incomplete",
179
+ recommendedNextStep: "Required Android evidence is incomplete. Address the reasons above and re-run android validation.",
180
+ reasons,
181
+ };
182
+ }
183
+ // ---- 3. ready-except-optional-manual-checks ---------------------------------
184
+ if (minorOrInformationalFindings.length > 0) {
185
+ for (const f of minorOrInformationalFindings) {
186
+ reasons.push({
187
+ code: "android-review-finding",
188
+ summary: `${f.severity} finding: ${f.title}`,
189
+ relatedFindingIds: [f.id],
190
+ severityOrStatus: f.severity,
191
+ impact: "advisory",
192
+ recommendedAction: f.recommendation ?? "Review before release.",
193
+ });
194
+ }
195
+ }
196
+ if (skippedOptional.length > 0) {
197
+ for (const c of skippedOptional) {
198
+ reasons.push({
199
+ code: "android-optional-check-not-run",
200
+ summary: `Optional check was not run or skipped: ${c.title}`,
201
+ relatedCheckId: c.id,
202
+ severityOrStatus: c.status,
203
+ impact: "advisory",
204
+ recommendedAction: "Run explicitly via --android-gradle-operations if this evidence is needed.",
205
+ });
206
+ }
207
+ }
208
+ if (failedOptional.length > 0) {
209
+ for (const c of failedOptional) {
210
+ reasons.push({
211
+ code: "android-optional-check-failed",
212
+ summary: `Optional operation failed: ${c.title}`,
213
+ relatedCheckId: c.id,
214
+ severityOrStatus: c.status,
215
+ impact: "advisory",
216
+ recommendedAction: "Review the operation output; an optional build/test/lint failure is validation evidence, not a security finding.",
217
+ });
218
+ }
219
+ }
220
+ const manualPlayItems = playReadiness.items.filter((i) => i.status === "manual-check-required" || i.status === "unresolved" || i.status === "missing");
221
+ if (playReadiness.applicable && manualPlayItems.length > 0) {
222
+ reasons.push({
223
+ code: "android-play-readiness-manual-items",
224
+ summary: `${manualPlayItems.length} Play-readiness checklist item(s) require manual confirmation.`,
225
+ impact: "advisory",
226
+ recommendedAction: "Review the Play-readiness checklist section before publishing.",
227
+ });
228
+ }
229
+ if (reasons.length > 0) {
230
+ return {
231
+ verdict: "ready-except-optional-manual-checks",
232
+ recommendedNextStep: "No blocking or required-evidence gaps remain. Review the advisory items above (findings, skipped optional checks, and manual Play-readiness items) before release preparation.",
233
+ reasons,
234
+ };
235
+ }
236
+ // ---- 4. ready-for-release-preparation ----------------------------------------
237
+ reasons.push({
238
+ code: "android-ready",
239
+ summary: "All required static Android checks completed with no blocking findings, no unexpected target modification, and no outstanding advisory items.",
240
+ impact: "advisory",
241
+ recommendedAction: "Proceed with release preparation. This does not certify Google Play compliance or publication readiness.",
242
+ });
243
+ return {
244
+ verdict: "ready-for-release-preparation",
245
+ recommendedNextStep: "All required static Android checks passed. Proceed with release preparation (this is not a Google Play compliance or publication-readiness claim).",
246
+ reasons,
247
+ };
248
+ }