@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
@@ -42,6 +42,7 @@ export const ARCHITECTURE_DRIFT_DETECTOR = {
42
42
  },
43
43
  run: (ctx) => {
44
44
  const issues = [];
45
+ const hasAndroidValidationSource = fs.existsSync(path.join(ctx.target.rootPath, "src", "mobile", "android", "validate", "validateAndroidTarget.ts"));
45
46
  const currentStateDocs = ctx.inventory.docsFiles.filter((f) => CURRENT_STATE_DOC_BASENAMES.has(path.basename(f.relativePath).toLowerCase()));
46
47
  for (const docFile of currentStateDocs) {
47
48
  const content = readBoundedFileText(ctx.target.rootPath, docFile.relativePath, docFile.sizeBytes, MAX_READ_BYTES);
@@ -61,7 +62,7 @@ export const ARCHITECTURE_DRIFT_DETECTOR = {
61
62
  const content = readBoundedFileText(ctx.target.rootPath, docFile.relativePath, docFile.sizeBytes, MAX_READ_BYTES);
62
63
  if (content === null)
63
64
  continue;
64
- issues.push(...findUnsupportedCurrentClaims(docFile.relativePath, content));
65
+ issues.push(...findUnsupportedCurrentClaims(docFile.relativePath, content, hasAndroidValidationSource));
65
66
  }
66
67
  return deduplicateIssuesById(issues);
67
68
  },
@@ -227,13 +228,15 @@ function findMajorDirsAbsentFromArchitectureDoc(ctx, docRelativePath, content) {
227
228
  }
228
229
  return issues;
229
230
  }
230
- function findUnsupportedCurrentClaims(docRelativePath, content) {
231
+ function findUnsupportedCurrentClaims(docRelativePath, content, hasAndroidValidationSource) {
231
232
  const issues = [];
232
233
  const subjects = [
233
234
  { id: "manual-pentest", label: "manual pentest", pattern: "manual pentest" },
234
235
  { id: "android-validation", label: "Android/mobile validation", pattern: "(?:android (?:validation|support))" },
235
236
  ];
236
237
  for (const subject of subjects) {
238
+ if (subject.id === "android-validation" && hasAndroidValidationSource)
239
+ continue;
237
240
  const pattern = currentClaimPatternFor(subject.pattern);
238
241
  const match = pattern.exec(content);
239
242
  if (!match)
@@ -67,6 +67,7 @@ export const SECURITY_VALIDATION_ASSUMPTION_ROT_DETECTOR = {
67
67
  const pkgVersion = ctx.sourceOfTruth.package?.version ?? null;
68
68
  const hasSecurityValidationSourceDir = ctx.sourceOfTruth.security.hasSecurityValidationSourceDir;
69
69
  const hasAttackScenariosDir = fs.existsSync(path.join(ctx.target.rootPath, "src", "securityValidation", "attackScenarios"));
70
+ const hasAndroidValidationSource = fs.existsSync(path.join(ctx.target.rootPath, "src", "mobile", "android", "validate", "validateAndroidTarget.ts"));
70
71
  for (const docFile of ctx.inventory.docsFiles) {
71
72
  const content = readBoundedFileText(ctx.target.rootPath, docFile.relativePath, docFile.sizeBytes, MAX_READ_BYTES);
72
73
  if (content === null)
@@ -75,7 +76,7 @@ export const SECURITY_VALIDATION_ASSUMPTION_ROT_DETECTOR = {
75
76
  issues.push(...findSkippedEqualsPassed(docFile.relativePath, content));
76
77
  issues.push(...findExhaustiveSecretClaim(docFile.relativePath, content));
77
78
  issues.push(...findCompleteNetworkIsolationClaim(docFile.relativePath, content));
78
- issues.push(...findUnsupportedCurrentClaims(docFile.relativePath, content));
79
+ issues.push(...findUnsupportedCurrentClaims(docFile.relativePath, content, hasAndroidValidationSource));
79
80
  if (pkgVersion)
80
81
  issues.push(...findStaleV021SecurityMention(docFile.relativePath, content, pkgVersion));
81
82
  if (hasSecurityValidationSourceDir && hasAttackScenariosDir) {
@@ -237,13 +238,15 @@ function findCompleteNetworkIsolationClaim(relativePath, content) {
237
238
  }),
238
239
  ];
239
240
  }
240
- function findUnsupportedCurrentClaims(relativePath, content) {
241
+ function findUnsupportedCurrentClaims(relativePath, content, hasAndroidValidationSource) {
241
242
  const issues = [];
242
243
  const subjects = [
243
244
  { id: "manual-pentest", label: "manual pentest", pattern: "manual pentest" },
244
245
  { id: "android-validation", label: "Android/mobile validation", pattern: "(?:android (?:validation|support)|mobile validation)" },
245
246
  ];
246
247
  for (const subject of subjects) {
248
+ if (subject.id === "android-validation" && hasAndroidValidationSource)
249
+ continue;
247
250
  const match = currentClaimPatternFor(subject.pattern).exec(content);
248
251
  if (!match)
249
252
  continue;
@@ -0,0 +1,300 @@
1
+ import path from "node:path";
2
+ import { makeAndroidFinding } from "../../audit/androidFinding.js";
3
+ import { buildAndroidSourceLocation } from "../sourceLocation.js";
4
+ import { makeCandidateEvidence } from "../candidateEvidence.js";
5
+ import { extractManifestBackupEvidence } from "./manifestEvidence.js";
6
+ function loc(targetRoot, absolutePath, position) {
7
+ return buildAndroidSourceLocation(targetRoot, absolutePath, position);
8
+ }
9
+ // A conservative, narrow "broad inclusion" signal: the entire root, or an
10
+ // entire sensitive domain with no path narrowing ("." — the whole domain).
11
+ function isBroadInclude(rule) {
12
+ if (rule.kind !== "include" || rule.malformed)
13
+ return false;
14
+ if (rule.domain === "root" || rule.domain === "device_root")
15
+ return true;
16
+ const sensitiveDomains = new Set(["sharedpref", "database", "external", "device_sharedpref", "device_database", "device_external"]);
17
+ return rule.domain !== undefined && sensitiveDomains.has(rule.domain) && rule.path === ".";
18
+ }
19
+ function analyzeRuleEntries(rules, ruleId, scopeLabel, sourceAbsolutePath, targetRoot, modulePath, sourceRelativePath) {
20
+ const candidates = [];
21
+ const evidenceText = [];
22
+ let hasBroadInclude = false;
23
+ for (const rule of rules) {
24
+ const location = loc(targetRoot, sourceAbsolutePath, { line: rule.location.line, column: rule.location.column });
25
+ if (rule.malformed) {
26
+ candidates.push(makeCandidateEvidence({
27
+ ruleId,
28
+ category: "android-backup-configuration",
29
+ confidence: "low",
30
+ modulePath,
31
+ location,
32
+ summary: `Malformed ${scopeLabel} rule: ${rule.malformedReason}`,
33
+ rawValue: `${rule.domainRaw ?? ""} ${rule.path ?? ""}`.trim(),
34
+ resolutionState: "malformed",
35
+ }));
36
+ continue;
37
+ }
38
+ if (rule.domain === undefined) {
39
+ candidates.push(makeCandidateEvidence({
40
+ ruleId,
41
+ category: "android-backup-configuration",
42
+ confidence: "low",
43
+ modulePath,
44
+ location,
45
+ summary: `Unsupported backup domain value in ${scopeLabel}: "${rule.domainRaw}"`,
46
+ rawValue: rule.domainRaw,
47
+ resolutionState: "unsupported",
48
+ }));
49
+ continue;
50
+ }
51
+ if (rule.kind === "exclude") {
52
+ evidenceText.push(`${sourceRelativePath}: ${scopeLabel} restrictive exclusion — domain=${rule.domain}, path=${rule.path}`);
53
+ continue;
54
+ }
55
+ if (isBroadInclude(rule)) {
56
+ hasBroadInclude = true;
57
+ candidates.push(makeCandidateEvidence({
58
+ ruleId,
59
+ category: "android-backup-configuration",
60
+ confidence: "medium",
61
+ modulePath,
62
+ location,
63
+ summary: `Broad ${scopeLabel} inclusion: domain=${rule.domain}, path=${rule.path}`,
64
+ rawValue: `${rule.domain}:${rule.path}`,
65
+ resolutionState: "resolved",
66
+ staticAnalysisLimitations: [
67
+ "Domain/path naming is conservative evidence only; it does not prove the included data is actually sensitive or that a backup is ever performed.",
68
+ ],
69
+ }));
70
+ }
71
+ }
72
+ return { candidates, evidenceText, hasBroadInclude };
73
+ }
74
+ function analyzeBackupRulesParseResult(parseResult, ruleId, sourceRelativePath, targetRoot, modulePath) {
75
+ const sourceAbsolutePath = path.join(targetRoot, sourceRelativePath);
76
+ if (parseResult.state === "malformed-xml") {
77
+ return {
78
+ candidates: [
79
+ makeCandidateEvidence({
80
+ ruleId,
81
+ category: "android-backup-configuration",
82
+ confidence: "medium",
83
+ modulePath,
84
+ location: loc(targetRoot, sourceAbsolutePath, parseResult.location ? { line: parseResult.location.line, column: parseResult.location.column } : undefined),
85
+ summary: `Referenced backup rules file is malformed XML: ${parseResult.reason}`,
86
+ rawValue: parseResult.reason,
87
+ resolutionState: "malformed",
88
+ }),
89
+ ],
90
+ evidenceText: [],
91
+ hasBroadCloudOrLegacyInclude: false,
92
+ };
93
+ }
94
+ if (parseResult.state === "unsupported-root") {
95
+ return {
96
+ candidates: [
97
+ makeCandidateEvidence({
98
+ ruleId,
99
+ category: "android-backup-configuration",
100
+ confidence: "medium",
101
+ modulePath,
102
+ location: loc(targetRoot, sourceAbsolutePath),
103
+ summary: `Referenced XML file has an unsupported root element <${parseResult.rootTagName}>`,
104
+ rawValue: parseResult.rootTagName,
105
+ resolutionState: "unsupported",
106
+ }),
107
+ ],
108
+ evidenceText: [],
109
+ hasBroadCloudOrLegacyInclude: false,
110
+ };
111
+ }
112
+ if (parseResult.state === "parsed-full-backup-content") {
113
+ const result = analyzeRuleEntries(parseResult.model.rules, ruleId, "full-backup-content", sourceAbsolutePath, targetRoot, modulePath, sourceRelativePath);
114
+ return { candidates: result.candidates, evidenceText: result.evidenceText, hasBroadCloudOrLegacyInclude: result.hasBroadInclude };
115
+ }
116
+ // parsed-data-extraction-rules
117
+ const cloudResult = parseResult.model.cloudBackup
118
+ ? analyzeRuleEntries(parseResult.model.cloudBackup.rules, ruleId, "cloud-backup", sourceAbsolutePath, targetRoot, modulePath, sourceRelativePath)
119
+ : { candidates: [], evidenceText: [], hasBroadInclude: false };
120
+ const transferResult = parseResult.model.deviceTransfer
121
+ ? analyzeRuleEntries(parseResult.model.deviceTransfer.rules, ruleId, "device-transfer", sourceAbsolutePath, targetRoot, modulePath, sourceRelativePath)
122
+ : { candidates: [], evidenceText: [], hasBroadInclude: false };
123
+ // Device-transfer broadness stays a review candidate only — it is local
124
+ // device-to-device transfer, not cloud exfiltration, so it never
125
+ // contributes to the allowBackup-level "major" escalation.
126
+ return {
127
+ candidates: [...cloudResult.candidates, ...transferResult.candidates],
128
+ evidenceText: [...cloudResult.evidenceText, ...transferResult.evidenceText],
129
+ hasBroadCloudOrLegacyInclude: cloudResult.hasBroadInclude,
130
+ };
131
+ }
132
+ function analyzeXmlReferenceEvidence(evidence, ruleId, targetRoot, manifestAbsolutePath, modulePath, applicationLocation) {
133
+ switch (evidence.state) {
134
+ case "absent":
135
+ return { candidates: [], evidenceText: [], hasBroadInclude: false };
136
+ case "resolved": {
137
+ const result = analyzeBackupRulesParseResult(evidence.parseResult, ruleId, evidence.candidate.relativePath, targetRoot, modulePath);
138
+ return { candidates: result.candidates, evidenceText: result.evidenceText, hasBroadInclude: result.hasBroadCloudOrLegacyInclude };
139
+ }
140
+ case "ambiguous": {
141
+ const candidates = [
142
+ makeCandidateEvidence({
143
+ ruleId,
144
+ category: "android-backup-configuration",
145
+ confidence: "low",
146
+ modulePath,
147
+ location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
148
+ summary: `Backup rules reference "${evidence.raw}" resolved to ${evidence.candidates.length} ambiguous candidates across source sets; no candidate was arbitrarily selected`,
149
+ rawValue: evidence.raw,
150
+ resolutionState: "unresolved",
151
+ }),
152
+ ];
153
+ let hasBroadInclude = false;
154
+ for (const candidate of evidence.candidates) {
155
+ const result = analyzeBackupRulesParseResult(candidate.parseResult, ruleId, candidate.candidate.relativePath, targetRoot, modulePath);
156
+ candidates.push(...result.candidates);
157
+ hasBroadInclude = hasBroadInclude || result.hasBroadCloudOrLegacyInclude;
158
+ }
159
+ return { candidates, evidenceText: [], hasBroadInclude };
160
+ }
161
+ case "missing":
162
+ return {
163
+ candidates: [
164
+ makeCandidateEvidence({
165
+ ruleId,
166
+ category: "android-backup-configuration",
167
+ confidence: "low",
168
+ modulePath,
169
+ location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
170
+ summary: `Backup rules reference "${evidence.raw}" did not resolve to any file in the searched source sets`,
171
+ rawValue: evidence.raw,
172
+ resolutionState: "missing",
173
+ staticAnalysisLimitations: ["A missing referenced rules file is not automatically a vulnerability; runtime behavior cannot be proven statically."],
174
+ }),
175
+ ],
176
+ evidenceText: [],
177
+ hasBroadInclude: false,
178
+ };
179
+ case "placeholder":
180
+ return {
181
+ candidates: [
182
+ makeCandidateEvidence({
183
+ ruleId,
184
+ category: "android-backup-configuration",
185
+ confidence: "low",
186
+ modulePath,
187
+ location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
188
+ summary: "Backup rules reference is an unresolved build-time placeholder",
189
+ rawValue: evidence.raw,
190
+ resolutionState: "unresolved",
191
+ }),
192
+ ],
193
+ evidenceText: [],
194
+ hasBroadInclude: false,
195
+ };
196
+ case "malformed":
197
+ return {
198
+ candidates: [
199
+ makeCandidateEvidence({
200
+ ruleId,
201
+ category: "android-backup-configuration",
202
+ confidence: "low",
203
+ modulePath,
204
+ location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
205
+ summary: `Backup rules reference is malformed: ${evidence.reason}`,
206
+ rawValue: evidence.raw,
207
+ resolutionState: "malformed",
208
+ }),
209
+ ],
210
+ evidenceText: [],
211
+ hasBroadInclude: false,
212
+ };
213
+ case "unsupported-type":
214
+ case "package-qualified":
215
+ case "module-unknown":
216
+ return {
217
+ candidates: [
218
+ makeCandidateEvidence({
219
+ ruleId,
220
+ category: "android-backup-configuration",
221
+ confidence: "low",
222
+ modulePath,
223
+ location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
224
+ summary: evidence.state === "module-unknown"
225
+ ? "Backup rules reference could not be resolved because this manifest's module could not be determined"
226
+ : `Backup rules reference is not a resolvable target-contained @xml/... resource (${evidence.state})`,
227
+ rawValue: evidence.raw,
228
+ resolutionState: "unsupported",
229
+ }),
230
+ ],
231
+ evidenceText: [],
232
+ hasBroadInclude: false,
233
+ };
234
+ }
235
+ }
236
+ // Analyzes one already-parsed manifest's backup/data-extraction evidence.
237
+ // Standalone until a later integration batch.
238
+ export function analyzeManifestBackupConfiguration(targetRoot, entry) {
239
+ const evidence = extractManifestBackupEvidence(targetRoot, entry);
240
+ const candidates = [];
241
+ const findings = [];
242
+ const evidenceText = [];
243
+ const modulePath = entry.modulePath;
244
+ const manifestAbsolutePath = path.join(targetRoot, entry.manifestPath);
245
+ const applicationLocation = evidence.applicationLocation;
246
+ let hasBroadInclude = false;
247
+ // fullBackupContent — legacy literal states never touch the resource
248
+ // resolver/parser (there is no file to analyze).
249
+ const fullBackupContent = evidence.fullBackupContent;
250
+ if (fullBackupContent.state === "legacy-literal-true" || fullBackupContent.state === "legacy-literal-false") {
251
+ evidenceText.push(`${entry.manifestPath}: fullBackupContent is a legacy literal (${fullBackupContent.raw})`);
252
+ }
253
+ else {
254
+ const result = analyzeXmlReferenceEvidence(fullBackupContent, "android-backup-full-backup-content", targetRoot, manifestAbsolutePath, modulePath, applicationLocation);
255
+ candidates.push(...result.candidates);
256
+ evidenceText.push(...result.evidenceText);
257
+ hasBroadInclude = hasBroadInclude || result.hasBroadInclude;
258
+ }
259
+ const dataExtractionResult = analyzeXmlReferenceEvidence(evidence.dataExtractionRules, "android-backup-data-extraction-rules", targetRoot, manifestAbsolutePath, modulePath, applicationLocation);
260
+ candidates.push(...dataExtractionResult.candidates);
261
+ evidenceText.push(...dataExtractionResult.evidenceText);
262
+ hasBroadInclude = hasBroadInclude || dataExtractionResult.hasBroadInclude;
263
+ // allowBackup analysis — the only source of an actual SecurityFinding in
264
+ // this analyzer (agents.txt Batch 3 section 9.7/9.17).
265
+ if (evidence.allowBackup.state === "explicit-true") {
266
+ findings.push(makeAndroidFinding({
267
+ ruleId: "android-backup-allow-backup",
268
+ title: hasBroadInclude
269
+ ? "Application allows backup with a broad backup-rule inclusion"
270
+ : "Application manifest explicitly permits backup (android:allowBackup=\"true\")",
271
+ severity: hasBroadInclude ? "major" : "minor",
272
+ confidence: hasBroadInclude ? "high" : "medium",
273
+ description: hasBroadInclude
274
+ ? "The manifest sets android:allowBackup=\"true\" and the resolved backup rules include a broad root/domain inclusion with no narrowing. This is high-confidence static evidence of a broad backup surface; it does not prove any specific file is actually backed up or that backup ever runs."
275
+ : "The manifest sets android:allowBackup=\"true\" with no broad-rule evidence found. This alone is not a vulnerability — it is the platform's own default behavior when the attribute is unset on old targetSdk versions, and is a common, legitimate configuration.",
276
+ manifestPath: entry.manifestPath,
277
+ location: applicationLocation,
278
+ evidenceDetails: [`source=manifest-attribute`, `broadRuleEvidence=${hasBroadInclude}`],
279
+ recommendation: hasBroadInclude
280
+ ? "Narrow the backup rules to exclude sensitive domains, or set android:allowBackup=\"false\" if backup is not required."
281
+ : "Review whether backup should remain enabled and whether backup rules narrow the included data appropriately.",
282
+ }));
283
+ }
284
+ else if (evidence.allowBackup.state === "malformed") {
285
+ candidates.push(makeCandidateEvidence({
286
+ ruleId: "android-backup-allow-backup",
287
+ category: "android-backup-configuration",
288
+ confidence: "low",
289
+ modulePath,
290
+ location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
291
+ summary: "android:allowBackup has an unresolved or malformed value",
292
+ rawValue: evidence.allowBackup.raw,
293
+ resolutionState: "malformed",
294
+ }));
295
+ }
296
+ else if (evidence.allowBackup.state === "explicit-false") {
297
+ evidenceText.push(`${entry.manifestPath}: allowBackup is explicitly false (restrictive)`);
298
+ }
299
+ return { evidence, candidates, findings, evidenceText };
300
+ }
@@ -0,0 +1,41 @@
1
+ import { buildAndroidManifestCheckResult } from "../../audit/checkResultBuilder.js";
2
+ import { sortCandidateEvidence } from "../ordering.js";
3
+ import { analyzeManifestBackupConfiguration } from "./analyzeBackupConfiguration.js";
4
+ // ---------------------------------------------------------------------------
5
+ // v0.4.1 Batch 3 — standalone Android backup/data-extraction check result.
6
+ //
7
+ // Reuses the existing manifest-audit status-decision policy
8
+ // (buildAndroidManifestCheckResult), mirroring Batch 2's checkResult.ts.
9
+ // Standalone: not called from validateAndroidTarget / any active
10
+ // orchestration, not rendered in reports, no CLI effect. Batch 8 integrates.
11
+ // ---------------------------------------------------------------------------
12
+ export const ANDROID_BACKUP_CONFIGURATION_AUDIT_CHECK_ID = "android-backup-configuration-audit";
13
+ export function auditAndroidBackupConfiguration(targetRoot, detection, manifests) {
14
+ const findings = [];
15
+ const candidateEvidence = [];
16
+ const evidence = [];
17
+ const warnings = [];
18
+ for (const entry of manifests) {
19
+ const result = analyzeManifestBackupConfiguration(targetRoot, entry);
20
+ findings.push(...result.findings);
21
+ candidateEvidence.push(...result.candidates);
22
+ evidence.push(`${entry.manifestPath}: allowBackup=${result.evidence.allowBackup.state}, fullBackupContent=${result.evidence.fullBackupContent.state}, dataExtractionRules=${result.evidence.dataExtractionRules.state}`, ...result.evidenceText);
23
+ }
24
+ const base = buildAndroidManifestCheckResult({
25
+ id: ANDROID_BACKUP_CONFIGURATION_AUDIT_CHECK_ID,
26
+ category: "android-backup-configuration",
27
+ title: "Android backup and data-extraction configuration audit",
28
+ detection,
29
+ manifests,
30
+ findings,
31
+ evidence,
32
+ warnings: [
33
+ ...warnings,
34
+ "Static analysis only: does not evaluate Gradle manifest merging, Android resource overlays, or whether a backup actually occurs at runtime.",
35
+ ],
36
+ });
37
+ return {
38
+ ...base,
39
+ candidateEvidence: sortCandidateEvidence(candidateEvidence),
40
+ };
41
+ }
@@ -0,0 +1,8 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 3 — public exports for the standalone Android backup/data-
3
+ // extraction audit. Mirrors Batch 2's networkSecurity/index.ts convention.
4
+ // ---------------------------------------------------------------------------
5
+ export { parseBackupRules } from "./parseBackupRules.js";
6
+ export { extractManifestBackupEvidence } from "./manifestEvidence.js";
7
+ export { analyzeManifestBackupConfiguration } from "./analyzeBackupConfiguration.js";
8
+ export { auditAndroidBackupConfiguration, ANDROID_BACKUP_CONFIGURATION_AUDIT_CHECK_ID } from "./checkResult.js";
@@ -0,0 +1,102 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { parseAndroidResourceReference } from "../resourceReference.js";
4
+ import { resolveAndroidXmlResourceReference } from "../resourceResolution.js";
5
+ import { parseBackupRules } from "./parseBackupRules.js";
6
+ function classifyBooleanAttribute(value, raw) {
7
+ if (value === true)
8
+ return { state: "explicit-true", raw: raw ?? "true" };
9
+ if (value === false)
10
+ return { state: "explicit-false", raw: raw ?? "false" };
11
+ if (raw !== undefined)
12
+ return { state: "malformed", raw };
13
+ return { state: "missing" };
14
+ }
15
+ function parseBackupRulesFile(absolutePath) {
16
+ let xmlText;
17
+ try {
18
+ xmlText = readFileSync(absolutePath, "utf8");
19
+ }
20
+ catch (error) {
21
+ return { state: "malformed-xml", reason: error instanceof Error ? error.message : "Unable to read the resolved backup rules file" };
22
+ }
23
+ return parseBackupRules(xmlText);
24
+ }
25
+ function classifyResolution(raw, resolution, targetRoot) {
26
+ switch (resolution.state) {
27
+ case "resolved": {
28
+ const candidate = resolution.candidates[0];
29
+ return {
30
+ state: "resolved",
31
+ raw,
32
+ modulePath: resolution.modulePath,
33
+ candidate,
34
+ parseResult: parseBackupRulesFile(path.join(targetRoot, candidate.relativePath)),
35
+ };
36
+ }
37
+ case "ambiguous":
38
+ return {
39
+ state: "ambiguous",
40
+ raw,
41
+ modulePath: resolution.modulePath,
42
+ candidates: resolution.candidates.map((candidate) => ({
43
+ candidate,
44
+ parseResult: parseBackupRulesFile(path.join(targetRoot, candidate.relativePath)),
45
+ })),
46
+ };
47
+ case "missing":
48
+ return { state: "missing", raw, modulePath: resolution.modulePath, searchedSourceSets: resolution.searchedSourceSets };
49
+ case "malformed-reference":
50
+ return { state: "malformed", raw, reason: resolution.reason };
51
+ case "unsupported-reference":
52
+ // Unreachable in practice — see Batch 2's manifestEvidence.ts for the
53
+ // identical rationale (this function only calls the resolver once a
54
+ // "parsed" xml-type reference is confirmed).
55
+ return { state: "unsupported-type", raw, resourceType: "type" in resolution.parsed ? resolution.parsed.type : "unknown" };
56
+ }
57
+ }
58
+ function classifyXmlReference(targetRoot, entry, raw) {
59
+ if (raw === undefined)
60
+ return { state: "absent" };
61
+ const parsedRef = parseAndroidResourceReference(raw);
62
+ if (parsedRef.state === "placeholder")
63
+ return { state: "placeholder", raw };
64
+ if (parsedRef.state === "malformed")
65
+ return { state: "malformed", raw, reason: parsedRef.reason };
66
+ if (parsedRef.state === "empty")
67
+ return { state: "malformed", raw, reason: "Reference is empty" };
68
+ if (parsedRef.state === "unsupported-type")
69
+ return { state: "unsupported-type", raw, resourceType: parsedRef.type };
70
+ if (parsedRef.state === "package-qualified")
71
+ return { state: "package-qualified", raw, packageQualifier: parsedRef.packageQualifier };
72
+ if (entry.modulePath === undefined)
73
+ return { state: "module-unknown", raw };
74
+ const moduleAbsolutePath = path.join(targetRoot, entry.modulePath);
75
+ const resolution = resolveAndroidXmlResourceReference(targetRoot, moduleAbsolutePath, raw);
76
+ return classifyResolution(raw, resolution, targetRoot);
77
+ }
78
+ function classifyFullBackupContent(targetRoot, entry) {
79
+ const raw = entry.manifest.application.fullBackupContentRef;
80
+ if (raw === "true")
81
+ return { state: "legacy-literal-true", raw };
82
+ if (raw === "false")
83
+ return { state: "legacy-literal-false", raw };
84
+ return classifyXmlReference(targetRoot, entry, raw);
85
+ }
86
+ // Extracts and classifies backup/data-extraction/debuggable/testOnly
87
+ // evidence for one already-parsed manifest. `targetRoot` must be the
88
+ // absolute Android target root.
89
+ export function extractManifestBackupEvidence(targetRoot, entry) {
90
+ const app = entry.manifest.application;
91
+ return {
92
+ manifestPath: entry.manifestPath,
93
+ modulePath: entry.modulePath,
94
+ sourceSetKind: entry.sourceSetKind,
95
+ applicationLocation: app.location,
96
+ allowBackup: classifyBooleanAttribute(app.allowBackup, app.allowBackupRaw),
97
+ debuggable: classifyBooleanAttribute(app.debuggable, app.debuggableRaw),
98
+ testOnly: classifyBooleanAttribute(app.testOnly, app.testOnlyRaw),
99
+ fullBackupContent: classifyFullBackupContent(targetRoot, entry),
100
+ dataExtractionRules: classifyXmlReference(targetRoot, entry, app.dataExtractionRulesRef),
101
+ };
102
+ }
@@ -0,0 +1,110 @@
1
+ import { findChildren, parseXmlDocument } from "../../manifest/xml/parseXml.js";
2
+ import { BACKUP_DOMAIN_VALUES, MAX_BACKUP_RULES_PER_SCOPE, } from "./types.js";
3
+ // ---------------------------------------------------------------------------
4
+ // v0.4.1 Batch 3 — dependency-free parser for the legacy
5
+ // <full-backup-content> and newer <data-extraction-rules> XML formats.
6
+ //
7
+ // Builds on the existing bounded XML tree parser
8
+ // (src/mobile/android/manifest/xml/parseXml.ts), mirroring Batch 2's
9
+ // parseNetworkSecurityConfig.ts layering — no second XML tokenizer. Parses
10
+ // exactly one already-resolved, target-contained file; never merges files,
11
+ // never evaluates resource overlays, never invokes Gradle/Android SDK/
12
+ // network. Malformed XML or an unsupported root never throws.
13
+ // ---------------------------------------------------------------------------
14
+ function getUnprefixedAttribute(element, localName) {
15
+ return element.attributes.find((attr) => attr.prefix === undefined && attr.localName === localName)?.value;
16
+ }
17
+ function parseBooleanAttribute(element, localName) {
18
+ const raw = getUnprefixedAttribute(element, localName);
19
+ if (raw === undefined)
20
+ return {};
21
+ if (raw === "true")
22
+ return { value: true, raw };
23
+ if (raw === "false")
24
+ return { value: false, raw };
25
+ return { raw };
26
+ }
27
+ function isBackupDomainValue(value) {
28
+ return BACKUP_DOMAIN_VALUES.includes(value);
29
+ }
30
+ function parseRuleEntry(kind, element) {
31
+ const domainRaw = getUnprefixedAttribute(element, "domain");
32
+ const path = getUnprefixedAttribute(element, "path");
33
+ const requireFlagsRaw = getUnprefixedAttribute(element, "requireFlags");
34
+ const domain = domainRaw !== undefined && isBackupDomainValue(domainRaw) ? domainRaw : undefined;
35
+ const malformed = domainRaw === undefined || path === undefined || path.length === 0;
36
+ const malformedReason = domainRaw === undefined ? "Missing domain attribute" : path === undefined || path.length === 0 ? "Missing or empty path attribute" : undefined;
37
+ return {
38
+ kind,
39
+ domainRaw,
40
+ domain,
41
+ path,
42
+ requireFlagsRaw,
43
+ location: element.location,
44
+ malformed,
45
+ malformedReason,
46
+ };
47
+ }
48
+ function parseRuleEntries(scopeElement) {
49
+ const includeEntries = findChildren(scopeElement, "include").map((el) => parseRuleEntry("include", el));
50
+ const excludeEntries = findChildren(scopeElement, "exclude").map((el) => parseRuleEntry("exclude", el));
51
+ // Preserves document order (include/exclude interleaving is semantically
52
+ // meaningful in the real format) rather than grouping by kind — includes
53
+ // are collected first only because findChildren is kind-specific; the
54
+ // caller should not assume this array reflects source order for mixed
55
+ // kinds beyond what findChildren naturally preserves per kind.
56
+ return [...includeEntries, ...excludeEntries].slice(0, MAX_BACKUP_RULES_PER_SCOPE);
57
+ }
58
+ function parseFullBackupContent(root, warnings) {
59
+ const knownChildren = new Set(["include", "exclude"]);
60
+ const unsupportedChildren = root.children.filter((child) => !knownChildren.has(child.localName)).map((child) => `<${child.tagName}> at line ${child.location.line}`);
61
+ const rules = parseRuleEntries(root);
62
+ if (rules.length >= MAX_BACKUP_RULES_PER_SCOPE) {
63
+ warnings.push(`<full-backup-content> rule count reached the bounded maximum of ${MAX_BACKUP_RULES_PER_SCOPE}; further rules were not processed.`);
64
+ }
65
+ return { rules, unsupportedChildren };
66
+ }
67
+ function parseDataExtractionRuleSet(element) {
68
+ const disableResult = parseBooleanAttribute(element, "disableIfNoEncryptionCapabilities");
69
+ return {
70
+ disableIfNoEncryptionCapabilities: disableResult.value,
71
+ disableIfNoEncryptionCapabilitiesRaw: disableResult.raw,
72
+ rules: parseRuleEntries(element),
73
+ location: element.location,
74
+ };
75
+ }
76
+ function parseDataExtractionRules(root, warnings) {
77
+ const knownChildren = new Set(["cloud-backup", "device-transfer"]);
78
+ const unsupportedChildren = root.children.filter((child) => !knownChildren.has(child.localName)).map((child) => `<${child.tagName}> at line ${child.location.line}`);
79
+ const cloudBackupEl = findChildren(root, "cloud-backup")[0];
80
+ if (findChildren(root, "cloud-backup").length > 1) {
81
+ warnings.push("Multiple <cloud-backup> elements found; only the first was used.");
82
+ }
83
+ const deviceTransferEl = findChildren(root, "device-transfer")[0];
84
+ if (findChildren(root, "device-transfer").length > 1) {
85
+ warnings.push("Multiple <device-transfer> elements found; only the first was used.");
86
+ }
87
+ return {
88
+ cloudBackup: cloudBackupEl ? parseDataExtractionRuleSet(cloudBackupEl) : undefined,
89
+ deviceTransfer: deviceTransferEl ? parseDataExtractionRuleSet(deviceTransferEl) : undefined,
90
+ unsupportedChildren,
91
+ };
92
+ }
93
+ // Parses either legacy full-backup-content or data-extraction-rules XML
94
+ // text, selecting the semantic model by the document's actual root element.
95
+ // Never throws for ordinary malformed external input.
96
+ export function parseBackupRules(xmlText) {
97
+ const parsed = parseXmlDocument(xmlText);
98
+ if (!parsed.ok) {
99
+ return { state: "malformed-xml", reason: parsed.error.message, location: parsed.error.location };
100
+ }
101
+ const root = parsed.root;
102
+ const warnings = parsed.warnings.map((w) => `${w.message} (line ${w.location.line})`);
103
+ if (root.localName === "full-backup-content") {
104
+ return { state: "parsed-full-backup-content", model: parseFullBackupContent(root, warnings), warnings };
105
+ }
106
+ if (root.localName === "data-extraction-rules") {
107
+ return { state: "parsed-data-extraction-rules", model: parseDataExtractionRules(root, warnings), warnings };
108
+ }
109
+ return { state: "unsupported-root", rootTagName: root.tagName };
110
+ }
@@ -0,0 +1,27 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 3 — bounded backup/data-extraction XML parse-tree model.
3
+ //
4
+ // Covers both the legacy <full-backup-content> format (API <=30, still
5
+ // widely used) and the newer <data-extraction-rules> format (API 31+,
6
+ // separates <cloud-backup> from <device-transfer>). Local to Android
7
+ // advanced security, mirroring Batch 2's networkSecurity/types.ts pattern —
8
+ // not pushed into the generic XML tree model. Locations are line/column
9
+ // only; file/module/source-set identity is attached by the caller once a
10
+ // file is resolved (see manifestEvidence.ts).
11
+ // ---------------------------------------------------------------------------
12
+ export const BACKUP_DOMAIN_VALUES = [
13
+ "root",
14
+ "file",
15
+ "database",
16
+ "sharedpref",
17
+ "external",
18
+ "device_root",
19
+ "device_file",
20
+ "device_database",
21
+ "device_sharedpref",
22
+ "device_external",
23
+ ];
24
+ // Defensive bound on the number of rule entries processed from one file —
25
+ // mirrors Batch 2's MAX_DOMAIN_CONFIG_DEPTH bound philosophy (never let a
26
+ // pathological fixture/target cause unbounded work).
27
+ export const MAX_BACKUP_RULES_PER_SCOPE = 500;