@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,118 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { relativeWithinRoot, resolveWithinRoot } from "../../../core/pathSafety.js";
4
+ import { parseAndroidResourceReference } from "./resourceReference.js";
5
+ const DEFAULT_SOURCE_SETS = ["main", "debug", "release"];
6
+ // Returns true only when `candidatePath` both lexically resolves within root
7
+ // (resolveWithinRoot) AND, if it is or passes through a symlink, its real
8
+ // path also resolves within root. Never throws — a filesystem error (e.g.
9
+ // broken symlink) is treated as "does not safely resolve".
10
+ function isSafelyContainedExistingFile(root, candidatePath) {
11
+ try {
12
+ resolveWithinRoot(root, candidatePath);
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ let stat;
18
+ try {
19
+ stat = fs.lstatSync(candidatePath);
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ if (!stat.isFile() && !stat.isSymbolicLink()) {
25
+ return false;
26
+ }
27
+ if (stat.isSymbolicLink()) {
28
+ let realPath;
29
+ try {
30
+ realPath = fs.realpathSync(candidatePath);
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ try {
36
+ resolveWithinRoot(root, realPath);
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ try {
42
+ if (!fs.statSync(realPath).isFile())
43
+ return false;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ return true;
50
+ }
51
+ function listCandidateSourceSets(root, modulePath) {
52
+ const srcDir = path.join(modulePath, "src");
53
+ let entries;
54
+ try {
55
+ entries = fs.readdirSync(srcDir);
56
+ }
57
+ catch {
58
+ return [...DEFAULT_SOURCE_SETS];
59
+ }
60
+ const discovered = new Set(DEFAULT_SOURCE_SETS);
61
+ for (const entry of entries) {
62
+ const entryPath = path.join(srcDir, entry);
63
+ try {
64
+ resolveWithinRoot(root, entryPath);
65
+ }
66
+ catch {
67
+ continue;
68
+ }
69
+ let stat;
70
+ try {
71
+ stat = fs.lstatSync(entryPath);
72
+ }
73
+ catch {
74
+ continue;
75
+ }
76
+ if (stat.isDirectory() || stat.isSymbolicLink()) {
77
+ discovered.add(entry);
78
+ }
79
+ }
80
+ // Deterministic order: independent of readdirSync's filesystem-dependent
81
+ // enumeration order.
82
+ return [...discovered].sort((a, b) => a.localeCompare(b));
83
+ }
84
+ export function resolveAndroidXmlResourceReference(root, modulePath, rawReference) {
85
+ const parsed = parseAndroidResourceReference(rawReference);
86
+ if (parsed.state === "malformed" || parsed.state === "empty" || parsed.state === "placeholder") {
87
+ return {
88
+ state: "malformed-reference",
89
+ reference: rawReference,
90
+ modulePath: relativeWithinRoot(root, modulePath),
91
+ reason: parsed.state === "malformed" ? parsed.reason : `Reference is ${parsed.state}, not a concrete resource value`,
92
+ };
93
+ }
94
+ if (parsed.state === "unsupported-type" || parsed.state === "package-qualified") {
95
+ return {
96
+ state: "unsupported-reference",
97
+ reference: rawReference,
98
+ modulePath: relativeWithinRoot(root, modulePath),
99
+ parsed,
100
+ };
101
+ }
102
+ const sourceSets = listCandidateSourceSets(root, modulePath);
103
+ const candidates = [];
104
+ for (const sourceSet of sourceSets) {
105
+ const candidatePath = path.join(modulePath, "src", sourceSet, "res", parsed.type, `${parsed.name}.xml`);
106
+ if (isSafelyContainedExistingFile(root, candidatePath)) {
107
+ candidates.push({ sourceSet, relativePath: relativeWithinRoot(root, candidatePath) });
108
+ }
109
+ }
110
+ const relativeModulePath = relativeWithinRoot(root, modulePath);
111
+ if (candidates.length === 0) {
112
+ return { state: "missing", reference: rawReference, modulePath: relativeModulePath, searchedSourceSets: sourceSets };
113
+ }
114
+ if (candidates.length === 1) {
115
+ return { state: "resolved", reference: rawReference, modulePath: relativeModulePath, candidates: [candidates[0]] };
116
+ }
117
+ return { state: "ambiguous", reference: rawReference, modulePath: relativeModulePath, candidates };
118
+ }
@@ -0,0 +1,141 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 1 — stable rule identifiers for the advanced Android security
3
+ // checks planned for later v0.4.1 batches.
4
+ //
5
+ // This file defines identifiers only. No detector, finding, or check runs
6
+ // because a rule id exists here — later batches implement the check logic
7
+ // and reference these ids. Follows the existing per-audit-file "android-"
8
+ // prefixed string-literal convention (see src/mobile/android/audit/*.ts:
9
+ // android-permission-*, android-component-*, android-deep-link-*,
10
+ // android-intent-filter-*) rather than inventing a second id scheme.
11
+ // ---------------------------------------------------------------------------
12
+ export const ANDROID_NETWORK_SECURITY_RULE_IDS = [
13
+ "android-network-cleartext-traffic",
14
+ "android-network-security-config",
15
+ "android-network-user-added-trust-anchor",
16
+ "android-network-debug-trust-override",
17
+ "android-network-broad-domain-config",
18
+ "android-network-pinning-metadata",
19
+ "android-network-pinning-unresolved",
20
+ ];
21
+ export const ANDROID_BACKUP_RELEASE_RULE_IDS = [
22
+ "android-backup-allow-backup",
23
+ "android-backup-full-backup-content",
24
+ "android-backup-data-extraction-rules",
25
+ "android-release-debuggable",
26
+ "android-release-test-only",
27
+ "android-release-build-type-configuration",
28
+ ];
29
+ export const ANDROID_SECRETS_SIGNING_RULE_IDS = [
30
+ "android-secret-hardcoded-candidate",
31
+ "android-secret-private-key-candidate",
32
+ "android-secret-token-password-candidate",
33
+ "android-signing-password-literal",
34
+ "android-signing-keystore-candidate",
35
+ "android-signing-path-leakage",
36
+ "android-signing-debug-in-release",
37
+ ];
38
+ export const ANDROID_WEBVIEW_RULE_IDS = [
39
+ "android-webview-javascript-enabled",
40
+ "android-webview-javascript-interface-exposure",
41
+ "android-webview-file-content-access",
42
+ "android-webview-universal-file-url-access",
43
+ "android-webview-mixed-content",
44
+ "android-webview-ssl-error-proceed",
45
+ "android-webview-debugging-enabled",
46
+ "android-webview-suspicious-url-loading",
47
+ ];
48
+ export const ANDROID_FILE_PROVIDER_RULE_IDS = [
49
+ "android-file-provider-exported",
50
+ "android-file-provider-missing-protection",
51
+ "android-file-provider-broad-paths",
52
+ "android-file-provider-missing-paths-xml",
53
+ "android-file-provider-malformed-paths-xml",
54
+ "android-file-provider-unresolved-reference",
55
+ ];
56
+ export const ANDROID_SENSITIVE_DATA_RULE_IDS = [
57
+ "android-sensitive-external-storage",
58
+ "android-sensitive-preferences",
59
+ "android-sensitive-unsafe-file-database-storage",
60
+ "android-sensitive-logging",
61
+ "android-sensitive-clipboard-write",
62
+ "android-sensitive-clipboard-read-review",
63
+ ];
64
+ export const ANDROID_FIREBASE_GOOGLE_RULE_IDS = [
65
+ "android-firebase-realtime-database-permissive-rules",
66
+ "android-firebase-firestore-permissive-rules",
67
+ "android-firebase-storage-permissive-rules",
68
+ "android-firebase-missing-local-rules",
69
+ "android-firebase-google-configuration-evidence",
70
+ ];
71
+ export const ANDROID_OPTIONAL_TOOL_RULE_IDS = [
72
+ "android-optional-tool-semgrep-evidence",
73
+ "android-optional-tool-osv-evidence",
74
+ "android-optional-tool-lint-evidence",
75
+ "android-optional-tool-dependency-check-evidence",
76
+ ];
77
+ // All planned v0.4.1 advanced Android rule ids in one deterministic,
78
+ // duplicate-free list. Uniqueness across every family is verified by a
79
+ // regression test (ANDROID-V041-B1-01) rather than assumed.
80
+ export const ANDROID_ADVANCED_RULE_IDS = [
81
+ ...ANDROID_NETWORK_SECURITY_RULE_IDS,
82
+ ...ANDROID_BACKUP_RELEASE_RULE_IDS,
83
+ ...ANDROID_SECRETS_SIGNING_RULE_IDS,
84
+ ...ANDROID_WEBVIEW_RULE_IDS,
85
+ ...ANDROID_FILE_PROVIDER_RULE_IDS,
86
+ ...ANDROID_SENSITIVE_DATA_RULE_IDS,
87
+ ...ANDROID_FIREBASE_GOOGLE_RULE_IDS,
88
+ ...ANDROID_OPTIONAL_TOOL_RULE_IDS,
89
+ ];
90
+ // New v0.4.1 check categories, additive to ANDROID_CHECK_CATEGORIES in
91
+ // src/mobile/android/validation/checkResult.ts. Kept here (rather than only
92
+ // inline in that file) so rule ids and their owning category live next to
93
+ // each other; checkResult.ts re-exports/spreads this list additively.
94
+ export const ANDROID_ADVANCED_CHECK_CATEGORIES = [
95
+ "android-network-security",
96
+ "android-backup-configuration",
97
+ "android-release-configuration",
98
+ "android-secret-candidates",
99
+ "android-signing-configuration",
100
+ "android-webview",
101
+ "android-file-provider",
102
+ "android-sensitive-storage",
103
+ "android-sensitive-logging",
104
+ "android-clipboard",
105
+ "android-firebase-google-services",
106
+ "android-semgrep",
107
+ "android-osv",
108
+ "android-lint",
109
+ "android-dependency-check",
110
+ ];
111
+ const RULE_ID_TO_CATEGORY = new Map([
112
+ ...ANDROID_NETWORK_SECURITY_RULE_IDS.map((id) => [id, "android-network-security"]),
113
+ ...ANDROID_BACKUP_RELEASE_RULE_IDS.map((id) => [id, id.startsWith("android-backup-") ? "android-backup-configuration" : "android-release-configuration"]),
114
+ ...ANDROID_SECRETS_SIGNING_RULE_IDS.map((id) => [id, id.startsWith("android-secret-") ? "android-secret-candidates" : "android-signing-configuration"]),
115
+ ...ANDROID_WEBVIEW_RULE_IDS.map((id) => [id, "android-webview"]),
116
+ ...ANDROID_FILE_PROVIDER_RULE_IDS.map((id) => [id, "android-file-provider"]),
117
+ ...ANDROID_SENSITIVE_DATA_RULE_IDS.map((id) => {
118
+ if (id === "android-sensitive-logging")
119
+ return [id, "android-sensitive-logging"];
120
+ if (id.startsWith("android-sensitive-clipboard"))
121
+ return [id, "android-clipboard"];
122
+ return [id, "android-sensitive-storage"];
123
+ }),
124
+ ...ANDROID_FIREBASE_GOOGLE_RULE_IDS.map((id) => [id, "android-firebase-google-services"]),
125
+ ...ANDROID_OPTIONAL_TOOL_RULE_IDS.map((id) => {
126
+ if (id === "android-optional-tool-semgrep-evidence")
127
+ return [id, "android-semgrep"];
128
+ if (id === "android-optional-tool-osv-evidence")
129
+ return [id, "android-osv"];
130
+ if (id === "android-optional-tool-lint-evidence")
131
+ return [id, "android-lint"];
132
+ return [id, "android-dependency-check"];
133
+ }),
134
+ ]);
135
+ export function categoryForAdvancedRuleId(ruleId) {
136
+ const category = RULE_ID_TO_CATEGORY.get(ruleId);
137
+ if (category === undefined) {
138
+ throw new Error(`No category registered for advanced Android rule id: ${ruleId}`);
139
+ }
140
+ return category;
141
+ }
@@ -0,0 +1,116 @@
1
+ import { makeAndroidFinding } from "../../audit/androidFinding.js";
2
+ import { buildAndroidSourceLocation } from "../sourceLocation.js";
3
+ import { makeCandidateEvidence } from "../candidateEvidence.js";
4
+ import { fingerprintCandidateValue, redactedPreviewForCandidate } from "../redaction.js";
5
+ import { matchPrivateKeyBlocks, matchSensitiveAssignments } from "./matchSecretCandidates.js";
6
+ import { isReferenceSyntax, shouldSuppressEntirely } from "./suppressValue.js";
7
+ function fileKindFor(relativePath) {
8
+ const lower = relativePath.toLowerCase();
9
+ if (lower.endsWith(".kt") || lower.endsWith(".kts") || lower.endsWith(".java") || lower.endsWith(".gradle") || lower.endsWith(".gradle.kts")) {
10
+ return "code";
11
+ }
12
+ if (lower.endsWith(".properties") || lower.endsWith(".yaml") || lower.endsWith(".yml") || lower.endsWith(".toml")) {
13
+ return "config-unquoted";
14
+ }
15
+ // .xml/.json use quoted-form matching (JSON always quotes; XML attribute
16
+ // values are quoted by construction).
17
+ return "config-quotable";
18
+ }
19
+ function classifyIdentifier(identifier) {
20
+ const normalized = identifier.toLowerCase().replace(/[-_]/g, "");
21
+ if (["storepassword", "keypassword", "signingpassword"].includes(normalized)) {
22
+ return { ruleId: "android-signing-password-literal", sensitiveDataCategory: "signing-password" };
23
+ }
24
+ if (["databasepassword", "dbpassword"].includes(normalized)) {
25
+ return { ruleId: "android-secret-token-password-candidate", sensitiveDataCategory: "database-credential" };
26
+ }
27
+ if (["password", "passwd", "pwd"].includes(normalized)) {
28
+ return { ruleId: "android-secret-token-password-candidate", sensitiveDataCategory: "password" };
29
+ }
30
+ if (normalized === "clientsecret") {
31
+ return { ruleId: "android-secret-token-password-candidate", sensitiveDataCategory: "oauth-client-secret" };
32
+ }
33
+ if (normalized === "accesstoken") {
34
+ return { ruleId: "android-secret-token-password-candidate", sensitiveDataCategory: "access-token" };
35
+ }
36
+ if (normalized === "bearertoken") {
37
+ return { ruleId: "android-secret-token-password-candidate", sensitiveDataCategory: "bearer-token" };
38
+ }
39
+ if (normalized === "authtoken") {
40
+ return { ruleId: "android-secret-token-password-candidate", sensitiveDataCategory: "access-token" };
41
+ }
42
+ if (normalized === "apisecret") {
43
+ return { ruleId: "android-secret-token-password-candidate", sensitiveDataCategory: "cloud-secret-key" };
44
+ }
45
+ // Bare "secret" and any other sensitive-pattern match not covered above.
46
+ return { ruleId: "android-secret-hardcoded-candidate", sensitiveDataCategory: "unknown-sensitive-value" };
47
+ }
48
+ export function analyzeSecretCandidateFile(targetRoot, file) {
49
+ const candidates = [];
50
+ const findings = [];
51
+ const fileKind = fileKindFor(file.relativePath);
52
+ for (const block of matchPrivateKeyBlocks(file.content)) {
53
+ const location = buildAndroidSourceLocation(targetRoot, file.absolutePath, { line: block.line });
54
+ const fingerprint = fingerprintCandidateValue(block.rawBlock);
55
+ if (!block.terminated) {
56
+ candidates.push(makeCandidateEvidence({
57
+ ruleId: "android-secret-private-key-candidate",
58
+ category: "android-secret-candidates",
59
+ confidence: "medium",
60
+ modulePath: file.modulePath,
61
+ location,
62
+ summary: `Unterminated ${block.headerLabel} block found; treated conservatively`,
63
+ rawValue: block.rawBlock,
64
+ resolutionState: "malformed",
65
+ staticAnalysisLimitations: ["Block was not terminated by a matching END marker within the bounded scan window; content was not validated as a real key."],
66
+ }));
67
+ continue;
68
+ }
69
+ findings.push(makeAndroidFinding({
70
+ ruleId: "android-secret-private-key-candidate",
71
+ title: `Private key material found (${block.headerLabel})`,
72
+ severity: "major",
73
+ confidence: "high",
74
+ description: `A ${block.headerLabel} block was found in target-contained text. This is high-confidence structural evidence of committed private key material; the key was not opened, parsed, or validated.`,
75
+ manifestPath: file.relativePath,
76
+ identity: fingerprint,
77
+ location: { line: block.line },
78
+ evidenceDetails: [`redactedPreview=${redactedPreviewForCandidate(block.rawBlock, { maxLength: 40 })}`],
79
+ recommendation: "Remove the private key from source control immediately and rotate the corresponding credential/certificate.",
80
+ }));
81
+ }
82
+ for (const match of matchSensitiveAssignments(file.content, fileKind)) {
83
+ if (shouldSuppressEntirely(match.rawValue))
84
+ continue;
85
+ const location = buildAndroidSourceLocation(targetRoot, file.absolutePath, { line: match.line });
86
+ const classification = classifyIdentifier(match.identifier);
87
+ if (isReferenceSyntax(match.rawValue)) {
88
+ candidates.push(makeCandidateEvidence({
89
+ ruleId: classification.ruleId,
90
+ category: classification.ruleId === "android-signing-password-literal" ? "android-signing-configuration" : "android-secret-candidates",
91
+ confidence: "low",
92
+ modulePath: file.modulePath,
93
+ location,
94
+ summary: `"${match.identifier}" is assigned an environment/property/placeholder reference, not a literal value`,
95
+ rawValue: match.rawValue,
96
+ resolutionState: "not-applicable",
97
+ staticAnalysisLimitations: ["Reference syntax was detected; the resolved value was not evaluated and is not treated as a hardcoded credential."],
98
+ }));
99
+ continue;
100
+ }
101
+ const fingerprint = fingerprintCandidateValue(match.rawValue);
102
+ findings.push(makeAndroidFinding({
103
+ ruleId: classification.ruleId,
104
+ title: `Hardcoded ${match.identifier} literal found`,
105
+ severity: "major",
106
+ confidence: "high",
107
+ description: `The identifier "${match.identifier}" is assigned a literal, non-placeholder value. This is high-confidence static evidence of a hardcoded credential; it is not proof the credential is currently valid or in active use.`,
108
+ manifestPath: file.relativePath,
109
+ identity: fingerprint,
110
+ location: { line: match.line },
111
+ evidenceDetails: [`identifier=${match.identifier}`, `redactedPreview=${redactedPreviewForCandidate(match.rawValue)}`],
112
+ recommendation: "Remove the hardcoded credential, rotate it, and load it from a secure secrets mechanism instead.",
113
+ }));
114
+ }
115
+ return { candidates, findings };
116
+ }
@@ -0,0 +1,80 @@
1
+ import { sortCandidateEvidence } from "../ordering.js";
2
+ import { discoverSecretSourceFiles } from "./discoverSecretSourceFiles.js";
3
+ import { analyzeSecretCandidateFile } from "./analyzeSecretCandidates.js";
4
+ // ---------------------------------------------------------------------------
5
+ // v0.4.1 Batch 4 — standalone Android secret-candidate check result.
6
+ //
7
+ // Operates over discovered files rather than parsed manifests, so it cannot
8
+ // reuse buildAndroidManifestCheckResult (Batch 3's manifest-audit builder) —
9
+ // this is a distinct, still-conservative status-decision policy: never-run
10
+ // for a non-Android target, "failed" only when a reportable (non-
11
+ // informational) finding exists, otherwise "passed". Standalone: not called
12
+ // from validateAndroidTarget / any active orchestration, not rendered in
13
+ // reports, no CLI effect. Batch 8 integrates.
14
+ // ---------------------------------------------------------------------------
15
+ export const ANDROID_SECRET_CANDIDATES_AUDIT_CHECK_ID = "android-secret-candidates-audit";
16
+ export function auditAndroidSecretCandidates(targetRoot, detection) {
17
+ if (detection.projectKind === "non-android") {
18
+ return {
19
+ id: ANDROID_SECRET_CANDIDATES_AUDIT_CHECK_ID,
20
+ category: "android-secret-candidates",
21
+ title: "Android hardcoded secret-candidate audit",
22
+ status: "unsupported",
23
+ requirementLevel: "optional",
24
+ ran: false,
25
+ skipped: true,
26
+ skipInfo: {
27
+ checkId: ANDROID_SECRET_CANDIDATES_AUDIT_CHECK_ID,
28
+ reason: "Target was not detected as an Android project.",
29
+ requirementLevel: "optional",
30
+ missingCapability: "android-detection",
31
+ verdictImpact: "does not apply to a non-Android target",
32
+ recommendedNextAction: "Re-run against an Android Gradle project, or verify project detection.",
33
+ },
34
+ evidence: [],
35
+ findings: [],
36
+ warnings: [],
37
+ errors: [],
38
+ sourcePaths: [],
39
+ confidence: "unknown",
40
+ environmentRequirements: [],
41
+ candidateEvidence: [],
42
+ };
43
+ }
44
+ const modulePaths = detection.modules.map((m) => m.path);
45
+ const { files, skipped } = discoverSecretSourceFiles(targetRoot, modulePaths);
46
+ const findingsById = new Map();
47
+ const candidatesById = new Map();
48
+ for (const file of files) {
49
+ const result = analyzeSecretCandidateFile(targetRoot, file);
50
+ for (const finding of result.findings)
51
+ findingsById.set(finding.id, finding);
52
+ for (const candidate of result.candidates)
53
+ candidatesById.set(candidate.id, candidate);
54
+ }
55
+ const findings = [...findingsById.values()].sort((a, b) => a.id.localeCompare(b.id));
56
+ const candidateEvidence = sortCandidateEvidence([...candidatesById.values()]);
57
+ const evidence = [`${files.length} file(s) scanned across ${new Set(files.map((f) => f.modulePath ?? "(root)")).size} module scope(s)`];
58
+ const warnings = skipped.map((s) => `${s.relativePath}: skipped (${s.reason}${s.detail ? `: ${s.detail}` : ""})`);
59
+ const hasReportableFinding = findings.some((f) => f.severity !== "informational");
60
+ return {
61
+ id: ANDROID_SECRET_CANDIDATES_AUDIT_CHECK_ID,
62
+ category: "android-secret-candidates",
63
+ title: "Android hardcoded secret-candidate audit",
64
+ status: hasReportableFinding ? "failed" : "passed",
65
+ requirementLevel: "required",
66
+ ran: true,
67
+ skipped: false,
68
+ evidence,
69
+ findings,
70
+ warnings: [
71
+ ...warnings,
72
+ "Static analysis only: does not validate credentials, contact external services, or scan Git history.",
73
+ ],
74
+ errors: [],
75
+ sourcePaths: files.map((f) => f.relativePath),
76
+ confidence: skipped.length > 0 ? "medium" : "high",
77
+ environmentRequirements: [],
78
+ candidateEvidence,
79
+ };
80
+ }
@@ -0,0 +1,152 @@
1
+ import fs from "node:fs";
2
+ import { collectFilesForGlobs } from "../../../../core/fileGlobs.js";
3
+ // ---------------------------------------------------------------------------
4
+ // v0.4.1 Batch 4 — bounded, deterministic file discovery for the secret-
5
+ // candidate scanner.
6
+ //
7
+ // Reuses the existing target-contained glob collector
8
+ // (src/core/fileGlobs.ts's collectFilesForGlobs, which itself uses
9
+ // src/core/pathSafety.ts for containment) for traversal — no second
10
+ // traversal engine. Adds narrow, documented bounds (size cap, binary
11
+ // detection) co-located here because no existing shared utility exposes
12
+ // structured skip evidence (the closest existing owner,
13
+ // src/securityValidation/attackScenarios/boundedSourceScan.ts, silently
14
+ // drops oversized/unreadable/binary files with no skip record, and is owned
15
+ // by an unrelated security-validation feature, not Android).
16
+ // ---------------------------------------------------------------------------
17
+ // Extension-based recursive globs plus explicit root-level literals — the
18
+ // existing glob matcher's "**/*.ext" form requires at least one path
19
+ // separator, so genuinely root-level files (e.g. local.properties sitting
20
+ // directly in the target root) need an explicit literal entry too.
21
+ export const SECRET_SCAN_GLOBS = [
22
+ "**/*.kt",
23
+ "**/*.kts",
24
+ "**/*.java",
25
+ "**/*.xml",
26
+ "**/*.gradle",
27
+ "**/*.gradle.kts",
28
+ "**/*.properties",
29
+ "**/*.json",
30
+ "**/*.yaml",
31
+ "**/*.yml",
32
+ "**/*.toml",
33
+ "gradle.properties",
34
+ "local.properties",
35
+ "build.gradle",
36
+ "build.gradle.kts",
37
+ "settings.gradle",
38
+ "settings.gradle.kts",
39
+ ];
40
+ // Path segments excluded beyond src/core/fileGlobs.ts's own directory
41
+ // exclusions (node_modules/dist/build/coverage/.git/lab-output/.my-dev-kit),
42
+ // matching the wider Android-appropriate set already used by
43
+ // src/mobile/android/detect/traversal.ts.
44
+ const EXCLUDED_PATH_SEGMENTS = [".gradle/", "out/", "reports/", ".idea/", ".vscode/", ".vs/"];
45
+ function isExcludedPath(relativePath) {
46
+ const normalized = `${relativePath.replace(/\\/g, "/")}/`;
47
+ return EXCLUDED_PATH_SEGMENTS.some((segment) => normalized === segment || normalized.startsWith(segment) || normalized.includes(`/${segment}`));
48
+ }
49
+ // Documented, co-located bounds (no existing shared constant applies here).
50
+ export const MAX_SECRET_SCAN_FILE_BYTES = 1_000_000;
51
+ export const MAX_SECRET_SCAN_FILE_COUNT = 5_000;
52
+ const NUL_CHAR = String.fromCharCode(0);
53
+ function inferModulePath(relativePath, knownModulePaths) {
54
+ const normalized = relativePath.replace(/\\/g, "/");
55
+ let best;
56
+ for (const modulePath of knownModulePaths) {
57
+ const prefix = `${modulePath.replace(/\\/g, "/")}/`;
58
+ if (normalized.startsWith(prefix) && (best === undefined || modulePath.length > best.length)) {
59
+ best = modulePath;
60
+ }
61
+ }
62
+ return best;
63
+ }
64
+ // Discovers a bounded, deterministic set of target-contained text files
65
+ // eligible for secret-candidate scanning. Never throws for missing glob base
66
+ // paths (per-glob isolation, same rationale as boundedSourceScan.ts), never
67
+ // reads beyond the size cap, and reports every skip with a structured
68
+ // reason rather than silently dropping files.
69
+ export function discoverSecretSourceFiles(targetRoot, knownModulePaths = []) {
70
+ const candidateMap = new Map();
71
+ for (const glob of SECRET_SCAN_GLOBS) {
72
+ let matches;
73
+ try {
74
+ matches = collectFilesForGlobs(targetRoot, [glob]);
75
+ }
76
+ catch {
77
+ continue;
78
+ }
79
+ for (const match of matches) {
80
+ const normalizedRel = match.relativePath.replace(/\\/g, "/");
81
+ if (isExcludedPath(normalizedRel))
82
+ continue;
83
+ candidateMap.set(normalizedRel, match.absolutePath);
84
+ }
85
+ }
86
+ const candidates = [...candidateMap.entries()]
87
+ .sort(([a], [b]) => a.localeCompare(b))
88
+ .slice(0, MAX_SECRET_SCAN_FILE_COUNT)
89
+ .map(([relativePath, absolutePath]) => ({ relativePath, absolutePath }));
90
+ const files = [];
91
+ const skipped = [];
92
+ for (const candidate of candidates) {
93
+ let stat;
94
+ try {
95
+ stat = fs.lstatSync(candidate.absolutePath);
96
+ }
97
+ catch (error) {
98
+ skipped.push({ relativePath: candidate.relativePath, reason: "unreadable", detail: error instanceof Error ? error.message : undefined });
99
+ continue;
100
+ }
101
+ // Symlinks are not followed outside the target: resolve the real path
102
+ // and re-check containment before ever reading content.
103
+ let realAbsolutePath = candidate.absolutePath;
104
+ if (stat.isSymbolicLink()) {
105
+ try {
106
+ realAbsolutePath = fs.realpathSync(candidate.absolutePath);
107
+ }
108
+ catch {
109
+ skipped.push({ relativePath: candidate.relativePath, reason: "unreadable", detail: "broken symlink" });
110
+ continue;
111
+ }
112
+ const normalizedRoot = targetRoot.replace(/\\/g, "/");
113
+ const normalizedReal = realAbsolutePath.replace(/\\/g, "/");
114
+ if (!normalizedReal.startsWith(normalizedRoot)) {
115
+ skipped.push({ relativePath: candidate.relativePath, reason: "unreadable", detail: "symlink escapes target root" });
116
+ continue;
117
+ }
118
+ try {
119
+ stat = fs.statSync(realAbsolutePath);
120
+ }
121
+ catch (error) {
122
+ skipped.push({ relativePath: candidate.relativePath, reason: "unreadable", detail: error instanceof Error ? error.message : undefined });
123
+ continue;
124
+ }
125
+ }
126
+ if (!stat.isFile())
127
+ continue;
128
+ if (stat.size > MAX_SECRET_SCAN_FILE_BYTES) {
129
+ skipped.push({ relativePath: candidate.relativePath, reason: "oversized", detail: `${stat.size} bytes exceeds ${MAX_SECRET_SCAN_FILE_BYTES}` });
130
+ continue;
131
+ }
132
+ let content;
133
+ try {
134
+ content = fs.readFileSync(realAbsolutePath, "utf8");
135
+ }
136
+ catch (error) {
137
+ skipped.push({ relativePath: candidate.relativePath, reason: "unreadable", detail: error instanceof Error ? error.message : undefined });
138
+ continue;
139
+ }
140
+ if (content.indexOf(NUL_CHAR) !== -1) {
141
+ skipped.push({ relativePath: candidate.relativePath, reason: "binary-like" });
142
+ continue;
143
+ }
144
+ files.push({
145
+ relativePath: candidate.relativePath,
146
+ absolutePath: candidate.absolutePath,
147
+ modulePath: inferModulePath(candidate.relativePath, knownModulePaths),
148
+ content,
149
+ });
150
+ }
151
+ return { files, skipped };
152
+ }
@@ -0,0 +1,9 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 4 — public exports for the standalone Android secret-
3
+ // candidate audit. Deliberately does not export the raw matcher
4
+ // (matchSecretCandidates.ts), suppression internals, or raw match types —
5
+ // only the discovery/analysis/check-result layers that already redact.
6
+ // ---------------------------------------------------------------------------
7
+ export { discoverSecretSourceFiles, SECRET_SCAN_GLOBS, MAX_SECRET_SCAN_FILE_BYTES, MAX_SECRET_SCAN_FILE_COUNT } from "./discoverSecretSourceFiles.js";
8
+ export { analyzeSecretCandidateFile } from "./analyzeSecretCandidates.js";
9
+ export { auditAndroidSecretCandidates, ANDROID_SECRET_CANDIDATES_AUDIT_CHECK_ID } from "./checkResult.js";