@dailephd/my-dev-kit-lab 0.4.0 → 0.4.2

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 (158) hide show
  1. package/README.md +269 -47
  2. package/dist/scripts/audits/runAudit.js +28 -1
  3. package/dist/scripts/security/validate.js +38 -5
  4. package/dist/src/audits/core/auditConfig.js +16 -1
  5. package/dist/src/audits/core/auditRunner.js +6 -2
  6. package/dist/src/audits/report/auditReportModel.js +7 -0
  7. package/dist/src/audits/report/renderAuditJsonReport.js +1 -0
  8. package/dist/src/audits/report/renderAuditTextReport.js +37 -0
  9. package/dist/src/audits/security/androidAuditIntegration.js +145 -0
  10. package/dist/src/audits/security/mapAndroidSecurityFindingToAuditIssue.js +116 -0
  11. package/dist/src/audits/security/mapSecurityFindingToAuditIssue.js +15 -5
  12. package/dist/src/audits/security/securityAuditAdapter.js +20 -2
  13. package/dist/src/audits/security/securityAuditTypes.js +37 -0
  14. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/analyzeBackupConfiguration.js +300 -0
  15. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/checkResult.js +41 -0
  16. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/index.js +8 -0
  17. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/manifestEvidence.js +102 -0
  18. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/parseBackupRules.js +110 -0
  19. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/types.js +27 -0
  20. package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +50 -0
  21. package/dist/src/mobile/android/advancedSecurity/clipboard/analyzeClipboardSecurity.js +166 -0
  22. package/dist/src/mobile/android/advancedSecurity/clipboard/checkResult.js +74 -0
  23. package/dist/src/mobile/android/advancedSecurity/clipboard/collectClipboardEvidence.js +82 -0
  24. package/dist/src/mobile/android/advancedSecurity/clipboard/index.js +1 -0
  25. package/dist/src/mobile/android/advancedSecurity/clipboard/types.js +1 -0
  26. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/checkResult.js +255 -0
  27. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/index.js +1 -0
  28. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintSarif.js +37 -0
  29. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintXml.js +36 -0
  30. package/dist/src/mobile/android/advancedSecurity/externalTools/artifacts.js +95 -0
  31. package/dist/src/mobile/android/advancedSecurity/externalTools/boundedOutput.js +33 -0
  32. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/checkResult.js +173 -0
  33. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/command.js +33 -0
  34. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/index.js +1 -0
  35. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/parseDependencyCheckJson.js +133 -0
  36. package/dist/src/mobile/android/advancedSecurity/externalTools/discoverExecutable.js +13 -0
  37. package/dist/src/mobile/android/advancedSecurity/externalTools/index.js +8 -0
  38. package/dist/src/mobile/android/advancedSecurity/externalTools/minimalEnvironment.js +26 -0
  39. package/dist/src/mobile/android/advancedSecurity/externalTools/mutation.js +77 -0
  40. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/checkResult.js +161 -0
  41. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/command.js +25 -0
  42. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/index.js +1 -0
  43. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/parseOsvJson.js +136 -0
  44. package/dist/src/mobile/android/advancedSecurity/externalTools/runBoundedExternalTool.js +51 -0
  45. package/dist/src/mobile/android/advancedSecurity/externalTools/runRequestedAndroidExternalTools.js +54 -0
  46. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/checkResult.js +170 -0
  47. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/command.js +28 -0
  48. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/index.js +2 -0
  49. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/parseSemgrepJson.js +141 -0
  50. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/rules.js +105 -0
  51. package/dist/src/mobile/android/advancedSecurity/externalTools/types.js +107 -0
  52. package/dist/src/mobile/android/advancedSecurity/fileProvider/checkResult.js +188 -0
  53. package/dist/src/mobile/android/advancedSecurity/fileProvider/index.js +1 -0
  54. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/analyzeFirebaseGoogleServices.js +197 -0
  55. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/checkResult.js +77 -0
  56. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/collectFirebaseServiceEvidence.js +193 -0
  57. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/discoverFirebaseArtifacts.js +128 -0
  58. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/index.js +1 -0
  59. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseDatabaseRules.js +55 -0
  60. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirebaseConfig.js +75 -0
  61. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirestoreStorageRules.js +88 -0
  62. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseGoogleServicesConfig.js +55 -0
  63. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/types.js +6 -0
  64. package/dist/src/mobile/android/advancedSecurity/networkSecurity/analyzeNetworkSecurity.js +394 -0
  65. package/dist/src/mobile/android/advancedSecurity/networkSecurity/checkResult.js +47 -0
  66. package/dist/src/mobile/android/advancedSecurity/networkSecurity/deriveEffectiveNetworkPolicy.js +52 -0
  67. package/dist/src/mobile/android/advancedSecurity/networkSecurity/index.js +14 -0
  68. package/dist/src/mobile/android/advancedSecurity/networkSecurity/manifestEvidence.js +109 -0
  69. package/dist/src/mobile/android/advancedSecurity/networkSecurity/parseNetworkSecurityConfig.js +186 -0
  70. package/dist/src/mobile/android/advancedSecurity/networkSecurity/types.js +1 -0
  71. package/dist/src/mobile/android/advancedSecurity/ordering.js +44 -0
  72. package/dist/src/mobile/android/advancedSecurity/redaction.js +54 -0
  73. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/analyzeReleaseConfiguration.js +221 -0
  74. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/checkResult.js +39 -0
  75. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/index.js +7 -0
  76. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/manifestEvidence.js +19 -0
  77. package/dist/src/mobile/android/advancedSecurity/resourceReference.js +69 -0
  78. package/dist/src/mobile/android/advancedSecurity/resourceResolution.js +118 -0
  79. package/dist/src/mobile/android/advancedSecurity/ruleIds.js +141 -0
  80. package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +116 -0
  81. package/dist/src/mobile/android/advancedSecurity/secretCandidates/checkResult.js +80 -0
  82. package/dist/src/mobile/android/advancedSecurity/secretCandidates/discoverSecretSourceFiles.js +152 -0
  83. package/dist/src/mobile/android/advancedSecurity/secretCandidates/index.js +9 -0
  84. package/dist/src/mobile/android/advancedSecurity/secretCandidates/matchSecretCandidates.js +136 -0
  85. package/dist/src/mobile/android/advancedSecurity/secretCandidates/suppressValue.js +61 -0
  86. package/dist/src/mobile/android/advancedSecurity/secretCandidates/types.js +11 -0
  87. package/dist/src/mobile/android/advancedSecurity/sensitiveCategories.js +34 -0
  88. package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifyDirectExpression.js +99 -0
  89. package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifySensitiveIdentifier.js +111 -0
  90. package/dist/src/mobile/android/advancedSecurity/sensitiveData/index.js +3 -0
  91. package/dist/src/mobile/android/advancedSecurity/sensitiveData/localSourceContext.js +90 -0
  92. package/dist/src/mobile/android/advancedSecurity/sensitiveData/splitArguments.js +59 -0
  93. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/analyzeSensitiveLogging.js +117 -0
  94. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/checkResult.js +74 -0
  95. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/collectLoggingEvidence.js +87 -0
  96. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/index.js +1 -0
  97. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/types.js +1 -0
  98. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/analyzeSensitiveStorage.js +182 -0
  99. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/checkResult.js +74 -0
  100. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/collectStorageEvidence.js +109 -0
  101. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/index.js +1 -0
  102. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/types.js +4 -0
  103. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +270 -0
  104. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/checkResult.js +119 -0
  105. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/discoverKeystoreCandidates.js +55 -0
  106. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +81 -0
  107. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/index.js +8 -0
  108. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/types.js +20 -0
  109. package/dist/src/mobile/android/advancedSecurity/sourceLocation.js +15 -0
  110. package/dist/src/mobile/android/advancedSecurity/webview/checkResult.js +210 -0
  111. package/dist/src/mobile/android/advancedSecurity/webview/index.js +1 -0
  112. package/dist/src/mobile/android/gradle/moduleMetadataExtractor.js +123 -17
  113. package/dist/src/mobile/android/manifest/parseAndroidManifest.js +27 -4
  114. package/dist/src/mobile/android/manifest/xml/parseXml.js +25 -9
  115. package/dist/src/mobile/android/report/model.js +32 -1
  116. package/dist/src/mobile/android/report/renderAndroidReport.js +63 -0
  117. package/dist/src/mobile/android/validate/validateAndroidTarget.js +146 -3
  118. package/dist/src/mobile/android/validation/checkResult.js +7 -0
  119. package/dist/src/securityValidation/commandRunner.js +10 -1
  120. package/dist/src/securityValidation/validate/cliOptions.js +46 -0
  121. package/package.json +1 -1
  122. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/debug/res/xml/ambiguous_backup_rules.xml +4 -0
  123. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/ambiguous_backup_rules.xml +4 -0
  124. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/backup_rules.xml +5 -0
  125. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/broad_backup_rules.xml +4 -0
  126. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/data_extraction_rules.xml +10 -0
  127. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/unsupported_domain_rules.xml +4 -0
  128. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/malformed_backup_rules.xml +4 -0
  129. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/unsupported_root.xml +4 -0
  130. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/debug/res/xml/ambiguous_config.xml +4 -0
  131. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/ambiguous_config.xml +4 -0
  132. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/base_cleartext_false.xml +8 -0
  133. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/broad_and_duplicate_domains.xml +8 -0
  134. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/empty_domain.xml +6 -0
  135. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/malformed_pin.xml +11 -0
  136. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/network_security_config.xml +26 -0
  137. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/malformed_network_security_config.xml +6 -0
  138. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/unsupported_root.xml +4 -0
  139. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-dynamic.gradle +7 -0
  140. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-false.gradle +9 -0
  141. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-true.gradle +13 -0
  142. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-false.gradle.kts +7 -0
  143. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-true.gradle.kts +10 -0
  144. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/missing-release-build-type.gradle.kts +7 -0
  145. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/debug/res/xml/network_security_config.xml +8 -0
  146. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/backup_rules.xml +4 -0
  147. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/network_security_config.xml +8 -0
  148. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-b/src/main/res/xml/malformed_network_security_config.xml +7 -0
  149. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.java +8 -0
  150. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.kt +9 -0
  151. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle +11 -0
  152. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle.kts +11 -0
  153. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/google-services.json +16 -0
  154. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/local.properties +3 -0
  155. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/binary.properties +0 -0
  156. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/oversized.json +1 -0
  157. package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/app/fake-release-keystore.jks +1 -0
  158. package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/build/generated-fake.keystore +1 -0
@@ -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
+ }
@@ -0,0 +1,117 @@
1
+ import { makeAndroidFinding } from "../../audit/androidFinding.js";
2
+ import { makeCandidateEvidence } from "../candidateEvidence.js";
3
+ import { buildAndroidSourceLocation } from "../sourceLocation.js";
4
+ import { classifyDirectExpression } from "../sensitiveData/classifyDirectExpression.js";
5
+ import { classifySensitiveIdentifier } from "../sensitiveData/classifySensitiveIdentifier.js";
6
+ import { collectLoggingEvidence } from "./collectLoggingEvidence.js";
7
+ // Authorization/Bearer/Cookie/session/token identifiers are already covered
8
+ // by classifySensitiveIdentifier's shared vocabulary (no separate header
9
+ // word list here) — a bare string literal equal to a header *name* (e.g.
10
+ // "Authorization" used as a log tag) is deliberately never inspected for
11
+ // content, so it never produces a finding (see module header note above).
12
+ function sensitiveIdentifierNameFor(expression) {
13
+ if (expression === undefined)
14
+ return undefined;
15
+ const classification = classifyDirectExpression(expression);
16
+ if (classification.kind !== "identifier" && classification.kind !== "member-access")
17
+ return undefined;
18
+ const name = expression.replace(/^.*\./, "");
19
+ const result = classifySensitiveIdentifier(name);
20
+ return result ? { sensitiveDataCategory: result } : undefined;
21
+ }
22
+ function isExceptionMessageExpression(expression) {
23
+ if (expression === undefined)
24
+ return false;
25
+ return /\.(message|localizedMessage)$/.test(expression) || /\.(getMessage|stackTraceToString)\s*\(\s*\)$/.test(expression);
26
+ }
27
+ export function analyzeSensitiveLoggingFile(targetRoot, file) {
28
+ const candidates = [];
29
+ const findings = [];
30
+ const matches = collectLoggingEvidence(file.content);
31
+ const limitations = [
32
+ "Bounded lexical source analysis; does not prove a log call executed, that logs are present on a device, or that Crashlytics uploaded data.",
33
+ ];
34
+ const location = (m) => buildAndroidSourceLocation(targetRoot, file.absolutePath, { line: m.line });
35
+ const finding = (m, title, evidenceDetails) => findings.push(makeAndroidFinding({
36
+ ruleId: "android-sensitive-logging",
37
+ title,
38
+ severity: m.debugGuarded ? "minor" : "major",
39
+ confidence: "high",
40
+ description: `${title} via ${m.api}. Static source evidence only; does not prove the call executed or that logs were collected.`,
41
+ manifestPath: file.relativePath,
42
+ identity: `${m.api}:${m.line}`,
43
+ location: { line: m.line },
44
+ evidenceDetails: [...evidenceDetails, m.debugGuarded ? "debug-guard=present" : "debug-guard=none"],
45
+ recommendation: "Remove sensitive values from log statements, or guard them out of release builds entirely.",
46
+ }));
47
+ const candidate = (m, summary, rawValue, confidence = "medium", resolutionState = "resolved") => candidates.push(makeCandidateEvidence({
48
+ ruleId: "android-sensitive-logging",
49
+ category: "android-sensitive-logging",
50
+ confidence,
51
+ modulePath: file.modulePath,
52
+ location: location(m),
53
+ summary,
54
+ rawValue,
55
+ resolutionState,
56
+ staticAnalysisLimitations: limitations,
57
+ }));
58
+ for (const m of matches) {
59
+ if (m.kind === "isloggable") {
60
+ candidate(m, "Log.isLoggable is contextual evidence; it is not a security control by itself", undefined, "low", "not-applicable");
61
+ continue;
62
+ }
63
+ if (m.kind === "print-stack-trace") {
64
+ candidate(m, "printStackTrace() called; review evidence only, does not prove secrets are present", m.throwableExpression, "low", "not-applicable");
65
+ continue;
66
+ }
67
+ if (m.kind === "crashlytics") {
68
+ if (m.api === "Crashlytics.recordException") {
69
+ candidate(m, "Crashlytics recordException() called", undefined, "low", "not-applicable");
70
+ continue;
71
+ }
72
+ if (m.api === "Crashlytics.setCustomKey") {
73
+ const keyLiteral = m.tagExpression !== undefined ? classifyDirectExpression(m.tagExpression) : undefined;
74
+ const sensitive = keyLiteral?.kind === "string-literal" && keyLiteral.literalValue !== undefined ? classifySensitiveIdentifier(keyLiteral.literalValue) : sensitiveIdentifierNameFor(m.tagExpression);
75
+ if (sensitive) {
76
+ finding(m, "Sensitive Crashlytics custom key or value set", [`key=${m.tagExpression}`]);
77
+ }
78
+ else {
79
+ candidate(m, "Crashlytics setCustomKey() called", m.messageExpression, "low");
80
+ }
81
+ continue;
82
+ }
83
+ if (m.api === "Crashlytics.setUserId") {
84
+ const sensitive = sensitiveIdentifierNameFor(m.messageExpression);
85
+ if (sensitive) {
86
+ finding(m, "Direct personal identifier supplied to Crashlytics setUserId", [`value=${m.messageExpression}`]);
87
+ }
88
+ else {
89
+ candidate(m, "Crashlytics setUserId() called; privacy review evidence", m.messageExpression, "low");
90
+ }
91
+ continue;
92
+ }
93
+ // Crashlytics.log — same sensitive-message policy as Log/Timber below.
94
+ }
95
+ // Log / Timber / stdout / Crashlytics.log share one sensitive-argument policy.
96
+ const tagSensitive = sensitiveIdentifierNameFor(m.tagExpression);
97
+ const messageSensitive = sensitiveIdentifierNameFor(m.messageExpression);
98
+ if (tagSensitive || messageSensitive) {
99
+ finding(m, `Direct sensitive value logged via ${m.api}`, [`argument=${messageSensitive ? m.messageExpression : m.tagExpression}`]);
100
+ continue;
101
+ }
102
+ if (isExceptionMessageExpression(m.messageExpression)) {
103
+ candidate(m, "Exception message/stack-trace text logged; content not inspected", m.messageExpression, "low", "not-applicable");
104
+ continue;
105
+ }
106
+ if (m.messageExpression !== undefined) {
107
+ const classification = classifyDirectExpression(m.messageExpression);
108
+ if (classification.kind === "method-call" || classification.kind === "collection-object" || classification.kind === "member-access" || classification.kind === "dynamic-unsupported") {
109
+ candidate(m, `Dynamic ${classification.kind.replace("-", " ")} logged; contents not inspected`, m.messageExpression, "low", "unresolved");
110
+ }
111
+ else if (classification.kind === "identifier") {
112
+ candidate(m, "Dynamic log message logged; identifier not recognized as sensitive by name", m.messageExpression, "low", "unresolved");
113
+ }
114
+ }
115
+ }
116
+ return { candidates, findings };
117
+ }
@@ -0,0 +1,74 @@
1
+ import { sortCandidateEvidence } from "../ordering.js";
2
+ import { discoverSecretSourceFiles } from "../secretCandidates/discoverSecretSourceFiles.js";
3
+ import { analyzeSensitiveLoggingFile } from "./analyzeSensitiveLogging.js";
4
+ // ---------------------------------------------------------------------------
5
+ // v0.4.1 Batch 6 — standalone Android sensitive-logging check result.
6
+ // Follows the exact Batch 4/5 standalone pattern: not called from
7
+ // validateAndroidTarget / any active orchestration, not rendered in reports,
8
+ // no CLI effect. Batch 8 integrates.
9
+ // ---------------------------------------------------------------------------
10
+ export const ANDROID_SENSITIVE_LOGGING_AUDIT_CHECK_ID = "android-sensitive-logging-audit";
11
+ export function auditAndroidSensitiveLogging(targetRoot, detection) {
12
+ if (detection.projectKind === "non-android") {
13
+ return {
14
+ id: ANDROID_SENSITIVE_LOGGING_AUDIT_CHECK_ID,
15
+ category: "android-sensitive-logging",
16
+ title: "Android sensitive-logging audit",
17
+ status: "unsupported",
18
+ requirementLevel: "optional",
19
+ ran: false,
20
+ skipped: true,
21
+ skipInfo: {
22
+ checkId: ANDROID_SENSITIVE_LOGGING_AUDIT_CHECK_ID,
23
+ reason: "Target was not detected as an Android project.",
24
+ requirementLevel: "optional",
25
+ missingCapability: "android-detection",
26
+ verdictImpact: "does not apply to a non-Android target",
27
+ recommendedNextAction: "Re-run against an Android Gradle project, or verify project detection.",
28
+ },
29
+ evidence: [],
30
+ findings: [],
31
+ warnings: [],
32
+ errors: [],
33
+ sourcePaths: [],
34
+ confidence: "unknown",
35
+ environmentRequirements: [],
36
+ candidateEvidence: [],
37
+ };
38
+ }
39
+ const modulePaths = detection.modules.map((m) => m.path);
40
+ const { files, skipped } = discoverSecretSourceFiles(targetRoot, modulePaths);
41
+ const sourceFiles = files.filter((file) => /\.(java|kt)$/.test(file.relativePath));
42
+ const findingsById = new Map();
43
+ const candidatesById = new Map();
44
+ for (const file of sourceFiles) {
45
+ const result = analyzeSensitiveLoggingFile(targetRoot, file);
46
+ for (const f of result.findings)
47
+ findingsById.set(f.id, f);
48
+ for (const c of result.candidates)
49
+ candidatesById.set(c.id, c);
50
+ }
51
+ const findings = [...findingsById.values()].sort((a, b) => a.id.localeCompare(b.id));
52
+ const candidateEvidence = sortCandidateEvidence([...candidatesById.values()]);
53
+ const hasReportableFinding = findings.some((f) => f.severity !== "informational");
54
+ return {
55
+ id: ANDROID_SENSITIVE_LOGGING_AUDIT_CHECK_ID,
56
+ category: "android-sensitive-logging",
57
+ title: "Android sensitive-logging audit",
58
+ status: hasReportableFinding ? "failed" : "passed",
59
+ requirementLevel: "required",
60
+ ran: true,
61
+ skipped: false,
62
+ evidence: [`${sourceFiles.length} Java/Kotlin file(s) scanned`],
63
+ findings,
64
+ warnings: [
65
+ ...skipped.map((s) => `${s.relativePath}: skipped (${s.reason}${s.detail ? `: ${s.detail}` : ""})`),
66
+ "Static analysis only: does not prove log calls executed, that logcat is accessible, or that Crashlytics uploaded data.",
67
+ ],
68
+ errors: [],
69
+ sourcePaths: sourceFiles.map((f) => f.relativePath),
70
+ confidence: skipped.length > 0 ? "medium" : "high",
71
+ environmentRequirements: [],
72
+ candidateEvidence,
73
+ };
74
+ }
@@ -0,0 +1,87 @@
1
+ import { executableMask, lineForOffset } from "../sensitiveData/localSourceContext.js";
2
+ import { splitTopLevelArguments } from "../sensitiveData/splitArguments.js";
3
+ // ---------------------------------------------------------------------------
4
+ // v0.4.1 Batch 6 — bounded direct evidence collection for sensitive-logging
5
+ // sinks (Android Log, Timber, stdout/stderr, printStackTrace, and
6
+ // Crashlytics payload sinks). Purely lexical, over comment/string-masked
7
+ // text with argument text re-sliced from the original content via regex
8
+ // match indices (see sensitiveStorage/collectStorageEvidence.ts for the same
9
+ // pattern and its rationale). Nothing here constructs CandidateEvidence or
10
+ // SecurityFinding.
11
+ // ---------------------------------------------------------------------------
12
+ const LOG_CALL = /\bLog\.(v|d|i|w|e|wtf|println)\s*\(([^;\n]*?)\)/gd;
13
+ const TIMBER_CALL = /\bTimber\.(v|d|i|w|e|wtf|log|tag)\s*\(([^;\n]*?)\)/gd;
14
+ const STDOUT_CALL = /(System\.out|System\.err)\.(print|println)\s*\(([^;\n]*?)\)/gd;
15
+ const KOTLIN_PRINT = /(?<![.\w])(print|println)\s*\(([^;\n]*?)\)/gd;
16
+ const PRINT_STACK_TRACE = /([A-Za-z_][\w.]*)\.printStackTrace\s*\(\s*\)/gd;
17
+ const CRASHLYTICS_CALL = /\b(FirebaseCrashlytics(?:\.getInstance\(\))?|crashlytics)\.(log|setCustomKey|setUserId|recordException)\s*\(([^;\n]*?)\)/gd;
18
+ const IS_LOGGABLE = /\bLog\.isLoggable\s*\(([^;\n]*?)\)/gd;
19
+ const DEBUG_GUARD_WINDOW = 3;
20
+ function realGroup(content, match, group) {
21
+ const span = match.indices?.[group];
22
+ if (!span)
23
+ return match[group];
24
+ return content.slice(span[0], span[1]);
25
+ }
26
+ function isDebugGuarded(content, offset) {
27
+ const startLine = lineForOffset(content, offset) - DEBUG_GUARD_WINDOW;
28
+ const lines = content.split("\n");
29
+ const from = Math.max(0, startLine - 1);
30
+ const window = lines.slice(from, lineForOffset(content, offset)).join("\n");
31
+ return /BuildConfig\.DEBUG|\bDEBUG\b/.test(window);
32
+ }
33
+ function push(list, content, kind, api, offset, extra) {
34
+ list.push({
35
+ kind,
36
+ api,
37
+ debugGuarded: isDebugGuarded(content, offset),
38
+ offset,
39
+ line: lineForOffset(content, offset),
40
+ ...extra,
41
+ });
42
+ }
43
+ export function collectLoggingEvidence(content) {
44
+ const mask = executableMask(content);
45
+ const matches = [];
46
+ for (const match of mask.matchAll(LOG_CALL)) {
47
+ const offset = match.index ?? 0;
48
+ const args = splitTopLevelArguments(realGroup(content, match, 2) ?? "");
49
+ if (match[1] === "println") {
50
+ push(matches, content, "log", `Log.println`, offset, { messageExpression: args[args.length - 1], tagExpression: args[1] });
51
+ continue;
52
+ }
53
+ push(matches, content, "log", `Log.${match[1]}`, offset, { tagExpression: args[0], messageExpression: args[1], throwableExpression: args[2] });
54
+ }
55
+ for (const match of mask.matchAll(IS_LOGGABLE)) {
56
+ const offset = match.index ?? 0;
57
+ push(matches, content, "isloggable", "Log.isLoggable", offset, {});
58
+ }
59
+ for (const match of mask.matchAll(TIMBER_CALL)) {
60
+ const offset = match.index ?? 0;
61
+ const args = splitTopLevelArguments(realGroup(content, match, 2) ?? "");
62
+ push(matches, content, "timber", `Timber.${match[1]}`, offset, { messageExpression: args[0], throwableExpression: args[1] });
63
+ }
64
+ for (const match of mask.matchAll(STDOUT_CALL)) {
65
+ const offset = match.index ?? 0;
66
+ const args = splitTopLevelArguments(realGroup(content, match, 3) ?? "");
67
+ push(matches, content, "stdout", `${match[1]}.${match[2]}`, offset, { messageExpression: args[0] });
68
+ }
69
+ for (const match of mask.matchAll(KOTLIN_PRINT)) {
70
+ const offset = match.index ?? 0;
71
+ const args = splitTopLevelArguments(realGroup(content, match, 2) ?? "");
72
+ push(matches, content, "stdout", match[1], offset, { messageExpression: args[0] });
73
+ }
74
+ for (const match of mask.matchAll(PRINT_STACK_TRACE)) {
75
+ const offset = match.index ?? 0;
76
+ push(matches, content, "print-stack-trace", "printStackTrace", offset, { throwableExpression: match[1] });
77
+ }
78
+ for (const match of mask.matchAll(CRASHLYTICS_CALL)) {
79
+ const offset = match.index ?? 0;
80
+ const args = splitTopLevelArguments(realGroup(content, match, 3) ?? "");
81
+ push(matches, content, "crashlytics", `Crashlytics.${match[2]}`, offset, {
82
+ tagExpression: match[2] === "setCustomKey" ? args[0] : undefined,
83
+ messageExpression: args[match[2] === "setCustomKey" ? 1 : 0],
84
+ });
85
+ }
86
+ return matches.sort((a, b) => a.offset - b.offset);
87
+ }
@@ -0,0 +1 @@
1
+ export { auditAndroidSensitiveLogging, ANDROID_SENSITIVE_LOGGING_AUDIT_CHECK_ID } from "./checkResult.js";