@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,136 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 4 — narrow, deterministic in-memory secret-candidate matching.
3
+ //
4
+ // This is the ONLY layer that ever holds a raw candidate value. Callers
5
+ // (analyzeSecretCandidates.ts) must redact/fingerprint immediately and never
6
+ // store, log, or export the raw value from this module. Nothing in this
7
+ // file is exported through index.ts.
8
+ //
9
+ // No entropy scoring is implemented: the structural markers (PEM headers)
10
+ // and explicit sensitive-identifier context already meet the "high
11
+ // confidence" bar the batch requires without it, and entropy alone must
12
+ // never create a finding per agents.txt Batch 4 section 9.8 — omitting it
13
+ // avoids speculative complexity with no corresponding required rule.
14
+ // ---------------------------------------------------------------------------
15
+ const PRIVATE_KEY_HEADER_PATTERN = /-----BEGIN ([A-Z0-9 ]*?PRIVATE KEY)-----/g;
16
+ function findBlockEnd(content, searchStart, headerLabel) {
17
+ const footer = `-----END ${headerLabel}-----`;
18
+ const footerIndex = content.indexOf(footer, searchStart);
19
+ if (footerIndex === -1) {
20
+ // Unterminated block — bound the captured region rather than reading to
21
+ // end of file unbounded.
22
+ const MAX_UNTERMINATED_BLOCK_LENGTH = 4000;
23
+ return { end: Math.min(content.length, searchStart + MAX_UNTERMINATED_BLOCK_LENGTH), terminated: false };
24
+ }
25
+ return { end: footerIndex + footer.length, terminated: true };
26
+ }
27
+ function lineNumberAt(content, index) {
28
+ let line = 1;
29
+ for (let i = 0; i < index && i < content.length; i++) {
30
+ if (content[i] === "\n")
31
+ line++;
32
+ }
33
+ return line;
34
+ }
35
+ // Public keys and certificates ("-----BEGIN PUBLIC KEY-----",
36
+ // "-----BEGIN CERTIFICATE-----") never match PRIVATE_KEY_HEADER_PATTERN
37
+ // (which requires the literal substring "PRIVATE KEY") — the boundary is
38
+ // enforced by construction, not a separate exclusion check.
39
+ export function matchPrivateKeyBlocks(content) {
40
+ const matches = [];
41
+ for (const headerMatch of content.matchAll(PRIVATE_KEY_HEADER_PATTERN)) {
42
+ const headerLabel = headerMatch[1];
43
+ const startIndex = headerMatch.index ?? 0;
44
+ const { end, terminated } = findBlockEnd(content, startIndex, headerLabel);
45
+ matches.push({
46
+ line: lineNumberAt(content, startIndex),
47
+ headerLabel,
48
+ rawBlock: content.slice(startIndex, end),
49
+ terminated,
50
+ });
51
+ }
52
+ return matches;
53
+ }
54
+ // Sensitive identifier names that, when paired with a literal (never a
55
+ // reference/placeholder), are treated as high-confidence secret-shaped
56
+ // evidence. Deliberately narrow — matches agents.txt Batch 4 section 9.5.
57
+ //
58
+ // Splits the identifier into words (snake_case and camelCase boundaries)
59
+ // rather than using a single \b-anchored regex, so real-world constant
60
+ // naming like `SECRET_TOKEN`, `DB_PASSWORD`, or `placeholderSecret` is
61
+ // recognized — a plain `\bsecret\b` regex fails on these because
62
+ // underscore and camelCase transitions are not `\b` word boundaries.
63
+ const SENSITIVE_SINGLE_WORDS = new Set(["password", "passwd", "pwd", "secret"]);
64
+ const SENSITIVE_WORD_PAIRS = [
65
+ ["client", "secret"],
66
+ ["access", "token"],
67
+ ["bearer", "token"],
68
+ ["auth", "token"],
69
+ ["api", "secret"],
70
+ ["signing", "password"],
71
+ ["store", "password"],
72
+ ["key", "password"],
73
+ ["database", "password"],
74
+ ["db", "password"],
75
+ ];
76
+ function splitIdentifierWords(identifier) {
77
+ return identifier
78
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
79
+ .split(/[_\-.\s]+/)
80
+ .map((word) => word.toLowerCase())
81
+ .filter((word) => word.length > 0);
82
+ }
83
+ function isSensitiveIdentifier(identifier) {
84
+ const words = splitIdentifierWords(identifier);
85
+ if (words.some((word) => SENSITIVE_SINGLE_WORDS.has(word)))
86
+ return true;
87
+ for (const [first, second] of SENSITIVE_WORD_PAIRS) {
88
+ for (let i = 0; i < words.length - 1; i++) {
89
+ if (words[i] === first && words[i + 1] === second)
90
+ return true;
91
+ }
92
+ }
93
+ return false;
94
+ }
95
+ // Finds `identifier = "value"` / `identifier: "value"` / `identifier="value"`
96
+ // / `"identifier": "value"` (JSON-style quoted key) pairs anywhere on a line
97
+ // (the value side quoting is required — code files never match an unquoted
98
+ // right-hand side, so a variable/function reference such as
99
+ // `val password = getPassword()` can never be mistaken for a literal).
100
+ const KEY_VALUE_QUOTED_PATTERN = /["']?([A-Za-z_][A-Za-z0-9_]*)["']?\s*[:=]\s*["']([^"']*)["']/g;
101
+ // Properties/YAML/TOML permit unquoted scalar values on a `key = value` /
102
+ // `key: value` line; still requires the key itself to match the sensitive
103
+ // identifier pattern.
104
+ const KEY_VALUE_UNQUOTED_PATTERN = /^\s*([A-Za-z_][A-Za-z0-9_.-]*)\s*[:=]\s*(\S.*?)\s*$/;
105
+ // Scans line-by-line (bounds work naturally; also gives exact line numbers
106
+ // without a second pass). Returns only pairs whose *captured identifier*
107
+ // (not just the line generally) matches a sensitive name — reference/
108
+ // placeholder/empty-value filtering happens in analyzeSecretCandidates.ts,
109
+ // which also owns redaction, so this function's return values are the last
110
+ // point at which a raw value exists before that immediate conversion.
111
+ export function matchSensitiveAssignments(content, fileKind) {
112
+ const matches = [];
113
+ const lines = content.split(/\r\n|\r|\n/);
114
+ for (let i = 0; i < lines.length; i++) {
115
+ const line = lines[i];
116
+ if (fileKind === "config-unquoted") {
117
+ const unquoted = line.match(KEY_VALUE_UNQUOTED_PATTERN);
118
+ if (unquoted && isSensitiveIdentifier(unquoted[1])) {
119
+ matches.push({ line: i + 1, identifier: unquoted[1], rawValue: stripSurroundingQuotes(unquoted[2]) });
120
+ }
121
+ continue;
122
+ }
123
+ for (const pairMatch of line.matchAll(KEY_VALUE_QUOTED_PATTERN)) {
124
+ if (isSensitiveIdentifier(pairMatch[1])) {
125
+ matches.push({ line: i + 1, identifier: pairMatch[1], rawValue: pairMatch[2] });
126
+ }
127
+ }
128
+ }
129
+ return matches;
130
+ }
131
+ function stripSurroundingQuotes(value) {
132
+ if (value.length >= 2 && ((value[0] === '"' && value.endsWith('"')) || (value[0] === "'" && value.endsWith("'")))) {
133
+ return value.slice(1, -1);
134
+ }
135
+ return value;
136
+ }
@@ -0,0 +1,61 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 4 — conservative placeholder/example/reference suppression.
3
+ //
4
+ // Deliberately a small, exact/narrow-normalized check list rather than a
5
+ // large heuristic catalog (agents.txt Batch 4 section 9.9). Operates only on
6
+ // the already-captured raw value (never re-reads the file), and this module
7
+ // is not exported through index.ts.
8
+ // ---------------------------------------------------------------------------
9
+ const EXACT_PLACEHOLDER_VALUES = new Set([
10
+ "changeme",
11
+ "change_me",
12
+ "change-me",
13
+ "password",
14
+ "secret",
15
+ "example",
16
+ "sample",
17
+ "dummy",
18
+ "fake",
19
+ "test",
20
+ "your-token-here",
21
+ "your_token_here",
22
+ "replace-me",
23
+ "replace_me",
24
+ "redacted",
25
+ "masked",
26
+ "todo",
27
+ "xxx",
28
+ "placeholder",
29
+ "n/a",
30
+ "none",
31
+ ].map((v) => v.toLowerCase()));
32
+ export function isEmptyValue(value) {
33
+ return value.trim().length === 0;
34
+ }
35
+ export function isObviousPlaceholderValue(value) {
36
+ const normalized = value.trim().toLowerCase();
37
+ if (normalized.length === 0)
38
+ return true;
39
+ if (EXACT_PLACEHOLDER_VALUES.has(normalized))
40
+ return true;
41
+ // Fully repeated single character (e.g. "****", "xxxxxxxx", "00000000").
42
+ if (/^(.)\1*$/.test(normalized))
43
+ return true;
44
+ return false;
45
+ }
46
+ // Environment/property/manifest-placeholder reference syntax embedded in the
47
+ // literal itself (e.g. a Kotlin string template `"${DB_PASSWORD}"`, a
48
+ // properties-file `${some.property}`, a shell-style `$VAR`, a Windows
49
+ // `%VAR%`, or an Android manifest `@string/...`/`@xml/...` reference). Any
50
+ // of these means the "literal" is not actually a hardcoded value.
51
+ const REFERENCE_SYNTAX_PATTERN = /\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|%[A-Za-z_][A-Za-z0-9_]*%|^@(string|xml|raw|array|integer|bool)\//;
52
+ export function isReferenceSyntax(value) {
53
+ return REFERENCE_SYNTAX_PATTERN.test(value.trim());
54
+ }
55
+ // True when the value should never become a finding/candidate at all —
56
+ // empty or an obvious placeholder/example. Reference syntax is handled
57
+ // separately by the caller because it should surface as non-literal
58
+ // metadata (Batch 4 section 9.12), not be silently dropped.
59
+ export function shouldSuppressEntirely(value) {
60
+ return isEmptyValue(value) || isObviousPlaceholderValue(value);
61
+ }
@@ -0,0 +1,11 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 4 — bounded secret-candidate scan contracts.
3
+ //
4
+ // Local to this feature; does not duplicate Batch 1's CandidateEvidence,
5
+ // SecurityFinding, or the shared bounded-source-scan utility
6
+ // (src/securityValidation/attackScenarios/boundedSourceScan.ts) — it wraps
7
+ // the existing target-containment/glob-collection primitives
8
+ // (src/core/fileGlobs.ts) with Android-appropriate exclusions and adds
9
+ // structured skip evidence, which neither existing utility provides.
10
+ // ---------------------------------------------------------------------------
11
+ export {};
@@ -0,0 +1,34 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 1 — sensitive-data category vocabulary for later secret/
3
+ // signing/configuration candidate scanners.
4
+ //
5
+ // Deliberately small and additive. This is not a security-severity scale
6
+ // (see SecuritySeverity in src/securityValidation/types.ts, reused as-is by
7
+ // candidateEvidence.ts) — it only classifies *what kind* of value a static
8
+ // candidate looks like.
9
+ //
10
+ // A bare Firebase API key (the public `apiKey` field in google-services.json
11
+ // / Firebase web config) must NOT be classified as "cloud-secret-key" or any
12
+ // other private-secret category by default: Firebase API keys identify a
13
+ // Firebase project but are not treated as confidential by Google's own
14
+ // documentation, and misclassifying them would produce a permanent, noisy
15
+ // false positive for a huge fraction of real Android apps. A later batch's
16
+ // Firebase scanner should classify that value as "unknown-sensitive-value"
17
+ // (or omit a candidate entirely) rather than a private-secret category.
18
+ // ---------------------------------------------------------------------------
19
+ export const SENSITIVE_DATA_CATEGORIES = [
20
+ "password",
21
+ "access-token",
22
+ "bearer-token",
23
+ "oauth-client-secret",
24
+ "cloud-secret-key",
25
+ "private-key",
26
+ "database-credential",
27
+ "signing-password",
28
+ "recovery-code",
29
+ "personal-data",
30
+ "unknown-sensitive-value",
31
+ ];
32
+ export function isSensitiveDataCategory(value) {
33
+ return SENSITIVE_DATA_CATEGORIES.includes(value);
34
+ }
@@ -0,0 +1,99 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 6 — shared bounded direct-expression classification.
3
+ //
4
+ // Classifies a single, already-extracted argument/value expression string
5
+ // (captured by an analyzer's own bounded regex, e.g. the second argument to
6
+ // a Log.d(...) call, or the value passed to putString(...)) into a coarse
7
+ // syntactic kind. This performs no evaluation, no alias resolution beyond
8
+ // what the caller already resolved, and no interprocedural analysis — it is
9
+ // purely a classifier over the literal text of the expression.
10
+ //
11
+ // Safety invariant: `rawExpression` and the derived `literalValue` are
12
+ // returned for the caller's own redaction step (via
13
+ // makeCandidateEvidence/redactedPreviewForCandidate) — `boundedSummary` never
14
+ // contains literal content, only the classified kind and shape, so a caller
15
+ // that forgets to redact still cannot leak a raw value through the summary.
16
+ // ---------------------------------------------------------------------------
17
+ export const DIRECT_EXPRESSION_KINDS = [
18
+ "string-literal",
19
+ "numeric-literal",
20
+ "boolean-literal",
21
+ "null-literal",
22
+ "identifier",
23
+ "member-access",
24
+ "method-parameter",
25
+ "method-call",
26
+ "environment-lookup",
27
+ "property-lookup",
28
+ "placeholder-interpolation",
29
+ "collection-object",
30
+ "dynamic-unsupported",
31
+ ];
32
+ const MAX_EXPRESSION_LENGTH = 400;
33
+ function bound(expression) {
34
+ return expression.length > MAX_EXPRESSION_LENGTH ? `${expression.slice(0, MAX_EXPRESSION_LENGTH)}...` : expression;
35
+ }
36
+ function unquoteJavaKotlinString(literal) {
37
+ const tripleMatch = /^"""([\s\S]*)"""$/.exec(literal);
38
+ if (tripleMatch)
39
+ return tripleMatch[1];
40
+ const doubleMatch = /^"((?:[^"\\]|\\.)*)"$/.exec(literal);
41
+ if (doubleMatch) {
42
+ try {
43
+ return JSON.parse(literal);
44
+ }
45
+ catch {
46
+ return doubleMatch[1];
47
+ }
48
+ }
49
+ return undefined;
50
+ }
51
+ export function classifyDirectExpression(rawExpression) {
52
+ const trimmed = bound(rawExpression.trim());
53
+ const summaryFor = (kind) => `${kind} (length=${trimmed.length})`;
54
+ if (trimmed.length === 0) {
55
+ return { kind: "dynamic-unsupported", isDirectLiteral: false, boundedSummary: summaryFor("dynamic-unsupported") };
56
+ }
57
+ // Kotlin string templates / Java concatenation with a dynamic operand:
58
+ // treated as placeholder-interpolation, not a pure literal, even though it
59
+ // may still contain a sensitive literal fragment — callers that need the
60
+ // literal fragment extract it separately before classification.
61
+ if (/\$\{[^}]*\}|\$[A-Za-z_][\w]*/.test(trimmed) && /^"/.test(trimmed)) {
62
+ return { kind: "placeholder-interpolation", isDirectLiteral: false, boundedSummary: summaryFor("placeholder-interpolation") };
63
+ }
64
+ if (/^"[^"]*"\s*\+/.test(trimmed) || /\+\s*"[^"]*"$/.test(trimmed) || (/^".*"$/.test(trimmed) && /\+/.test(trimmed))) {
65
+ return { kind: "placeholder-interpolation", isDirectLiteral: false, boundedSummary: summaryFor("placeholder-interpolation") };
66
+ }
67
+ const unquoted = unquoteJavaKotlinString(trimmed);
68
+ if (unquoted !== undefined) {
69
+ return { kind: "string-literal", isDirectLiteral: true, literalValue: unquoted, boundedSummary: summaryFor("string-literal") };
70
+ }
71
+ if (/^-?\d+(\.\d+)?[fFdDlL]?$/.test(trimmed)) {
72
+ return { kind: "numeric-literal", isDirectLiteral: true, literalValue: trimmed, boundedSummary: summaryFor("numeric-literal") };
73
+ }
74
+ if (trimmed === "true" || trimmed === "false") {
75
+ return { kind: "boolean-literal", isDirectLiteral: true, literalValue: trimmed, boundedSummary: summaryFor("boolean-literal") };
76
+ }
77
+ if (trimmed === "null") {
78
+ return { kind: "null-literal", isDirectLiteral: false, boundedSummary: summaryFor("null-literal") };
79
+ }
80
+ if (/^(System\.getenv|getenv)\s*\(/.test(trimmed)) {
81
+ return { kind: "environment-lookup", isDirectLiteral: false, boundedSummary: summaryFor("environment-lookup") };
82
+ }
83
+ if (/^BuildConfig\./.test(trimmed)) {
84
+ return { kind: "property-lookup", isDirectLiteral: false, boundedSummary: summaryFor("property-lookup") };
85
+ }
86
+ if (/^[{\[]/.test(trimmed)) {
87
+ return { kind: "collection-object", isDirectLiteral: false, boundedSummary: summaryFor("collection-object") };
88
+ }
89
+ if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*\s*\(/.test(trimmed)) {
90
+ return { kind: "method-call", isDirectLiteral: false, boundedSummary: summaryFor("method-call") };
91
+ }
92
+ if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)+$/.test(trimmed)) {
93
+ return { kind: "member-access", isDirectLiteral: false, boundedSummary: summaryFor("member-access") };
94
+ }
95
+ if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
96
+ return { kind: "identifier", isDirectLiteral: false, boundedSummary: summaryFor("identifier") };
97
+ }
98
+ return { kind: "dynamic-unsupported", isDirectLiteral: false, boundedSummary: summaryFor("dynamic-unsupported") };
99
+ }
@@ -0,0 +1,111 @@
1
+ const EXACT_WORD_CATEGORY = new Map([
2
+ // Authentication and credentials
3
+ ["password", "password"],
4
+ ["passwd", "password"],
5
+ ["pwd", "password"],
6
+ ["passcode", "password"],
7
+ ["pin", "password"],
8
+ ["secret", "unknown-sensitive-value"],
9
+ ["credential", "unknown-sensitive-value"],
10
+ ["credentials", "unknown-sensitive-value"],
11
+ ["auth", "access-token"],
12
+ ["authorization", "access-token"],
13
+ ["bearer", "bearer-token"],
14
+ ["bearertoken", "bearer-token"],
15
+ ["token", "access-token"],
16
+ ["accesstoken", "access-token"],
17
+ ["refreshtoken", "access-token"],
18
+ ["authtoken", "access-token"],
19
+ ["sessiontoken", "access-token"],
20
+ ["apisecret", "cloud-secret-key"],
21
+ ["clientsecret", "oauth-client-secret"],
22
+ // Signing and recovery
23
+ ["signingpassword", "signing-password"],
24
+ ["storepassword", "signing-password"],
25
+ ["keypassword", "signing-password"],
26
+ ["recoverycode", "recovery-code"],
27
+ ["backupcode", "recovery-code"],
28
+ ["otp", "recovery-code"],
29
+ ["onetimepassword", "recovery-code"],
30
+ // Personal and account data
31
+ ["email", "personal-data"],
32
+ ["phone", "personal-data"],
33
+ ["phonenumber", "personal-data"],
34
+ ["address", "personal-data"],
35
+ ["location", "personal-data"],
36
+ ["latitude", "personal-data"],
37
+ ["longitude", "personal-data"],
38
+ ["userid", "personal-data"],
39
+ ["accountid", "personal-data"],
40
+ ["deviceid", "personal-data"],
41
+ ["advertisingid", "personal-data"],
42
+ // Financial or regulated data (no dedicated category — reuses
43
+ // personal-data, the closest Batch 1 regulated-data category)
44
+ ["cardnumber", "personal-data"],
45
+ ["creditcard", "personal-data"],
46
+ ["bankaccount", "personal-data"],
47
+ ["ssn", "personal-data"],
48
+ ["taxid", "personal-data"],
49
+ // Database/session data
50
+ ["session", "access-token"],
51
+ ["cookie", "access-token"],
52
+ ["databasepassword", "database-credential"],
53
+ ["dbpassword", "database-credential"],
54
+ ["connectionstring", "database-credential"],
55
+ ]);
56
+ // Public identifiers that must never be classified as sensitive by name
57
+ // alone, even though a naive substring match might otherwise catch them.
58
+ const PUBLIC_IDENTIFIER_WORDS = new Set([
59
+ "apikey",
60
+ "projectid",
61
+ "appid",
62
+ "packagename",
63
+ "storagebucket",
64
+ "databaseurl",
65
+ "senderid",
66
+ "clientid",
67
+ "mobilesdkappid",
68
+ "applicationid",
69
+ "namespace",
70
+ ]);
71
+ // Splits camelCase/PascalCase/snake_case/kebab-case identifiers into
72
+ // lowercase words, e.g. "userAuthToken" / "user_auth_token" -> ["user",
73
+ // "auth", "token"].
74
+ export function splitIdentifierWords(identifier) {
75
+ return identifier
76
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
77
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
78
+ .replace(/[-_.]+/g, " ")
79
+ .trim()
80
+ .split(/\s+/)
81
+ .filter(Boolean)
82
+ .map((word) => word.toLowerCase());
83
+ }
84
+ function normalizeCompound(words) {
85
+ return words.join("");
86
+ }
87
+ // Returns a classification only for a defensible direct match: either the
88
+ // whole normalized identifier (with separators removed) matches a known
89
+ // compound entry, or any individual word in the identifier exactly matches a
90
+ // known single-word entry. Never matches on an arbitrary substring.
91
+ export function classifySensitiveIdentifier(identifier) {
92
+ const words = splitIdentifierWords(identifier);
93
+ if (words.length === 0)
94
+ return undefined;
95
+ const compound = normalizeCompound(words);
96
+ if (PUBLIC_IDENTIFIER_WORDS.has(compound))
97
+ return undefined;
98
+ const compoundCategory = EXACT_WORD_CATEGORY.get(compound);
99
+ if (compoundCategory !== undefined) {
100
+ return { sensitiveDataCategory: compoundCategory, normalized: compound };
101
+ }
102
+ for (const word of words) {
103
+ if (PUBLIC_IDENTIFIER_WORDS.has(word))
104
+ continue;
105
+ const category = EXACT_WORD_CATEGORY.get(word);
106
+ if (category !== undefined) {
107
+ return { sensitiveDataCategory: category, normalized: word };
108
+ }
109
+ }
110
+ return undefined;
111
+ }
@@ -0,0 +1,3 @@
1
+ export { classifySensitiveIdentifier, splitIdentifierWords } from "./classifySensitiveIdentifier.js";
2
+ export { classifyDirectExpression, DIRECT_EXPRESSION_KINDS } from "./classifyDirectExpression.js";
3
+ export { executableMask, lineForOffset, methodScopes, scopeAt, sameScope } from "./localSourceContext.js";
@@ -0,0 +1,90 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 6 — narrow shared local Java/Kotlin source-context helpers.
3
+ //
4
+ // Extracted (not copied verbatim) from the bounded masking/scope technique
5
+ // Batch 5's WebView analyzer implemented privately in
6
+ // advancedSecurity/webview/checkResult.ts. That file is left completely
7
+ // unchanged (its own mask/scope logic stays inline) — this module is new,
8
+ // additive code used only by the four Batch 6 analyzers, generalized just
9
+ // enough to be language-general rather than SSL-callback-specific. It is
10
+ // NOT a compiler front end: no AST, no symbol resolution, no cross-method
11
+ // data flow. It only offers:
12
+ // - executableMask(): comment/string-masked text with byte-for-byte
13
+ // preserved offsets and line breaks, so downstream regexes never match
14
+ // inside a comment or a string literal, and reported line numbers stay
15
+ // correct against the original source.
16
+ // - methodScopes(): brace-depth-bounded method-like ranges (used for
17
+ // same-method correlation only — never cross-method).
18
+ // - lineForOffset(): 1-based line number for a byte offset.
19
+ // ---------------------------------------------------------------------------
20
+ const STRING_OR_COMMENT = /"""[\s\S]*?"""|"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|\/\/[^\n]*|\/\*[\s\S]*?\*\//g;
21
+ // Masks comment and string/char contents with spaces (preserving embedded
22
+ // newlines) so line numbers and offsets never shift and downstream regexes
23
+ // never match text that only appears inside a comment or a literal.
24
+ export function executableMask(source) {
25
+ return source.replace(STRING_OR_COMMENT, (match) => match.replace(/[^\n]/g, " "));
26
+ }
27
+ export function lineForOffset(source, offset) {
28
+ let line = 1;
29
+ for (let i = 0; i < offset && i < source.length; i += 1) {
30
+ if (source[i] === "\n")
31
+ line += 1;
32
+ }
33
+ return line;
34
+ }
35
+ const METHOD_HEADER = /(?:fun|void|[A-Za-z_$][\w$.<>,?\[\] ]*?)\s+[A-Za-z_$][\w$]*\s*\([^)]*\)[^{;]*\{/g;
36
+ function braceEnd(mask, openBraceIndex) {
37
+ let depth = 0;
38
+ for (let i = openBraceIndex; i < mask.length; i += 1) {
39
+ if (mask[i] === "{")
40
+ depth += 1;
41
+ else if (mask[i] === "}") {
42
+ depth -= 1;
43
+ if (depth === 0)
44
+ return i;
45
+ }
46
+ }
47
+ return mask.length - 1;
48
+ }
49
+ // Bounded, best-effort method-like scope extraction over already-masked
50
+ // text. Overlapping/nested matches are tolerated (each open-brace found by
51
+ // the header regex gets its own scope) — callers only use scope identity for
52
+ // "same enclosing scope" correlation, never for accurate call-graph shape.
53
+ export function methodScopes(mask) {
54
+ const scopes = [];
55
+ let match;
56
+ let id = 0;
57
+ METHOD_HEADER.lastIndex = 0;
58
+ while ((match = METHOD_HEADER.exec(mask)) !== null) {
59
+ const openBrace = match.index + match[0].length - 1;
60
+ if (mask[openBrace] !== "{")
61
+ continue;
62
+ const close = braceEnd(mask, openBrace);
63
+ scopes.push({ from: match.index, to: close, id });
64
+ id += 1;
65
+ }
66
+ return scopes;
67
+ }
68
+ // Innermost (smallest-span) scope containing offset, or undefined if offset
69
+ // is not inside any recognized method-like range (e.g. field initializer).
70
+ export function scopeAt(scopes, offset) {
71
+ let best;
72
+ for (const scope of scopes) {
73
+ if (offset >= scope.from && offset <= scope.to) {
74
+ if (best === undefined || scope.to - scope.from < best.to - best.from)
75
+ best = scope;
76
+ }
77
+ }
78
+ return best;
79
+ }
80
+ // Same-method correlation: true only when both offsets resolve to the same
81
+ // enclosing scope id (or both resolve to no scope, treated as file-level and
82
+ // therefore not correlated across arbitrary distance — callers should apply
83
+ // an additional bounded-distance check for the no-scope case).
84
+ export function sameScope(scopes, offsetA, offsetB) {
85
+ const a = scopeAt(scopes, offsetA);
86
+ const b = scopeAt(scopes, offsetB);
87
+ if (a === undefined || b === undefined)
88
+ return false;
89
+ return a.id === b.id;
90
+ }
@@ -0,0 +1,59 @@
1
+ // ---------------------------------------------------------------------------
2
+ // v0.4.1 Batch 6 — shared bounded top-level argument splitter.
3
+ //
4
+ // Splits a call's argument text on top-level commas only (commas nested
5
+ // inside (), [], {}, or string/char literals are not split points). Used by
6
+ // all four Batch 6 analyzers to pull individual arguments out of an already
7
+ // bounded, already-masked-for-comments call match. Not a parser: does not
8
+ // understand trailing lambdas, named/default arguments beyond a literal
9
+ // `name = value` prefix strip, or varargs semantics.
10
+ // ---------------------------------------------------------------------------
11
+ const MAX_ARGUMENTS = 12;
12
+ export function splitTopLevelArguments(argsText) {
13
+ const args = [];
14
+ let depth = 0;
15
+ let current = "";
16
+ let quote;
17
+ for (let i = 0; i < argsText.length; i += 1) {
18
+ const ch = argsText[i];
19
+ if (quote) {
20
+ current += ch;
21
+ if (ch === "\\") {
22
+ i += 1;
23
+ if (i < argsText.length)
24
+ current += argsText[i];
25
+ continue;
26
+ }
27
+ if (ch === quote)
28
+ quote = undefined;
29
+ continue;
30
+ }
31
+ if (ch === '"' || ch === "'") {
32
+ quote = ch;
33
+ current += ch;
34
+ continue;
35
+ }
36
+ if (ch === "(" || ch === "[" || ch === "{") {
37
+ depth += 1;
38
+ current += ch;
39
+ continue;
40
+ }
41
+ if (ch === ")" || ch === "]" || ch === "}") {
42
+ depth -= 1;
43
+ current += ch;
44
+ continue;
45
+ }
46
+ if (ch === "," && depth === 0) {
47
+ args.push(current.trim());
48
+ current = "";
49
+ if (args.length >= MAX_ARGUMENTS)
50
+ break;
51
+ continue;
52
+ }
53
+ current += ch;
54
+ }
55
+ if (current.trim().length > 0 || args.length === 0)
56
+ args.push(current.trim());
57
+ // Strip a leading `name = ` Kotlin named-argument prefix, if present.
58
+ return args.map((arg) => arg.replace(/^[A-Za-z_$][\w$]*\s*=\s*(?!=)/, ""));
59
+ }