@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
@@ -133,6 +133,43 @@ export function renderAuditTextReport(model) {
133
133
  }
134
134
  }
135
135
  lines.push("");
136
+ // v0.4.2 Batch 3 -- bounded summary only, mirroring the "Security
137
+ // validation summary" section above exactly. Confirmed mapped Android
138
+ // findings are NOT re-listed here -- they already appear in the "Issues"
139
+ // section below like any other AuditIssue (see spec section 12: "the
140
+ // existing generic issues section remains the detailed presentation for
141
+ // confirmed mapped Android findings"). CandidateEvidence bodies never
142
+ // appear here either -- only counts, with a pointer to the full Android
143
+ // report for detail.
144
+ lines.push("Android security validation summary");
145
+ lines.push(divider("-"));
146
+ if (!model.androidSecurity.summary.requested) {
147
+ lines.push(" (not requested -- add --android alongside --types security to include it)");
148
+ }
149
+ else {
150
+ const android = model.androidSecurity.summary;
151
+ const candidates = model.androidSecurity.candidates;
152
+ const applicabilityNote = android.applicable === false ? " (target is not an Android project)" : "";
153
+ lines.push(` Status: ${android.status}${applicabilityNote}`);
154
+ lines.push(` Verdict: ${android.verdict ?? "(unknown)"}`);
155
+ lines.push(` Checks: total=${android.totalChecks} passed=${android.passedChecks} withFindings=${android.checksWithFindings} candidateOnly=${android.candidateOnlyChecks} skipped=${android.skippedChecks} inconclusive=${android.inconclusiveChecks} failed=${android.failedChecks}`);
156
+ lines.push(` Findings: confirmed=${android.confirmedFindingCount} mappedIssues=${android.mappedIssueCount}`);
157
+ lines.push(` Candidates (review evidence, not confirmed vulnerabilities): total=${candidates.totalCount} checksWithCandidates=${candidates.checksWithCandidates}`);
158
+ if (android.warnings.length > 0) {
159
+ lines.push(` Warnings: ${android.warnings.length}`);
160
+ }
161
+ if (android.errors.length > 0) {
162
+ lines.push(` Errors: ${android.errors.length}`);
163
+ for (const e of android.errors.slice(0, 5)) {
164
+ lines.push(` ${sanitizeLine(e)}`);
165
+ }
166
+ }
167
+ if (android.reportPaths.text || android.reportPaths.json) {
168
+ lines.push(` Full report: ${android.reportPaths.text ?? "(none)"} / ${android.reportPaths.json ?? "(none)"}`);
169
+ }
170
+ lines.push(" Static analysis only -- does not prove runtime behavior, APK/AAB validity, Play compliance, or pentest results.");
171
+ }
172
+ lines.push("");
136
173
  lines.push("Issue summary");
137
174
  lines.push(divider("-"));
138
175
  lines.push(` Total issues: ${model.summary.totalIssues}`);
@@ -0,0 +1,145 @@
1
+ import path from "node:path";
2
+ import { validateAndroidTarget } from "../../mobile/android/validate/validateAndroidTarget.js";
3
+ import { toAndroidReportModel } from "../../mobile/android/report/model.js";
4
+ import { writeAndroidReportFiles } from "../../mobile/android/report/writeAndroidReportFiles.js";
5
+ import { reportFilenamePrefix } from "../../securityValidation/validate/resolveTarget.js";
6
+ import { mapAndroidSecurityFindingToAuditIssue } from "./mapAndroidSecurityFindingToAuditIssue.js";
7
+ import { ANDROID_AUDIT_NOT_REQUESTED_SUMMARY, EMPTY_ANDROID_AUDIT_CANDIDATE_SUMMARY, } from "./securityAuditTypes.js";
8
+ const ANDROID_REPORT_SUBDIR = "android";
9
+ // Bounded, redacted — never the thrown value's stack, never raw child-process
10
+ // output (there is none at this boundary; validateAndroidTarget itself never
11
+ // spawns a process unless Gradle/external-tool options are requested, which
12
+ // Batch 2 never supplies).
13
+ const MAX_ANDROID_ERROR_MESSAGE_LENGTH = 500;
14
+ function boundErrorMessage(message) {
15
+ return message.length <= MAX_ANDROID_ERROR_MESSAGE_LENGTH
16
+ ? message
17
+ : `${message.slice(0, MAX_ANDROID_ERROR_MESSAGE_LENGTH)}... [truncated, ${message.length} chars total]`;
18
+ }
19
+ function failedSummary(errorMessage) {
20
+ return {
21
+ ...ANDROID_AUDIT_NOT_REQUESTED_SUMMARY,
22
+ requested: true,
23
+ applicable: null,
24
+ status: "failed",
25
+ errors: [boundErrorMessage(errorMessage)],
26
+ };
27
+ }
28
+ // Exact-duplicate dedupe by AuditIssue id only, first occurrence wins —
29
+ // mirrors validateAndroidTarget's own findingsById convention. Iterates
30
+ // checks in their own fixed order (never re-sorted) and each check's own
31
+ // findings in their own order — never the top-level, already-flattened
32
+ // result.findings, which has lost per-check identity this mapper needs as
33
+ // context. Exported standalone so aggregation correctness (ordering,
34
+ // dedup, provenance) can be tested against small literal AndroidCheckResult
35
+ // fixtures without needing a full AndroidValidationResult.
36
+ export function aggregateAndroidIssues(checks, reportReference) {
37
+ const issuesById = new Map();
38
+ for (const check of checks) {
39
+ for (const finding of check.findings) {
40
+ const issue = mapAndroidSecurityFindingToAuditIssue(finding, {
41
+ checkId: check.id,
42
+ checkCategory: check.category,
43
+ checkConfidence: check.confidence,
44
+ reportReference,
45
+ });
46
+ if (!issuesById.has(issue.id)) {
47
+ issuesById.set(issue.id, issue);
48
+ }
49
+ }
50
+ }
51
+ return [...issuesById.values()];
52
+ }
53
+ export function buildCandidateSummary(checks) {
54
+ const byConfidence = { ...EMPTY_ANDROID_AUDIT_CANDIDATE_SUMMARY.byConfidence };
55
+ const byResolutionState = { ...EMPTY_ANDROID_AUDIT_CANDIDATE_SUMMARY.byResolutionState };
56
+ const byCategory = {};
57
+ let totalCount = 0;
58
+ let checksWithCandidates = 0;
59
+ for (const check of checks) {
60
+ const candidates = check.candidateEvidence ?? [];
61
+ if (candidates.length === 0)
62
+ continue;
63
+ checksWithCandidates += 1;
64
+ for (const candidate of candidates) {
65
+ totalCount += 1;
66
+ byConfidence[candidate.confidence] += 1;
67
+ byResolutionState[candidate.resolutionState] += 1;
68
+ byCategory[candidate.category] = (byCategory[candidate.category] ?? 0) + 1;
69
+ }
70
+ }
71
+ // Deterministic key order regardless of check/candidate traversal order.
72
+ const sortedByCategory = Object.fromEntries(Object.entries(byCategory).sort(([a], [b]) => a.localeCompare(b)));
73
+ return { totalCount, checksWithCandidates, byConfidence, byResolutionState, byCategory: sortedByCategory };
74
+ }
75
+ export async function runAndroidAuditIntegration(options) {
76
+ const runAndroidValidation = options.runAndroidValidation ?? validateAndroidTarget;
77
+ let result;
78
+ try {
79
+ result = await runAndroidValidation({
80
+ toolRoot: options.toolRoot,
81
+ targetPath: options.targetPathArg,
82
+ });
83
+ }
84
+ catch (error) {
85
+ const message = error instanceof Error ? error.message : String(error);
86
+ return { issues: [], summary: failedSummary(`Android validation failed to run: ${message}`) };
87
+ }
88
+ const isApplicable = result.detection.projectKind !== "non-android";
89
+ const reportModel = toAndroidReportModel(result, { profile: "android" });
90
+ const reportDir = path.join(options.toolRoot, "reports", "security", ANDROID_REPORT_SUBDIR);
91
+ const prefix = reportFilenamePrefix({
92
+ isSelf: result.target.local.isSelf,
93
+ packageName: result.target.local.packageName,
94
+ packageVersion: result.target.local.packageVersion,
95
+ targetRoot: result.target.local.targetRoot,
96
+ });
97
+ let reportPaths;
98
+ let writeErrors = [];
99
+ try {
100
+ const written = writeAndroidReportFiles({ outDir: reportDir, prefix, report: reportModel, formats: ["text", "json"] });
101
+ reportPaths = { text: written.textPath, json: written.jsonPath };
102
+ }
103
+ catch (error) {
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ reportPaths = { text: null, json: null };
106
+ writeErrors = [boundErrorMessage(`Failed to write Android report files: ${message}`)];
107
+ }
108
+ const issues = aggregateAndroidIssues(result.checks, reportPaths);
109
+ const passedChecks = result.checks.filter((c) => c.status === "passed").length;
110
+ const skippedChecks = result.checks.filter((c) => c.status === "skipped").length;
111
+ const inconclusiveChecks = result.checks.filter((c) => c.status === "inconclusive").length;
112
+ const failedChecks = result.checks.filter((c) => c.status === "failed" || c.status === "error").length;
113
+ const checksWithFindings = result.checks.filter((c) => c.findings.length > 0).length;
114
+ const candidateOnlyChecks = result.checks.filter((c) => c.findings.length === 0 && (c.candidateEvidence?.length ?? 0) > 0).length;
115
+ const candidateEvidenceCount = result.checks.reduce((sum, c) => sum + (c.candidateEvidence?.length ?? 0), 0);
116
+ const summary = {
117
+ requested: true,
118
+ applicable: isApplicable,
119
+ status: "completed",
120
+ verdict: result.verdict,
121
+ totalChecks: result.checks.length,
122
+ passedChecks,
123
+ checksWithFindings,
124
+ candidateOnlyChecks,
125
+ skippedChecks,
126
+ inconclusiveChecks,
127
+ failedChecks,
128
+ confirmedFindingCount: result.findings.length,
129
+ mappedIssueCount: issues.length,
130
+ candidateEvidenceCount,
131
+ // No Gradle/external-tool programmatic passthrough exists yet in Batch 2
132
+ // (see securityAuditTypes.ts's AndroidAuditSummary comment) — the default
133
+ // request always executes zero of either.
134
+ requestedGradleOperationCount: 0,
135
+ executedGradleOperationCount: 0,
136
+ requestedExternalToolCount: 0,
137
+ executedExternalToolCount: 0,
138
+ skippedExternalToolCount: 0,
139
+ reportPaths,
140
+ warnings: result.warnings,
141
+ errors: [...result.errors, ...writeErrors],
142
+ candidateSummary: buildCandidateSummary(result.checks),
143
+ };
144
+ return { issues, summary };
145
+ }
@@ -0,0 +1,116 @@
1
+ import { makeAuditEvidence } from "../core/auditIssue.js";
2
+ import { isBlockingSecuritySeverity, mapSecuritySeverityToAuditSeverity } from "./mapSecurityFindingToAuditIssue.js";
3
+ // ---------------------------------------------------------------------------
4
+ // v0.4.2 Batch 1 — pure Android SecurityFinding -> AuditIssue mapper.
5
+ //
6
+ // Android checks already reuse SecurityFinding as-is (see
7
+ // src/mobile/android/audit/androidFinding.ts's own header comment) — there is
8
+ // no parallel "AndroidSecurityFinding" type. What Android findings lose by
9
+ // reusing the generic SecurityFinding shape is per-family taxonomy: every
10
+ // Android finding's own `category` is the single shared value "static-scan"
11
+ // (a SecurityCheckCategory), while the *specific* Android family (backup,
12
+ // secrets, webview, ...) only lives on the owning AndroidCheckResult.category.
13
+ // This mapper therefore takes a small AndroidAuditMappingContext alongside
14
+ // the finding — supplied by the (future Batch 2) caller that already has the
15
+ // owning AndroidCheckResult in hand — rather than trying to recover that
16
+ // context by parsing the finding's own opaque id/evidence text.
17
+ //
18
+ // Reuses mapSecurityFindingToAuditIssue's severity/blocking policy exactly
19
+ // (SecuritySeverity is the same shared type for Android and non-Android
20
+ // findings) instead of defining a second severity table. Pure, no I/O, no
21
+ // mutation, no CandidateEvidence handling — CandidateEvidence is a distinct
22
+ // type (see src/mobile/android/advancedSecurity/candidateEvidence.ts) and
23
+ // cannot be passed here at all: this function's parameter type is
24
+ // SecurityFinding, not CandidateEvidence.
25
+ // ---------------------------------------------------------------------------
26
+ export const ANDROID_SECURITY_AUDIT_DETECTOR_ID = "android-security-validation-adapter";
27
+ const DEFAULT_CONFIDENCE = "medium";
28
+ const DEFAULT_FALSE_POSITIVE_RISK = "medium";
29
+ // MobileConfidence carries an extra "unknown" state SecurityFinding-derived
30
+ // AuditConfidence has no equivalent for; mapped to the same conservative
31
+ // "medium" default the generic security mapper uses when no per-finding
32
+ // confidence exists at all, rather than inventing a fourth AuditConfidence
33
+ // value.
34
+ function mapCheckConfidenceToAuditConfidence(confidence) {
35
+ if (confidence === "high" || confidence === "medium" || confidence === "low")
36
+ return confidence;
37
+ return DEFAULT_CONFIDENCE;
38
+ }
39
+ // makeAndroidFinding (src/mobile/android/audit/androidFinding.ts) is the sole
40
+ // place Android findings compose their `evidence` string, always in the exact
41
+ // form `location=<relativePath>(:<line>)?` (optionally followed by further
42
+ // ` | `-joined detail segments). Extracting the line from that well-known,
43
+ // single-producer format is not fabricating a source position — it is
44
+ // reading a real value the same codebase already wrote, deterministically.
45
+ // Guarded so a value is only trusted when its path matches the finding's own
46
+ // affectedFiles[0]; anything else is left absent rather than guessed.
47
+ const LOCATION_EVIDENCE_PATTERN = /(?:^|\s\|\s)location=([^\s|]+)/;
48
+ function extractAndroidFindingLine(finding) {
49
+ if (!finding.evidence)
50
+ return undefined;
51
+ const relativePath = finding.affectedFiles?.[0];
52
+ if (!relativePath)
53
+ return undefined;
54
+ const match = LOCATION_EVIDENCE_PATTERN.exec(finding.evidence);
55
+ if (!match)
56
+ return undefined;
57
+ const [locationPath, lineText] = match[1].split(":");
58
+ if (locationPath !== relativePath || lineText === undefined)
59
+ return undefined;
60
+ const line = Number(lineText);
61
+ return Number.isInteger(line) && line > 0 ? line : undefined;
62
+ }
63
+ export function mapAndroidSecurityFindingToAuditIssue(finding, context) {
64
+ const severity = mapSecuritySeverityToAuditSeverity(finding.severity);
65
+ const isBlockingSeverity = isBlockingSecuritySeverity(finding.severity);
66
+ const relativePath = finding.affectedFiles?.[0];
67
+ const line = extractAndroidFindingLine(finding);
68
+ const provenanceSuffix = context.externalToolId ? `, tool=${context.externalToolId}` : "";
69
+ const evidence = finding.evidence
70
+ ? [
71
+ makeAuditEvidence({
72
+ kind: "observation",
73
+ message: `Android security validation (${context.checkId}${provenanceSuffix}): ${finding.title}`,
74
+ excerpt: finding.evidence,
75
+ filePath: relativePath,
76
+ line,
77
+ source: `android-security-validation:${context.checkId}`,
78
+ confidence: mapCheckConfidenceToAuditConfidence(context.checkConfidence),
79
+ }),
80
+ ]
81
+ : [];
82
+ const reportPath = context.reportReference?.json ?? context.reportReference?.text ?? undefined;
83
+ if (reportPath) {
84
+ evidence.push(makeAuditEvidence({
85
+ kind: "reference",
86
+ message: "Original Android security-validation report",
87
+ filePath: reportPath,
88
+ source: "android-security-validation:report",
89
+ confidence: DEFAULT_CONFIDENCE,
90
+ }));
91
+ }
92
+ return {
93
+ id: `android:${context.checkId}:${finding.id}`,
94
+ auditType: "security",
95
+ detectorId: ANDROID_SECURITY_AUDIT_DETECTOR_ID,
96
+ title: finding.title,
97
+ description: finding.description,
98
+ severity,
99
+ confidence: mapCheckConfidenceToAuditConfidence(context.checkConfidence),
100
+ falsePositiveRisk: DEFAULT_FALSE_POSITIVE_RISK,
101
+ // Deliberately the Android-specific check category (e.g.
102
+ // "android-backup-configuration"), not finding.category — every Android
103
+ // finding's own `category` is the single shared "static-scan" value, so
104
+ // reusing it here would make every mapped Android issue's category
105
+ // uselessly uniform. See module header comment.
106
+ category: context.checkCategory,
107
+ evidence,
108
+ affectedFiles: finding.affectedFiles ?? [],
109
+ recommendedAction: finding.recommendation ?? "Review the Android security validation report for remediation guidance.",
110
+ suggestedFixStrategy: finding.recommendation ?? "See the linked Android security validation report for details.",
111
+ validationCommands: ["npm run security:validate -- --profile android"],
112
+ releaseBlocking: isBlockingSeverity,
113
+ implementationBlocking: isBlockingSeverity,
114
+ autoFixEligible: false,
115
+ };
116
+ }
@@ -31,12 +31,22 @@ const SEVERITY_MAP = {
31
31
  // false positives) nor discounts it as unreliable.
32
32
  const DEFAULT_CONFIDENCE = "medium";
33
33
  const DEFAULT_FALSE_POSITIVE_RISK = "medium";
34
+ // Exported so other SecurityFinding-consuming mappers (e.g. the Android
35
+ // audit-adapter mapper, v0.4.2 Batch 1) reuse the same conservative
36
+ // severity policy instead of defining a second one.
37
+ export function mapSecuritySeverityToAuditSeverity(severity) {
38
+ return SEVERITY_MAP[severity];
39
+ }
40
+ // Mirrors verdict.ts's calculateVerdict: blocker and major findings are what
41
+ // drive a "not-ready-security-blocker-remains" verdict — minor/informational
42
+ // findings never block release or implementation on their own. Exported for
43
+ // reuse by other SecurityFinding-consuming mappers.
44
+ export function isBlockingSecuritySeverity(severity) {
45
+ return severity === "blocker" || severity === "major";
46
+ }
34
47
  export function mapSecurityFindingToAuditIssue(finding) {
35
- const severity = SEVERITY_MAP[finding.severity];
36
- // Mirrors verdict.ts's calculateVerdict: blocker and major findings are
37
- // what drive a "not-ready-security-blocker-remains" verdict — minor/
38
- // informational findings never block release or implementation on their own.
39
- const isBlockingSeverity = finding.severity === "blocker" || finding.severity === "major";
48
+ const severity = mapSecuritySeverityToAuditSeverity(finding.severity);
49
+ const isBlockingSeverity = isBlockingSecuritySeverity(finding.severity);
40
50
  const evidence = finding.evidence
41
51
  ? [
42
52
  makeAuditEvidence({
@@ -6,7 +6,9 @@ import { writeSecurityReportFiles } from "../../securityValidation/report/writeS
6
6
  import { DEFAULT_SECURITY_CHECKS, DEFAULT_SECURITY_PROFILE } from "../../securityValidation/validate/cliOptions.js";
7
7
  import { verdictToHumanLabel } from "../../securityValidation/validate/verdict.js";
8
8
  import { mapSecurityFindingToAuditIssue } from "./mapSecurityFindingToAuditIssue.js";
9
- export async function runSecurityAuditAdapter(options) {
9
+ import { runAndroidAuditIntegration } from "./androidAuditIntegration.js";
10
+ import { ANDROID_AUDIT_NOT_REQUESTED_SUMMARY, } from "./securityAuditTypes.js";
11
+ export async function runSecurityAuditAdapter(options, dependencies = {}) {
10
12
  const { toolRoot, config } = options;
11
13
  const summary = await runSecurityValidation({
12
14
  cwd: toolRoot,
@@ -60,7 +62,23 @@ export async function runSecurityAuditAdapter(options) {
60
62
  verdictReasonSummary: summary.verdictReasonSummary ?? null,
61
63
  reportPaths: { text: textPath, json: jsonPath },
62
64
  };
63
- return { issues, summary: securitySummary };
65
+ // v0.4.2 Batch 2 — Android integration is strictly additive: existing
66
+ // non-Android issues/summary above are computed identically whether or not
67
+ // Android is requested, and Android issues are always appended after them,
68
+ // never interleaved or reordered.
69
+ let androidIssues = [];
70
+ let androidSummary = ANDROID_AUDIT_NOT_REQUESTED_SUMMARY;
71
+ if (options.android?.enabled) {
72
+ const androidResult = await runAndroidAuditIntegration({
73
+ toolRoot,
74
+ targetPathArg: config.targetPathArg,
75
+ request: options.android,
76
+ runAndroidValidation: dependencies.runAndroidValidation,
77
+ });
78
+ androidIssues = androidResult.issues;
79
+ androidSummary = androidResult.summary;
80
+ }
81
+ return { issues: [...issues, ...androidIssues], summary: securitySummary, android: androidSummary };
64
82
  }
65
83
  function buildRunScopedReportSuffix(startedAt, finishedAt) {
66
84
  return `${sanitizeTimestamp(startedAt)}-${sanitizeTimestamp(finishedAt)}-${process.pid}`;
@@ -15,3 +15,40 @@ export const SECURITY_AUDIT_NOT_RUN_SUMMARY = {
15
15
  verdictReasonSummary: null,
16
16
  reportPaths: { text: null, json: null },
17
17
  };
18
+ // Mirrors SecurityAuditReportSummary's own `ran`-first, always-present
19
+ // convention: a status literal rather than inferring "did it run" from
20
+ // nullable fields, so callers/tests can switch on one value instead of
21
+ // reconstructing a state machine from several optional fields.
22
+ export const ANDROID_AUDIT_STATUSES = ["not-requested", "not-applicable", "completed", "failed"];
23
+ export const EMPTY_ANDROID_AUDIT_CANDIDATE_SUMMARY = {
24
+ totalCount: 0,
25
+ checksWithCandidates: 0,
26
+ byConfidence: { unknown: 0, low: 0, medium: 0, high: 0 },
27
+ byResolutionState: { resolved: 0, unresolved: 0, missing: 0, malformed: 0, unsupported: 0, "not-applicable": 0 },
28
+ byCategory: {},
29
+ };
30
+ export const ANDROID_AUDIT_NOT_REQUESTED_SUMMARY = {
31
+ requested: false,
32
+ applicable: null,
33
+ status: "not-requested",
34
+ verdict: null,
35
+ totalChecks: 0,
36
+ passedChecks: 0,
37
+ checksWithFindings: 0,
38
+ candidateOnlyChecks: 0,
39
+ skippedChecks: 0,
40
+ inconclusiveChecks: 0,
41
+ failedChecks: 0,
42
+ confirmedFindingCount: 0,
43
+ mappedIssueCount: 0,
44
+ candidateEvidenceCount: 0,
45
+ requestedGradleOperationCount: 0,
46
+ executedGradleOperationCount: 0,
47
+ requestedExternalToolCount: 0,
48
+ executedExternalToolCount: 0,
49
+ skippedExternalToolCount: 0,
50
+ reportPaths: { text: null, json: null },
51
+ warnings: [],
52
+ errors: [],
53
+ candidateSummary: EMPTY_ANDROID_AUDIT_CANDIDATE_SUMMARY,
54
+ };