@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,193 @@
1
+ import { executableMask, lineForOffset, methodScopes, scopeAt } from "../sensitiveData/localSourceContext.js";
2
+ import { splitTopLevelArguments } from "../sensitiveData/splitArguments.js";
3
+ import { classifyDirectExpression } from "../sensitiveData/classifyDirectExpression.js";
4
+ import { classifySensitiveIdentifier } from "../sensitiveData/classifySensitiveIdentifier.js";
5
+ // ---------------------------------------------------------------------------
6
+ // v0.4.1 Batch 6 — bounded Firebase/Google service-use, collection-metadata,
7
+ // and analytics-event evidence collection. Purely lexical: Gradle files are
8
+ // never evaluated, no dependency graph is resolved, and no Bundle/object
9
+ // contents are inferred beyond direct same-method arguments.
10
+ // ---------------------------------------------------------------------------
11
+ const GRADLE_PLUGIN = /\bid\s*\(?["']([\w.-]*google-services|com\.google\.firebase\.crashlytics)["']\)?/g;
12
+ const GRADLE_DEPENDENCY = /["'](?:com\.google\.firebase:)?(firebase-(?:database|firestore|storage|auth|analytics|crashlytics|messaging|perf))[:"']/g;
13
+ // Gradle dependency/plugin coordinates are always string literals, unlike
14
+ // Java/Kotlin executable calls — masking string *contents* (executableMask)
15
+ // would blank out exactly the text this scanner needs to match. Only
16
+ // comments are masked here; string literals are left intact.
17
+ const COMMENT_ONLY = /\/\/[^\n]*|\/\*[\s\S]*?\*\//g;
18
+ function maskCommentsOnly(source) {
19
+ return source.replace(COMMENT_ONLY, (match) => match.replace(/[^\n]/g, " "));
20
+ }
21
+ const SERVICE_BY_ARTIFACT = {
22
+ "firebase-database": "database",
23
+ "firebase-firestore": "firestore",
24
+ "firebase-storage": "storage",
25
+ "firebase-auth": "auth",
26
+ "firebase-analytics": "analytics",
27
+ "firebase-crashlytics": "crashlytics",
28
+ "firebase-messaging": "messaging",
29
+ "firebase-perf": "performance",
30
+ };
31
+ const SOURCE_USAGE = [
32
+ [/\bFirebaseDatabase\b/, "database"],
33
+ [/\bFirebaseFirestore\b/, "firestore"],
34
+ [/\bFirebaseStorage\b/, "storage"],
35
+ [/\bFirebaseAuth\b/, "auth"],
36
+ [/\bFirebaseAnalytics\b/, "analytics"],
37
+ [/\bFirebaseCrashlytics\b/, "crashlytics"],
38
+ [/\bFirebaseMessaging(?:Service)?\b/, "messaging"],
39
+ [/\bFirebasePerformance\b/, "performance"],
40
+ ];
41
+ export function collectGradleServiceEvidence(file) {
42
+ const evidence = [];
43
+ const mask = maskCommentsOnly(file.content);
44
+ for (const match of mask.matchAll(GRADLE_PLUGIN)) {
45
+ const offset = match.index ?? 0;
46
+ evidence.push({
47
+ service: match[1].includes("crashlytics") ? "crashlytics" : "database",
48
+ source: "gradle-plugin",
49
+ detail: match[1],
50
+ relativePath: file.relativePath,
51
+ modulePath: file.modulePath,
52
+ line: lineForOffset(file.content, offset),
53
+ });
54
+ }
55
+ for (const match of mask.matchAll(GRADLE_DEPENDENCY)) {
56
+ const offset = match.index ?? 0;
57
+ const service = SERVICE_BY_ARTIFACT[match[1]];
58
+ if (!service)
59
+ continue;
60
+ evidence.push({
61
+ service,
62
+ source: "gradle-dependency",
63
+ detail: match[1],
64
+ relativePath: file.relativePath,
65
+ modulePath: file.modulePath,
66
+ line: lineForOffset(file.content, offset),
67
+ });
68
+ }
69
+ return evidence;
70
+ }
71
+ export function collectSourceServiceEvidence(file) {
72
+ const evidence = [];
73
+ const mask = executableMask(file.content);
74
+ for (const [pattern, service] of SOURCE_USAGE) {
75
+ const re = new RegExp(pattern.source, "g");
76
+ for (const match of mask.matchAll(re)) {
77
+ const offset = match.index ?? 0;
78
+ evidence.push({
79
+ service,
80
+ source: /^import\b/.test(mask.slice(Math.max(0, offset - 8), offset)) ? "source-import" : "source-usage",
81
+ detail: match[0],
82
+ relativePath: file.relativePath,
83
+ modulePath: file.modulePath,
84
+ line: lineForOffset(file.content, offset),
85
+ });
86
+ }
87
+ }
88
+ return evidence;
89
+ }
90
+ const COLLECTION_SETTER = /\b(setAnalyticsCollectionEnabled|setCrashlyticsCollectionEnabled|setAutoInitEnabled)\s*\(([^;\n]*?)\)/gd;
91
+ function realGroup(content, match, group) {
92
+ const span = match.indices?.[group];
93
+ return span ? content.slice(span[0], span[1]) : match[group];
94
+ }
95
+ const SETTER_SERVICE = {
96
+ setAnalyticsCollectionEnabled: "analytics",
97
+ setCrashlyticsCollectionEnabled: "crashlytics",
98
+ setAutoInitEnabled: "messaging",
99
+ };
100
+ function stateOf(argument) {
101
+ if (argument === undefined)
102
+ return "dynamic";
103
+ const classification = classifyDirectExpression(argument);
104
+ if (classification.kind === "boolean-literal")
105
+ return argument.trim() === "true" ? "true" : "false";
106
+ return "dynamic";
107
+ }
108
+ export function collectCollectionSetterEvidence(file) {
109
+ const mask = executableMask(file.content);
110
+ const evidence = [];
111
+ for (const match of mask.matchAll(COLLECTION_SETTER)) {
112
+ const offset = match.index ?? 0;
113
+ const args = splitTopLevelArguments(realGroup(file.content, match, 2) ?? "");
114
+ evidence.push({
115
+ service: SETTER_SERVICE[match[1]],
116
+ state: stateOf(args[0]),
117
+ relativePath: file.relativePath,
118
+ modulePath: file.modulePath,
119
+ line: lineForOffset(file.content, offset),
120
+ });
121
+ }
122
+ return evidence;
123
+ }
124
+ const META_DATA_TAG = /<meta-data\b[^>]*\/?>(?:\s*<\/meta-data>)?/g;
125
+ const NAME_ATTR = /android:name\s*=\s*"([^"]*)"/;
126
+ const VALUE_ATTR = /android:value\s*=\s*"([^"]*)"/;
127
+ const COLLECTION_META_NAMES = new Set([
128
+ "firebase_analytics_collection_enabled",
129
+ "firebase_crashlytics_collection_enabled",
130
+ "firebase_messaging_auto_init_enabled",
131
+ "firebase_performance_collection_enabled",
132
+ ]);
133
+ export function collectManifestCollectionMetadata(file) {
134
+ const evidence = [];
135
+ for (const match of file.content.matchAll(META_DATA_TAG)) {
136
+ const tag = match[0];
137
+ const name = NAME_ATTR.exec(tag)?.[1];
138
+ if (!name || !COLLECTION_META_NAMES.has(name))
139
+ continue;
140
+ const rawValue = VALUE_ATTR.exec(tag)?.[1];
141
+ const state = rawValue === "true" ? "true" : rawValue === "false" ? "false" : "dynamic";
142
+ evidence.push({
143
+ name,
144
+ rawValue: rawValue ?? "(missing)",
145
+ state,
146
+ relativePath: file.relativePath,
147
+ line: lineForOffset(file.content, match.index ?? 0),
148
+ });
149
+ }
150
+ return evidence;
151
+ }
152
+ const LOG_EVENT_CALL = /\b(?:FirebaseAnalytics|analytics)\.getInstance\([^)]*\)\.logEvent\s*\(([^;\n]*?)\)|\blogEvent\s*\(([^;\n]*?)\)/gd;
153
+ const BUNDLE_PUT = /([A-Za-z_][\w.]*)\.(putString|putLong|putDouble|putBoolean)\s*\(([^;\n]*?)\)/gd;
154
+ export function collectAnalyticsEventEvidence(file) {
155
+ const mask = executableMask(file.content);
156
+ const scopes = methodScopes(mask);
157
+ const evidence = [];
158
+ const bundlePuts = [];
159
+ for (const match of mask.matchAll(BUNDLE_PUT)) {
160
+ const offset = match.index ?? 0;
161
+ const args = splitTopLevelArguments(realGroup(file.content, match, 3) ?? "");
162
+ bundlePuts.push({ key: args[0], value: args[1], offset, scopeId: scopeAt(scopes, offset)?.id });
163
+ }
164
+ for (const match of mask.matchAll(LOG_EVENT_CALL)) {
165
+ const offset = match.index ?? 0;
166
+ const rawArgs = realGroup(file.content, match, 1) ?? realGroup(file.content, match, 2) ?? "";
167
+ const args = splitTopLevelArguments(rawArgs);
168
+ const scopeId = scopeAt(scopes, offset)?.id;
169
+ let sensitiveKey;
170
+ let sensitiveValue;
171
+ let isDirectLiteral = false;
172
+ for (const put of bundlePuts.filter((p) => p.scopeId === scopeId)) {
173
+ const literal = put.key !== undefined && classifyDirectExpression(put.key).kind === "string-literal" ? classifyDirectExpression(put.key).literalValue : undefined;
174
+ const classification = literal !== undefined ? classifySensitiveIdentifier(literal) : undefined;
175
+ if (classification) {
176
+ sensitiveKey = put.key;
177
+ sensitiveValue = put.value;
178
+ isDirectLiteral = put.value !== undefined && classifyDirectExpression(put.value).kind === "string-literal";
179
+ break;
180
+ }
181
+ }
182
+ evidence.push({
183
+ eventNameExpression: args[0],
184
+ sensitiveParameterKey: sensitiveKey,
185
+ sensitiveParameterValueExpression: sensitiveValue,
186
+ isDirectLiteralValue: isDirectLiteral,
187
+ relativePath: file.relativePath,
188
+ modulePath: file.modulePath,
189
+ line: lineForOffset(file.content, offset),
190
+ });
191
+ }
192
+ return evidence;
193
+ }
@@ -0,0 +1,128 @@
1
+ import fs from "node:fs";
2
+ import { collectFilesForGlobs } from "../../../../core/fileGlobs.js";
3
+ import { discoverSecretSourceFiles, MAX_SECRET_SCAN_FILE_BYTES } from "../secretCandidates/discoverSecretSourceFiles.js";
4
+ // ---------------------------------------------------------------------------
5
+ // v0.4.1 Batch 6 — bounded, target-contained discovery of Firebase/Google
6
+ // configuration and rules artifacts.
7
+ //
8
+ // google-services.json and firebase.json are already covered by Batch 4's
9
+ // discoverSecretSourceFiles (its glob list includes **/*.json), so those are
10
+ // reused directly rather than re-discovered. .firebaserc and *.rules files
11
+ // are not covered by that glob list (dotfiles and a non-listed extension),
12
+ // so a small supplemental scan reuses collectFilesForGlobs (the same
13
+ // target-containment primitive discoverSecretSourceFiles itself wraps)
14
+ // rather than introducing a second traversal engine.
15
+ // ---------------------------------------------------------------------------
16
+ const NUL_CHAR = String.fromCharCode(0);
17
+ function readSupplemental(targetRoot, globs, knownModulePaths) {
18
+ const files = [];
19
+ const skipped = [];
20
+ const matches = [];
21
+ const seen = new Set();
22
+ for (const glob of globs) {
23
+ let globMatches;
24
+ try {
25
+ globMatches = collectFilesForGlobs(targetRoot, [glob]);
26
+ }
27
+ catch {
28
+ continue;
29
+ }
30
+ for (const match of globMatches) {
31
+ if (seen.has(match.relativePath))
32
+ continue;
33
+ seen.add(match.relativePath);
34
+ matches.push(match);
35
+ }
36
+ }
37
+ for (const match of matches) {
38
+ const relativePath = match.relativePath.replace(/\\/g, "/");
39
+ if (relativePath.includes("node_modules/") || relativePath.includes(".gradle/") || relativePath.includes("build/"))
40
+ continue;
41
+ let stat;
42
+ try {
43
+ stat = fs.statSync(match.absolutePath);
44
+ }
45
+ catch (error) {
46
+ skipped.push({ relativePath, reason: "unreadable", detail: error instanceof Error ? error.message : undefined });
47
+ continue;
48
+ }
49
+ if (!stat.isFile())
50
+ continue;
51
+ if (stat.size > MAX_SECRET_SCAN_FILE_BYTES) {
52
+ skipped.push({ relativePath, reason: "oversized", detail: `${stat.size} bytes exceeds ${MAX_SECRET_SCAN_FILE_BYTES}` });
53
+ continue;
54
+ }
55
+ let content;
56
+ try {
57
+ content = fs.readFileSync(match.absolutePath, "utf8");
58
+ }
59
+ catch (error) {
60
+ skipped.push({ relativePath, reason: "unreadable", detail: error instanceof Error ? error.message : undefined });
61
+ continue;
62
+ }
63
+ if (content.indexOf(NUL_CHAR) !== -1) {
64
+ skipped.push({ relativePath, reason: "binary-like" });
65
+ continue;
66
+ }
67
+ const modulePath = knownModulePaths.find((m) => relativePath.startsWith(`${m}/`));
68
+ files.push({ relativePath, absolutePath: match.absolutePath, modulePath, content });
69
+ }
70
+ return { files, skipped };
71
+ }
72
+ export function discoverFirebaseArtifacts(targetRoot, knownModulePaths = []) {
73
+ const { files, skipped } = discoverSecretSourceFiles(targetRoot, knownModulePaths);
74
+ const googleServicesFiles = [];
75
+ const firebaseJsonFiles = [];
76
+ const databaseRulesJsonFiles = [];
77
+ const gradleFiles = [];
78
+ const sourceFiles = [];
79
+ const manifestFiles = [];
80
+ const foundRelativePaths = new Set();
81
+ for (const file of files) {
82
+ const lower = file.relativePath.toLowerCase();
83
+ foundRelativePaths.add(file.relativePath);
84
+ if (lower.endsWith("google-services.json"))
85
+ googleServicesFiles.push(file);
86
+ else if (lower.endsWith("/firebase.json") || lower === "firebase.json")
87
+ firebaseJsonFiles.push(file);
88
+ else if (lower.endsWith("database.rules.json"))
89
+ databaseRulesJsonFiles.push(file);
90
+ else if (/\.(gradle|gradle\.kts)$/.test(lower))
91
+ gradleFiles.push(file);
92
+ else if (/\.(java|kt)$/.test(lower))
93
+ sourceFiles.push(file);
94
+ else if (lower.endsWith("androidmanifest.xml"))
95
+ manifestFiles.push(file);
96
+ }
97
+ // Root-level filenames (e.g. a repo-root database.rules.json) are not
98
+ // matched by "**/*.json"-style globs, which require at least one path
99
+ // separator (the same limitation Batch 4's discovery module documents) —
100
+ // explicit root-level literals fill that gap for Firebase's well-known
101
+ // conventional filenames, deduplicated against what discoverSecretSourceFiles
102
+ // already found.
103
+ const rc = readSupplemental(targetRoot, ["**/.firebaserc", ".firebaserc"], knownModulePaths);
104
+ const rules = readSupplemental(targetRoot, ["**/*.rules", "firestore.rules", "storage.rules", "database.rules"], knownModulePaths);
105
+ const rootJson = readSupplemental(targetRoot, ["google-services.json", "firebase.json", "database.rules.json"], knownModulePaths);
106
+ for (const file of rootJson.files) {
107
+ if (foundRelativePaths.has(file.relativePath))
108
+ continue;
109
+ const lower = file.relativePath.toLowerCase();
110
+ if (lower.endsWith("google-services.json"))
111
+ googleServicesFiles.push(file);
112
+ else if (lower.endsWith("firebase.json"))
113
+ firebaseJsonFiles.push(file);
114
+ else if (lower.endsWith("database.rules.json"))
115
+ databaseRulesJsonFiles.push(file);
116
+ }
117
+ return {
118
+ googleServicesFiles,
119
+ firebaseJsonFiles,
120
+ firebaseRcFiles: rc.files,
121
+ databaseRulesJsonFiles,
122
+ rulesFiles: rules.files,
123
+ gradleFiles,
124
+ sourceFiles,
125
+ manifestFiles,
126
+ skipped: [...skipped, ...rc.skipped, ...rules.skipped, ...rootJson.skipped],
127
+ };
128
+ }
@@ -0,0 +1 @@
1
+ export { auditAndroidFirebaseGoogleServices, ANDROID_FIREBASE_GOOGLE_SERVICES_AUDIT_CHECK_ID } from "./checkResult.js";
@@ -0,0 +1,55 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 6 — bounded local Realtime Database rules parser.
3
+ //
4
+ // Recursively walks the parsed JSON tree collecting .read/.write/.validate/
5
+ // .indexOn entries with their JSON path. This is not the Firebase rules
6
+ // evaluator: no runtime auth is evaluated, and a local rules file is never
7
+ // treated as proof of the deployed configuration.
8
+ // ---------------------------------------------------------------------------
9
+ const MAX_DEPTH = 40;
10
+ const MAX_NODES = 20_000;
11
+ const MAX_CONDITION_LENGTH = 200;
12
+ const RULE_KEYS = new Set([".read", ".write", ".validate", ".indexOn"]);
13
+ function summarize(value) {
14
+ const text = typeof value === "string" ? value : JSON.stringify(value);
15
+ return text.length > MAX_CONDITION_LENGTH ? `${text.slice(0, MAX_CONDITION_LENGTH)}...` : text;
16
+ }
17
+ export function parseDatabaseRules(file) {
18
+ let parsed;
19
+ try {
20
+ parsed = JSON.parse(file.content);
21
+ }
22
+ catch {
23
+ return { file, malformed: true, entries: [], boundsExceeded: false };
24
+ }
25
+ const root = parsed && typeof parsed === "object" ? parsed.rules ?? parsed : parsed;
26
+ const entries = [];
27
+ let nodeCount = 0;
28
+ let boundsExceeded = false;
29
+ function walk(node, jsonPath, depth) {
30
+ if (boundsExceeded)
31
+ return;
32
+ nodeCount += 1;
33
+ if (nodeCount > MAX_NODES || depth > MAX_DEPTH) {
34
+ boundsExceeded = true;
35
+ return;
36
+ }
37
+ if (node === null || typeof node !== "object" || Array.isArray(node))
38
+ return;
39
+ for (const [key, value] of Object.entries(node)) {
40
+ if (RULE_KEYS.has(key)) {
41
+ entries.push({
42
+ jsonPath,
43
+ operation: key,
44
+ literalBoolean: typeof value === "boolean" ? value : undefined,
45
+ conditionSummary: summarize(value),
46
+ depth,
47
+ });
48
+ continue;
49
+ }
50
+ walk(value, jsonPath === "/" ? `/${key}` : `${jsonPath}/${key}`, depth + 1);
51
+ }
52
+ }
53
+ walk(root, "/", 0);
54
+ return { file, malformed: false, entries, boundsExceeded };
55
+ }
@@ -0,0 +1,75 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 6 — bounded, safe firebase.json and .firebaserc parsing.
3
+ // Only the sections relevant to Android security review (rules file
4
+ // references, project aliases) are extracted; hosting/functions/emulators
5
+ // sections are intentionally ignored (they are unrelated to Android
6
+ // security findings per agents.txt Batch 6 section 13.7).
7
+ // ---------------------------------------------------------------------------
8
+ function asRecord(value) {
9
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
10
+ }
11
+ function asArray(value) {
12
+ return Array.isArray(value) ? value : undefined;
13
+ }
14
+ function asString(value) {
15
+ return typeof value === "string" ? value : undefined;
16
+ }
17
+ function collectRulesPaths(section) {
18
+ const paths = [];
19
+ const record = asRecord(section);
20
+ if (record) {
21
+ const rules = asString(record.rules);
22
+ if (rules)
23
+ paths.push(rules);
24
+ return paths;
25
+ }
26
+ const array = asArray(section);
27
+ if (array) {
28
+ for (const entry of array) {
29
+ const entryRecord = asRecord(entry);
30
+ const rules = asString(entryRecord?.rules);
31
+ if (rules)
32
+ paths.push(rules);
33
+ }
34
+ }
35
+ return paths;
36
+ }
37
+ export function parseFirebaseJson(file) {
38
+ let parsed;
39
+ try {
40
+ parsed = JSON.parse(file.content);
41
+ }
42
+ catch {
43
+ return { file, malformed: true, databaseRulesPaths: [], firestoreRulesPaths: [], storageRulesPaths: [] };
44
+ }
45
+ const root = asRecord(parsed);
46
+ if (!root)
47
+ return { file, malformed: true, databaseRulesPaths: [], firestoreRulesPaths: [], storageRulesPaths: [] };
48
+ return {
49
+ file,
50
+ malformed: false,
51
+ databaseRulesPaths: collectRulesPaths(root.database),
52
+ firestoreRulesPaths: collectRulesPaths(root.firestore),
53
+ storageRulesPaths: collectRulesPaths(root.storage),
54
+ };
55
+ }
56
+ export function parseFirebaseRc(file) {
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(file.content);
60
+ }
61
+ catch {
62
+ return { file, malformed: true, aliases: {} };
63
+ }
64
+ const root = asRecord(parsed);
65
+ const projects = asRecord(root?.projects);
66
+ if (!projects)
67
+ return { file, malformed: false, aliases: {} };
68
+ const aliases = {};
69
+ for (const [alias, value] of Object.entries(projects)) {
70
+ const projectId = asString(value);
71
+ if (projectId)
72
+ aliases[alias] = projectId;
73
+ }
74
+ return { file, malformed: false, defaultProject: aliases.default, aliases };
75
+ }
@@ -0,0 +1,88 @@
1
+ import { executableMask, lineForOffset } from "../sensitiveData/localSourceContext.js";
2
+ // ---------------------------------------------------------------------------
3
+ // v0.4.1 Batch 6 — bounded local Firestore/Storage security-rules parser.
4
+ //
5
+ // A brace-aware, comment/string-masked local scanner over `match { ... }` /
6
+ // `allow ...: if ...;` syntax — not a full Firebase rules-language compiler.
7
+ // Functions, imports, and complex boolean expressions are preserved only as
8
+ // a bounded condition summary; this module never evaluates a condition and
9
+ // never claims a rule is (or is not) the deployed configuration.
10
+ // ---------------------------------------------------------------------------
11
+ const MAX_MATCH_BLOCKS = 500;
12
+ const MAX_ALLOW_STATEMENTS = 1000;
13
+ const MAX_CONDITION_LENGTH = 300;
14
+ const MATCH_HEADER = /\bmatch\s+([^\n{]+?)\s*\{/g;
15
+ const ALLOW_STATEMENT = /\ballow\s+([A-Za-z,\s]+?)\s*(?::\s*if\s+([^;]+?))?\s*;/g;
16
+ const TEST_MODE_TIME = /request\.time\s*[<>]=?\s*timestamp\.(date|value)\s*\(/;
17
+ function braceEnd(mask, openBraceIndex) {
18
+ let depth = 0;
19
+ for (let i = openBraceIndex; i < mask.length; i += 1) {
20
+ if (mask[i] === "{")
21
+ depth += 1;
22
+ else if (mask[i] === "}") {
23
+ depth -= 1;
24
+ if (depth === 0)
25
+ return i;
26
+ }
27
+ }
28
+ return mask.length - 1;
29
+ }
30
+ function boundedCondition(condition) {
31
+ if (condition === undefined)
32
+ return "(no condition — always allowed)";
33
+ const trimmed = condition.trim();
34
+ return trimmed.length > MAX_CONDITION_LENGTH ? `${trimmed.slice(0, MAX_CONDITION_LENGTH)}...` : trimmed;
35
+ }
36
+ export function parseFirestoreOrStorageRules(file, service) {
37
+ const mask = executableMask(file.content);
38
+ let boundsExceeded = false;
39
+ const blocks = [];
40
+ for (const match of mask.matchAll(MATCH_HEADER)) {
41
+ if (blocks.length >= MAX_MATCH_BLOCKS) {
42
+ boundsExceeded = true;
43
+ break;
44
+ }
45
+ const from = match.index ?? 0;
46
+ const openBrace = from + match[0].length - 1;
47
+ blocks.push({ pattern: match[1].trim(), from, to: braceEnd(mask, openBrace) });
48
+ }
49
+ function enclosingPattern(offset) {
50
+ let best;
51
+ for (const block of blocks) {
52
+ if (offset >= block.from && offset <= block.to) {
53
+ if (best === undefined || block.to - block.from < best.to - best.from)
54
+ best = block;
55
+ }
56
+ }
57
+ return best?.pattern ?? "(unresolved match scope)";
58
+ }
59
+ const matches = [];
60
+ let allowCount = 0;
61
+ for (const match of mask.matchAll(ALLOW_STATEMENT)) {
62
+ if (allowCount >= MAX_ALLOW_STATEMENTS) {
63
+ boundsExceeded = true;
64
+ break;
65
+ }
66
+ allowCount += 1;
67
+ const offset = match.index ?? 0;
68
+ const operations = match[1]
69
+ .split(",")
70
+ .map((op) => op.trim())
71
+ .filter(Boolean);
72
+ const conditionRaw = match[2]?.trim();
73
+ const isLiteralTrue = conditionRaw === undefined || conditionRaw === "true";
74
+ const isLiteralFalse = conditionRaw === "false";
75
+ matches.push({
76
+ service,
77
+ matchPath: enclosingPattern(offset),
78
+ operations,
79
+ conditionSummary: boundedCondition(conditionRaw),
80
+ isLiteralTrue,
81
+ isLiteralFalse,
82
+ hasAuthCondition: conditionRaw !== undefined && /request\.auth/.test(conditionRaw),
83
+ isTestModeTimeCondition: conditionRaw !== undefined && TEST_MODE_TIME.test(conditionRaw),
84
+ line: lineForOffset(file.content, offset),
85
+ });
86
+ }
87
+ return { file, malformed: false, matches, boundsExceeded };
88
+ }
@@ -0,0 +1,55 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 6 — bounded, safe google-services.json parsing.
3
+ //
4
+ // A bare Firebase API key is not a private secret by format (see
5
+ // sensitiveCategories.ts) — this parser only records bounded presence
6
+ // metadata (a count) for oauth_client/api_key entries, never the values
7
+ // themselves. Malformed JSON is handled safely (never throws to the
8
+ // caller); unsupported/unexpected shapes degrade to an empty client list
9
+ // rather than crashing.
10
+ // ---------------------------------------------------------------------------
11
+ function asRecord(value) {
12
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
13
+ }
14
+ function asArray(value) {
15
+ return Array.isArray(value) ? value : undefined;
16
+ }
17
+ function asString(value) {
18
+ return typeof value === "string" ? value : undefined;
19
+ }
20
+ export function parseGoogleServicesConfig(file) {
21
+ let parsed;
22
+ try {
23
+ parsed = JSON.parse(file.content);
24
+ }
25
+ catch {
26
+ return { file, malformed: true, clients: [] };
27
+ }
28
+ const root = asRecord(parsed);
29
+ if (!root)
30
+ return { file, malformed: true, clients: [] };
31
+ const projectInfo = asRecord(root.project_info);
32
+ const clientsRaw = asArray(root.client) ?? [];
33
+ const clients = clientsRaw.map((entry) => {
34
+ const clientRecord = asRecord(entry) ?? {};
35
+ const clientInfo = asRecord(clientRecord.client_info);
36
+ const androidClientInfo = asRecord(clientInfo?.android_client_info);
37
+ const oauthClient = asArray(clientRecord.oauth_client) ?? [];
38
+ const apiKey = asArray(clientRecord.api_key) ?? [];
39
+ return {
40
+ packageName: asString(androidClientInfo?.package_name),
41
+ mobileSdkAppId: asString(clientInfo?.mobilesdk_app_id),
42
+ apiKeyCount: apiKey.length,
43
+ oauthClientCount: oauthClient.length,
44
+ };
45
+ });
46
+ return {
47
+ file,
48
+ malformed: false,
49
+ projectId: asString(projectInfo?.project_id),
50
+ projectNumber: asString(projectInfo?.project_number),
51
+ storageBucket: asString(projectInfo?.storage_bucket),
52
+ firebaseUrl: asString(projectInfo?.firebase_url),
53
+ clients,
54
+ };
55
+ }
@@ -0,0 +1,6 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 6 — Firebase/Google services semantic contracts. Local to
3
+ // this feature; does not duplicate Batch 1's CandidateEvidence/
4
+ // SecurityFinding/AndroidCheckResult.
5
+ // ---------------------------------------------------------------------------
6
+ export {};