@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,177 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.0 Batch 5 — the three required check families that no prior batch
3
+ // produced an AndroidCheckResult for: project detection, manifest parsing
4
+ // (as distinct from the audits that consume parsed manifests), and target
5
+ // immutability evidence (agents.txt Batch 5 section 10.1).
6
+ // ---------------------------------------------------------------------------
7
+ export function buildAndroidDetectionCheckResult(detection) {
8
+ const sourcePaths = [...detection.gradleSettingsFiles, ...detection.rootBuildFiles].sort((a, b) => a.localeCompare(b));
9
+ if (detection.projectKind === "non-android") {
10
+ return {
11
+ id: "android-project-detection",
12
+ category: "android-detection",
13
+ title: "Android project detection",
14
+ status: "unsupported",
15
+ requirementLevel: "optional",
16
+ ran: true,
17
+ skipped: true,
18
+ skipInfo: {
19
+ checkId: "android-project-detection",
20
+ reason: "Target was not detected as an Android project.",
21
+ requirementLevel: "optional",
22
+ missingCapability: "android-evidence",
23
+ verdictImpact: "does not apply to a non-Android target",
24
+ recommendedNextAction: "Confirm the target is an Android Gradle project.",
25
+ },
26
+ evidence: detection.evidence,
27
+ findings: [],
28
+ warnings: detection.warnings,
29
+ errors: [],
30
+ sourcePaths,
31
+ confidence: detection.confidence,
32
+ environmentRequirements: [],
33
+ };
34
+ }
35
+ const isPartial = detection.projectKind === "partial" || detection.projectKind === "unknown" || detection.partialOrUnsupportedStructure;
36
+ return {
37
+ id: "android-project-detection",
38
+ category: "android-detection",
39
+ title: "Android project detection",
40
+ status: isPartial ? "inconclusive" : "passed",
41
+ requirementLevel: "required",
42
+ ran: true,
43
+ skipped: false,
44
+ evidence: detection.evidence,
45
+ findings: [],
46
+ warnings: detection.warnings,
47
+ errors: [],
48
+ sourcePaths,
49
+ confidence: detection.confidence,
50
+ environmentRequirements: [],
51
+ };
52
+ }
53
+ const MALFORMED_PREFIX = "Malformed XML:";
54
+ export function buildAndroidManifestParsingCheckResult(detection, manifests) {
55
+ const sourcePaths = manifests.map((m) => m.manifestPath);
56
+ if (detection.projectKind === "non-android") {
57
+ return {
58
+ id: "android-manifest-parsing",
59
+ category: "android-manifest",
60
+ title: "Android manifest parsing",
61
+ status: "unsupported",
62
+ requirementLevel: "optional",
63
+ ran: false,
64
+ skipped: true,
65
+ skipInfo: {
66
+ checkId: "android-manifest-parsing",
67
+ reason: "Target was not detected as an Android project.",
68
+ requirementLevel: "optional",
69
+ missingCapability: "android-detection",
70
+ verdictImpact: "does not apply to a non-Android target",
71
+ recommendedNextAction: "Confirm the target is an Android Gradle project.",
72
+ },
73
+ evidence: [],
74
+ findings: [],
75
+ warnings: [],
76
+ errors: [],
77
+ sourcePaths: [],
78
+ confidence: "unknown",
79
+ environmentRequirements: [],
80
+ };
81
+ }
82
+ if (manifests.length === 0) {
83
+ return {
84
+ id: "android-manifest-parsing",
85
+ category: "android-manifest",
86
+ title: "Android manifest parsing",
87
+ status: "inconclusive",
88
+ requirementLevel: "required",
89
+ ran: true,
90
+ skipped: false,
91
+ evidence: [],
92
+ findings: [],
93
+ warnings: ["No AndroidManifest.xml was found for this target."],
94
+ errors: [],
95
+ sourcePaths: [],
96
+ confidence: "unknown",
97
+ environmentRequirements: [],
98
+ };
99
+ }
100
+ const malformed = manifests.filter((m) => m.manifest.parseWarnings.some((w) => w.startsWith(MALFORMED_PREFIX)));
101
+ const allMalformed = malformed.length === manifests.length;
102
+ const warnings = manifests.flatMap((m) => m.manifest.parseWarnings);
103
+ if (allMalformed) {
104
+ return {
105
+ id: "android-manifest-parsing",
106
+ category: "android-manifest",
107
+ title: "Android manifest parsing",
108
+ status: "error",
109
+ requirementLevel: "required",
110
+ ran: true,
111
+ skipped: false,
112
+ evidence: [],
113
+ findings: [],
114
+ warnings,
115
+ errors: malformed.flatMap((m) => m.manifest.parseWarnings.filter((w) => w.startsWith(MALFORMED_PREFIX))),
116
+ sourcePaths,
117
+ confidence: "unknown",
118
+ environmentRequirements: [],
119
+ };
120
+ }
121
+ const confidence = malformed.length > 0 ? "medium" : "high";
122
+ return {
123
+ id: "android-manifest-parsing",
124
+ category: "android-manifest",
125
+ title: "Android manifest parsing",
126
+ status: "passed",
127
+ requirementLevel: "required",
128
+ ran: true,
129
+ skipped: false,
130
+ evidence: [`${manifests.length} manifest(s) parsed independently (not merged)`],
131
+ findings: [],
132
+ warnings,
133
+ errors: [],
134
+ sourcePaths,
135
+ confidence,
136
+ environmentRequirements: [],
137
+ };
138
+ }
139
+ export function buildAndroidTargetImmutabilityCheckResult(mutation) {
140
+ if (!mutation.comparable) {
141
+ return {
142
+ id: "android-target-immutability",
143
+ category: "android-target-immutability",
144
+ title: "Android target immutability",
145
+ status: "inconclusive",
146
+ requirementLevel: "optional",
147
+ ran: true,
148
+ skipped: false,
149
+ evidence: [],
150
+ findings: [],
151
+ warnings: [`Target mutation evidence unavailable: ${mutation.reason ?? "unknown reason"}`],
152
+ errors: [],
153
+ sourcePaths: [],
154
+ confidence: "unknown",
155
+ environmentRequirements: [],
156
+ targetModificationObserved: undefined,
157
+ };
158
+ }
159
+ const unexpected = mutation.unexpectedChanges.length > 0;
160
+ return {
161
+ id: "android-target-immutability",
162
+ category: "android-target-immutability",
163
+ title: "Android target immutability",
164
+ status: unexpected ? "failed" : "passed",
165
+ requirementLevel: "optional",
166
+ ran: true,
167
+ skipped: false,
168
+ evidence: mutation.expectedGeneratedChanges.map((p) => `generatedOutput=${p}`),
169
+ findings: [],
170
+ warnings: unexpected ? [`Unexpected target change(s): ${mutation.unexpectedChanges.join(", ")}`] : [],
171
+ errors: [],
172
+ sourcePaths: [],
173
+ confidence: "high",
174
+ environmentRequirements: [],
175
+ targetModificationObserved: unexpected,
176
+ };
177
+ }
@@ -0,0 +1,145 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.0 Batch 5 — bounded Play-readiness checklist placeholders (agents.txt
3
+ // Batch 5 section 14).
4
+ //
5
+ // Informational and planning-oriented only. Never claims Google Play
6
+ // approval, compliance, upload readiness, or publication readiness — no
7
+ // network access, no live policy lookup. Manual/policy placeholder items are
8
+ // always "manual-check-required" regardless of local evidence.
9
+ // ---------------------------------------------------------------------------
10
+ export const ANDROID_PLAY_READINESS_ITEM_STATUSES = [
11
+ "confirmed",
12
+ "missing",
13
+ "unresolved",
14
+ "manual-check-required",
15
+ "not-applicable",
16
+ ];
17
+ const MANUAL_POLICY_ITEMS = [
18
+ { id: "privacy-policy-required", title: "Privacy policy", detail: "Privacy policy requirement must be assessed manually." },
19
+ { id: "play-data-safety-declaration", title: "Data Safety declaration", detail: "Google Play Data Safety declaration must be completed manually." },
20
+ {
21
+ id: "sensitive-permission-justification",
22
+ title: "Sensitive permission justification",
23
+ detail: "Sensitive or restricted permission declarations may require additional forms or justification.",
24
+ },
25
+ { id: "store-listing-content", title: "Store listing content", detail: "Store listing content must be prepared manually." },
26
+ { id: "release-notes", title: "Release notes", detail: "Release notes must be prepared." },
27
+ { id: "app-access-instructions", title: "App access instructions", detail: "App access instructions may be required." },
28
+ { id: "content-rating", title: "Content rating", detail: "Content rating must be completed." },
29
+ {
30
+ id: "target-audience-content-declarations",
31
+ title: "Target audience and content",
32
+ detail: "Target audience and content declarations must be completed.",
33
+ },
34
+ { id: "ads-declaration", title: "Ads declaration", detail: "Ads declaration may be required." },
35
+ {
36
+ id: "testing-track-account-requirements",
37
+ title: "Testing track and account requirements",
38
+ detail: "Testing-track and account requirements must be checked separately.",
39
+ },
40
+ {
41
+ id: "current-play-policy-review",
42
+ title: "Current Google Play policy review",
43
+ detail: "Current Google Play policies must be reviewed at publication time. This tool performs no live policy lookup.",
44
+ },
45
+ ];
46
+ function itemsForLibrary() {
47
+ const applicationItemIds = [
48
+ "app-module-identified",
49
+ "application-id-resolved",
50
+ "namespace-resolved",
51
+ "version-code-resolved",
52
+ "version-name-resolved",
53
+ "min-sdk-resolved",
54
+ "target-sdk-resolved",
55
+ "compile-sdk-resolved",
56
+ "target-sdk-policy-check",
57
+ "launcher-activity-identified",
58
+ "application-label-present",
59
+ "application-icon-present",
60
+ "adaptive-round-icon-evidence",
61
+ "permissions-summary-available",
62
+ ];
63
+ const items = applicationItemIds.map((id) => ({
64
+ id,
65
+ title: id,
66
+ status: "not-applicable",
67
+ detail: "Google Play application readiness does not apply directly to a library artifact.",
68
+ }));
69
+ for (const policyItem of MANUAL_POLICY_ITEMS) {
70
+ items.push({ ...policyItem, status: "not-applicable" });
71
+ }
72
+ return items;
73
+ }
74
+ export function buildAndroidPlayReadinessChecklist(input) {
75
+ if (input.isNonAndroid) {
76
+ return {
77
+ applicable: false,
78
+ note: "This target was not detected as an Android project. Play-readiness checklist items are not applicable.",
79
+ items: itemsForLibrary().map((item) => ({ ...item, detail: "Target was not detected as an Android project." })),
80
+ };
81
+ }
82
+ if (input.isLibraryOnly) {
83
+ return {
84
+ applicable: false,
85
+ note: "This target is a pure Android library. Play-readiness checklist items are not applicable to a library artifact.",
86
+ items: itemsForLibrary(),
87
+ };
88
+ }
89
+ const { releaseMetadata, manifests } = input;
90
+ const hasLauncher = manifests.some((m) => Boolean(m.launcherActivityName));
91
+ const hasLabel = manifests.some((m) => Boolean(m.application.label));
92
+ const hasIcon = manifests.some((m) => Boolean(m.application.iconRef));
93
+ const hasAdaptiveIconEvidence = manifests.some((m) => Boolean(m.application.adaptiveIconEvidence));
94
+ const permissionsAvailable = manifests.length > 0;
95
+ const resolvedOrManual = (value, unresolvedFieldName) => {
96
+ if (value !== undefined && value !== null && value !== "")
97
+ return "confirmed";
98
+ const wasAttempted = releaseMetadata.unresolvedFields.some((f) => f.startsWith(`${unresolvedFieldName}:`));
99
+ return wasAttempted ? "unresolved" : "missing";
100
+ };
101
+ const items = [
102
+ {
103
+ id: "app-module-identified",
104
+ title: "Application module identified",
105
+ status: releaseMetadata.applicationModulePath ? "confirmed" : "missing",
106
+ detail: releaseMetadata.applicationModuleSelectionNote,
107
+ },
108
+ { id: "application-id-resolved", title: "applicationId resolved", status: resolvedOrManual(releaseMetadata.applicationId, "applicationId"), detail: releaseMetadata.applicationId ?? "Not statically resolved." },
109
+ { id: "namespace-resolved", title: "namespace resolved", status: resolvedOrManual(releaseMetadata.namespace, "namespace"), detail: releaseMetadata.namespace ?? "Not statically resolved." },
110
+ { id: "version-code-resolved", title: "versionCode resolved", status: resolvedOrManual(releaseMetadata.versionCode, "versionCode"), detail: String(releaseMetadata.versionCode ?? "Not statically resolved.") },
111
+ { id: "version-name-resolved", title: "versionName resolved", status: resolvedOrManual(releaseMetadata.versionName, "versionName"), detail: releaseMetadata.versionName ?? "Not statically resolved." },
112
+ { id: "min-sdk-resolved", title: "minSdk resolved", status: resolvedOrManual(releaseMetadata.minSdk, "minSdk"), detail: String(releaseMetadata.minSdk ?? "Not statically resolved.") },
113
+ { id: "target-sdk-resolved", title: "targetSdk resolved", status: resolvedOrManual(releaseMetadata.targetSdk, "targetSdk"), detail: String(releaseMetadata.targetSdk ?? "Not statically resolved.") },
114
+ { id: "compile-sdk-resolved", title: "compileSdk resolved", status: resolvedOrManual(releaseMetadata.compileSdk, "compileSdk"), detail: String(releaseMetadata.compileSdk ?? "Not statically resolved.") },
115
+ {
116
+ id: "target-sdk-policy-check",
117
+ title: "Current Google Play target SDK policy",
118
+ status: "manual-check-required",
119
+ detail: "Current Google Play target SDK policy must be checked separately — not evaluated by this tool.",
120
+ },
121
+ { id: "launcher-activity-identified", title: "Launcher activity identified", status: hasLauncher ? "confirmed" : "missing", detail: hasLauncher ? "Launcher activity found in a parsed manifest." : "No launcher activity evidence found." },
122
+ { id: "application-label-present", title: "Application label reference present", status: hasLabel ? "confirmed" : "missing", detail: hasLabel ? "android:label present." : "No android:label found on <application>." },
123
+ { id: "application-icon-present", title: "Application icon reference present", status: hasIcon ? "confirmed" : "missing", detail: hasIcon ? "android:icon present." : "No android:icon found on <application>." },
124
+ {
125
+ id: "adaptive-round-icon-evidence",
126
+ title: "Round/adaptive icon evidence",
127
+ status: hasAdaptiveIconEvidence ? "confirmed" : "unresolved",
128
+ detail: "Adaptive/round icon resources are not resolved by this tool; only direct manifest evidence, if any, is reflected here.",
129
+ },
130
+ {
131
+ id: "permissions-summary-available",
132
+ title: "Permissions summary available",
133
+ status: permissionsAvailable ? "confirmed" : "missing",
134
+ detail: "See the permissions audit section for the full declared-permission summary.",
135
+ },
136
+ ];
137
+ for (const policyItem of MANUAL_POLICY_ITEMS) {
138
+ items.push({ ...policyItem, status: "manual-check-required" });
139
+ }
140
+ return {
141
+ applicable: true,
142
+ note: "Bounded, local-evidence-only checklist. Confirms what static analysis found; every policy/manual item still requires human review before a Play Store submission. No live Google Play policy lookup was performed.",
143
+ items,
144
+ };
145
+ }
@@ -0,0 +1,315 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveLocalProjectTarget } from "../../../core/localProjectTarget.js";
4
+ import { detectAndroidProject } from "../detect/detectAndroidProject.js";
5
+ import { createAndroidProfile } from "../profile.js";
6
+ import { createAndroidTargetMetadata } from "../targetMetadata.js";
7
+ import { parseAllAndroidManifests } from "../manifest/parseAndroidManifest.js";
8
+ import { auditAndroidPermissions } from "../audit/permissionAudit.js";
9
+ import { auditAndroidExportedComponents } from "../audit/exportedComponentAudit.js";
10
+ import { auditAndroidIntentFilters } from "../audit/intentFilterAudit.js";
11
+ import { auditAndroidDeepLinks } from "../audit/deepLinkAudit.js";
12
+ import { readAndroidGradleMetadata } from "../gradle/readAndroidGradleMetadata.js";
13
+ import { buildAndroidGradleMetadataCheckResult } from "../gradle/gradleMetadataCheckResult.js";
14
+ import { buildAndroidReleaseMetadataSummary } from "../gradle/releaseMetadataSummary.js";
15
+ import { runOptionalGradleValidation } from "../gradle/validate/runOptionalGradleValidation.js";
16
+ import { captureTargetSnapshot, buildTargetMutationReport } from "../gradle/validate/targetMutation.js";
17
+ import { buildAndroidPlayReadinessChecklist } from "./playReadinessChecklist.js";
18
+ import { calculateAndroidVerdict } from "./androidVerdict.js";
19
+ import { buildAndroidDetectionCheckResult, buildAndroidManifestParsingCheckResult, buildAndroidTargetImmutabilityCheckResult, } from "./detectionAndImmutabilityCheckResults.js";
20
+ import { ANDROID_VALIDATION_ARTIFACT_TYPE, ANDROID_VALIDATION_SCHEMA_VERSION, } from "../validation/result.js";
21
+ import { auditAndroidNetworkSecurity } from "../advancedSecurity/networkSecurity/checkResult.js";
22
+ import { auditAndroidBackupConfiguration } from "../advancedSecurity/backupConfiguration/checkResult.js";
23
+ import { auditAndroidReleaseConfiguration } from "../advancedSecurity/releaseConfiguration/checkResult.js";
24
+ import { auditAndroidSecretCandidates } from "../advancedSecurity/secretCandidates/checkResult.js";
25
+ import { auditAndroidSigningConfiguration } from "../advancedSecurity/signingConfiguration/checkResult.js";
26
+ import { auditAndroidWebViewSecurity } from "../advancedSecurity/webview/checkResult.js";
27
+ import { auditAndroidFileProviders } from "../advancedSecurity/fileProvider/checkResult.js";
28
+ import { auditAndroidSensitiveStorage } from "../advancedSecurity/sensitiveStorage/checkResult.js";
29
+ import { auditAndroidSensitiveLogging } from "../advancedSecurity/sensitiveLogging/checkResult.js";
30
+ import { auditAndroidClipboardSecurity } from "../advancedSecurity/clipboard/checkResult.js";
31
+ import { auditAndroidFirebaseGoogleServices } from "../advancedSecurity/firebaseGoogle/checkResult.js";
32
+ import { runRequestedAndroidExternalTools } from "../advancedSecurity/externalTools/runRequestedAndroidExternalTools.js";
33
+ import { createRealExternalToolExecutor } from "../advancedSecurity/externalTools/runBoundedExternalTool.js";
34
+ import { createRealGradleCommandExecutor } from "../gradle/validate/executor.js";
35
+ function runIsolatedCheck(descriptor) {
36
+ try {
37
+ return descriptor.run();
38
+ }
39
+ catch (error) {
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ return {
42
+ id: descriptor.id,
43
+ category: descriptor.category,
44
+ title: descriptor.title,
45
+ status: "error",
46
+ requirementLevel: "optional",
47
+ ran: false,
48
+ skipped: false,
49
+ evidence: [],
50
+ findings: [],
51
+ warnings: [],
52
+ errors: [`Advanced check raised an unexpected error: ${message}`],
53
+ sourcePaths: [],
54
+ confidence: "unknown",
55
+ environmentRequirements: [],
56
+ candidateEvidence: [],
57
+ };
58
+ }
59
+ }
60
+ function buildAdvancedSecurityChecks(targetRoot, detection, manifestEntries, gradleModules) {
61
+ // Fixed order (agents.txt Batch 8 section 9.1) — never alphabetical, never
62
+ // derived from filesystem/object iteration.
63
+ const descriptors = [
64
+ { id: "android-network-security-audit", category: "android-network-security", title: "Android Network Security Config audit", run: () => auditAndroidNetworkSecurity(targetRoot, detection, manifestEntries) },
65
+ { id: "android-backup-configuration-audit", category: "android-backup-configuration", title: "Android backup configuration audit", run: () => auditAndroidBackupConfiguration(targetRoot, detection, manifestEntries) },
66
+ { id: "android-release-configuration-audit", category: "android-release-configuration", title: "Android release configuration audit", run: () => auditAndroidReleaseConfiguration(targetRoot, detection, manifestEntries, gradleModules) },
67
+ { id: "android-secret-candidates-audit", category: "android-secret-candidates", title: "Android hardcoded secret-candidate audit", run: () => auditAndroidSecretCandidates(targetRoot, detection) },
68
+ { id: "android-signing-configuration-audit", category: "android-signing-configuration", title: "Android signing configuration audit", run: () => auditAndroidSigningConfiguration(targetRoot, detection, gradleModules) },
69
+ { id: "android-webview-security-audit", category: "android-webview", title: "Android WebView security audit", run: () => auditAndroidWebViewSecurity(targetRoot, detection) },
70
+ { id: "android-file-provider-audit", category: "android-file-provider", title: "Android FileProvider security audit", run: () => auditAndroidFileProviders(targetRoot, detection, manifestEntries) },
71
+ { id: "android-sensitive-storage-audit", category: "android-sensitive-storage", title: "Android sensitive-storage audit", run: () => auditAndroidSensitiveStorage(targetRoot, detection) },
72
+ { id: "android-sensitive-logging-audit", category: "android-sensitive-logging", title: "Android sensitive-logging audit", run: () => auditAndroidSensitiveLogging(targetRoot, detection) },
73
+ { id: "android-clipboard-security-audit", category: "android-clipboard", title: "Android clipboard security audit", run: () => auditAndroidClipboardSecurity(targetRoot, detection) },
74
+ { id: "android-firebase-google-services-audit", category: "android-firebase-google-services", title: "Android Firebase/Google services audit", run: () => auditAndroidFirebaseGoogleServices(targetRoot, detection) },
75
+ ];
76
+ return descriptors.map(runIsolatedCheck);
77
+ }
78
+ // Memoizes by Gradle operation id: the fixed allowlist (operations.ts) maps
79
+ // each operation id to fixed args, so within one validation run the same
80
+ // operation id always means the same command — caching by id is safe and
81
+ // prevents running `lintDebug` twice when both --android-gradle-operations
82
+ // lint-debug and --android-external-tools android-lint are requested
83
+ // together (agents.txt Batch 8 section 10.6).
84
+ function createDedupingGradleExecutor(base) {
85
+ const cache = new Map();
86
+ return (plan) => {
87
+ const cached = cache.get(plan.operationId);
88
+ if (cached)
89
+ return cached;
90
+ const promise = base(plan);
91
+ cache.set(plan.operationId, promise);
92
+ return promise;
93
+ };
94
+ }
95
+ function readToolPackageMetadata(toolRoot) {
96
+ try {
97
+ const pkgRaw = fs.readFileSync(path.join(toolRoot, "package.json"), "utf8");
98
+ const pkg = JSON.parse(pkgRaw);
99
+ return { toolPackageName: pkg.name ?? "my-dev-kit-lab", toolPackageVersion: pkg.version ?? "unknown" };
100
+ }
101
+ catch {
102
+ return { toolPackageName: "my-dev-kit-lab", toolPackageVersion: "unknown" };
103
+ }
104
+ }
105
+ function hasGitDirectory(targetRoot) {
106
+ try {
107
+ return fs.existsSync(path.join(targetRoot, ".git"));
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
113
+ export async function validateAndroidTarget(options) {
114
+ const startedAt = new Date().toISOString();
115
+ const startedMs = Date.now();
116
+ // 1. Resolve the local target.
117
+ const local = resolveLocalProjectTarget(options.targetPath, options.toolRoot);
118
+ const resolvedTargetRoot = path.resolve(local.targetRoot);
119
+ const hasGit = local.hasGit || hasGitDirectory(resolvedTargetRoot);
120
+ // 2. Capture initial target snapshot (read-only).
121
+ const snapshotBefore = captureTargetSnapshot(resolvedTargetRoot, hasGit);
122
+ // 3. Detect and classify the Android project.
123
+ const detection = detectAndroidProject(resolvedTargetRoot);
124
+ // 5. Build Android target metadata / profile.
125
+ const androidProfile = createAndroidProfile({
126
+ detectionConfidence: detection.confidence,
127
+ evidence: detection.evidence,
128
+ unsupportedNotes: detection.warnings,
129
+ projectType: detection.projectKind,
130
+ projectSubtype: detection.uiToolkit,
131
+ supportedCapabilities: ["static-detection", "manifest-analysis", "gradle-metadata"],
132
+ });
133
+ const targetMetadata = createAndroidTargetMetadata({ local, androidProfile, detection });
134
+ // 6-7. Parse every discovered manifest independently (module/source-set
135
+ // association preserved by parseAllAndroidManifests itself).
136
+ const manifestEntries = parseAllAndroidManifests(resolvedTargetRoot, detection);
137
+ const manifests = manifestEntries.map((e) => e.manifest);
138
+ // 8-11. Four static audits — failure isolation: each returns its own
139
+ // AndroidCheckResult/findings independently of the others.
140
+ const permissionsCheck = auditAndroidPermissions(detection, manifestEntries);
141
+ const componentsCheck = auditAndroidExportedComponents(detection, manifestEntries);
142
+ const intentFiltersCheck = auditAndroidIntentFilters(detection, manifestEntries);
143
+ const deepLinksCheck = auditAndroidDeepLinks(detection, manifestEntries);
144
+ // 12-14. Static Gradle metadata + release metadata summary.
145
+ const gradle = readAndroidGradleMetadata(resolvedTargetRoot, detection);
146
+ const gradleMetadataCheck = buildAndroidGradleMetadataCheckResult(detection, gradle);
147
+ const releaseMetadata = buildAndroidReleaseMetadataSummary(detection, gradle);
148
+ // Detection + manifest-parsing checks (required families with no prior
149
+ // batch owner).
150
+ const detectionCheck = buildAndroidDetectionCheckResult(detection);
151
+ const manifestParsingCheck = buildAndroidManifestParsingCheckResult(detection, manifestEntries);
152
+ // Shared, deduping Gradle executor: reused for both --android-gradle-
153
+ // operations and (if android-lint is also externally requested) the
154
+ // Batch 7 Android Lint adapter, so `lintDebug` never runs twice for one
155
+ // validation run (agents.txt Batch 8 section 10.6).
156
+ const sharedGradleExecutor = createDedupingGradleExecutor(options.gradleExecutor ?? createRealGradleCommandExecutor());
157
+ // 15-16. Optional Gradle operations — never run unless explicitly requested.
158
+ const requestedOperationIds = options.requestedGradleOperationIds ?? [];
159
+ const gradleOperationResults = requestedOperationIds.length > 0
160
+ ? await runOptionalGradleValidation({
161
+ enabled: true,
162
+ targetRoot: resolvedTargetRoot,
163
+ detection,
164
+ operationIds: requestedOperationIds,
165
+ executor: sharedGradleExecutor,
166
+ allowWithoutTaskDiscovery: options.allowGradleWithoutTaskDiscovery,
167
+ platform: options.platform,
168
+ })
169
+ : [];
170
+ // v0.4.1 Batch 8 — eleven internal advanced Android checks, active by
171
+ // default (agents.txt Batch 8 section 9.1). Sequential, fixed order,
172
+ // isolated per-check via runIsolatedCheck. Never executes a process or
173
+ // touches the network.
174
+ const advancedSecurityChecks = buildAdvancedSecurityChecks(resolvedTargetRoot, detection, manifestEntries, gradle.modules);
175
+ // v0.4.1 Batch 8 — optional external-tool integration. Never executes
176
+ // unless requestedExternalToolIds is non-empty (same "no request, no
177
+ // execution" convention as Gradle operations). Wrapped in try/catch so a
178
+ // dispatcher-level failure (e.g. an unknown tool id slipping past CLI
179
+ // validation in a programmatic caller) cannot discard the checks already
180
+ // gathered above.
181
+ const requestedExternalToolIds = options.requestedExternalToolIds ?? [];
182
+ let externalToolChecks = [];
183
+ if (requestedExternalToolIds.length > 0) {
184
+ try {
185
+ externalToolChecks = await runRequestedAndroidExternalTools({
186
+ request: {
187
+ requestedTools: requestedExternalToolIds,
188
+ targetRoot: resolvedTargetRoot,
189
+ artifactRoot: options.externalToolArtifactRoot ?? path.join(options.toolRoot, "reports", "security", "external-tools"),
190
+ networkPolicy: options.externalNetworkPolicy ?? "deny",
191
+ },
192
+ detection,
193
+ executors: {
194
+ semgrep: options.externalToolExecutors?.semgrep ?? createRealExternalToolExecutor(),
195
+ osv: options.externalToolExecutors?.osv ?? createRealExternalToolExecutor(),
196
+ androidLint: options.externalToolExecutors?.androidLint ?? sharedGradleExecutor,
197
+ dependencyCheck: options.externalToolExecutors?.dependencyCheck ?? createRealExternalToolExecutor(),
198
+ },
199
+ discover: options.externalToolDiscover,
200
+ javaAvailable: options.javaAvailable,
201
+ lintTaskAvailable: options.lintTaskAvailable,
202
+ });
203
+ }
204
+ catch (error) {
205
+ const message = error instanceof Error ? error.message : String(error);
206
+ externalToolChecks = [
207
+ {
208
+ id: "android-external-tools-dispatch",
209
+ category: "android-semgrep",
210
+ title: "External security-tool dispatch",
211
+ status: "error",
212
+ requirementLevel: "optional",
213
+ ran: false,
214
+ skipped: false,
215
+ evidence: [],
216
+ findings: [],
217
+ warnings: [],
218
+ errors: [`External-tool dispatch raised an unexpected error: ${message}`],
219
+ sourcePaths: [],
220
+ confidence: "unknown",
221
+ environmentRequirements: [],
222
+ candidateEvidence: [],
223
+ },
224
+ ];
225
+ }
226
+ }
227
+ // 17. Target mutation evidence — captured after every process this run may
228
+ // have executed (Gradle operations, advanced checks, and external tools),
229
+ // so the static-only path also proves it made no changes.
230
+ const snapshotAfter = captureTargetSnapshot(resolvedTargetRoot, hasGit);
231
+ const targetMutation = buildTargetMutationReport(snapshotBefore, snapshotAfter);
232
+ const immutabilityCheck = buildAndroidTargetImmutabilityCheckResult(targetMutation);
233
+ // 18. Assemble checks/findings in deterministic order (agents.txt Batch 8
234
+ // section 11.1): the existing v0.4.0 check order is preserved exactly
235
+ // (ending with immutabilityCheck, as before), and the eleven advanced
236
+ // checks and any requested external tools are APPENDED after it — never
237
+ // interleaved into the existing order.
238
+ const checks = [
239
+ detectionCheck,
240
+ manifestParsingCheck,
241
+ permissionsCheck,
242
+ componentsCheck,
243
+ intentFiltersCheck,
244
+ deepLinksCheck,
245
+ gradleMetadataCheck,
246
+ ...gradleOperationResults,
247
+ immutabilityCheck,
248
+ ...advancedSecurityChecks,
249
+ ...externalToolChecks,
250
+ ];
251
+ const findingsById = new Map();
252
+ for (const check of checks) {
253
+ for (const finding of check.findings) {
254
+ // Exact-duplicate dedupe by id only (agents.txt section 10.4) —
255
+ // distinct manifests/components/permissions/deep-links always produce
256
+ // distinct ids (see androidFinding.ts's content-derived id builder),
257
+ // so this never collapses genuinely different evidence.
258
+ if (!findingsById.has(finding.id)) {
259
+ findingsById.set(finding.id, finding);
260
+ }
261
+ }
262
+ }
263
+ const findings = [...findingsById.values()].sort((a, b) => a.id.localeCompare(b.id));
264
+ const skippedChecks = checks.flatMap((c) => (c.skipInfo ? [c.skipInfo] : []));
265
+ const warnings = checks.flatMap((c) => c.warnings);
266
+ const errors = checks.flatMap((c) => c.errors);
267
+ const environmentLimitations = [...new Set(checks.flatMap((c) => c.environmentRequirements))];
268
+ // 19. Play-readiness checklist placeholders.
269
+ const isLibraryOnly = detection.projectKind === "library";
270
+ const isNonAndroid = detection.projectKind === "non-android";
271
+ const playReadiness = buildAndroidPlayReadinessChecklist({ isLibraryOnly, isNonAndroid, releaseMetadata, manifests });
272
+ // 20. Verdict.
273
+ const multipleApplicationModules = detection.applicationModules.length > 1;
274
+ const { verdict, recommendedNextStep, reasons } = calculateAndroidVerdict({
275
+ checks,
276
+ findings,
277
+ detection,
278
+ playReadiness,
279
+ targetMutation,
280
+ multipleApplicationModules,
281
+ });
282
+ const finishedAt = new Date().toISOString();
283
+ const durationMs = Date.now() - startedMs;
284
+ const { toolPackageName, toolPackageVersion } = readToolPackageMetadata(options.toolRoot);
285
+ return {
286
+ schemaVersion: ANDROID_VALIDATION_SCHEMA_VERSION,
287
+ artifactType: ANDROID_VALIDATION_ARTIFACT_TYPE,
288
+ tool: { toolRoot: path.resolve(options.toolRoot), toolPackageName, toolPackageVersion },
289
+ target: targetMetadata,
290
+ profile: androidProfile,
291
+ detection,
292
+ manifests,
293
+ gradle,
294
+ checks,
295
+ findings,
296
+ skippedChecks,
297
+ warnings,
298
+ errors,
299
+ releaseMetadataSummary: {
300
+ available: Boolean(releaseMetadata.applicationModulePath),
301
+ note: releaseMetadata.applicationModuleSelectionNote,
302
+ },
303
+ releaseMetadata,
304
+ playReadinessSummary: { available: playReadiness.applicable, note: playReadiness.note },
305
+ playReadiness,
306
+ verdict,
307
+ verdictReasons: reasons,
308
+ recommendedNextStep,
309
+ targetMutationEvidence: targetMutation,
310
+ reportReferences: [],
311
+ startedAt,
312
+ finishedAt,
313
+ durationMs,
314
+ };
315
+ }