@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.
- package/README.md +269 -47
- package/dist/scripts/audits/runAudit.js +28 -1
- package/dist/scripts/security/validate.js +38 -5
- package/dist/src/audits/core/auditConfig.js +16 -1
- package/dist/src/audits/core/auditRunner.js +6 -2
- package/dist/src/audits/report/auditReportModel.js +7 -0
- package/dist/src/audits/report/renderAuditJsonReport.js +1 -0
- package/dist/src/audits/report/renderAuditTextReport.js +37 -0
- package/dist/src/audits/security/androidAuditIntegration.js +145 -0
- package/dist/src/audits/security/mapAndroidSecurityFindingToAuditIssue.js +116 -0
- package/dist/src/audits/security/mapSecurityFindingToAuditIssue.js +15 -5
- package/dist/src/audits/security/securityAuditAdapter.js +20 -2
- package/dist/src/audits/security/securityAuditTypes.js +37 -0
- package/dist/src/mobile/android/advancedSecurity/backupConfiguration/analyzeBackupConfiguration.js +300 -0
- package/dist/src/mobile/android/advancedSecurity/backupConfiguration/checkResult.js +41 -0
- package/dist/src/mobile/android/advancedSecurity/backupConfiguration/index.js +8 -0
- package/dist/src/mobile/android/advancedSecurity/backupConfiguration/manifestEvidence.js +102 -0
- package/dist/src/mobile/android/advancedSecurity/backupConfiguration/parseBackupRules.js +110 -0
- package/dist/src/mobile/android/advancedSecurity/backupConfiguration/types.js +27 -0
- package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +50 -0
- package/dist/src/mobile/android/advancedSecurity/clipboard/analyzeClipboardSecurity.js +166 -0
- package/dist/src/mobile/android/advancedSecurity/clipboard/checkResult.js +74 -0
- package/dist/src/mobile/android/advancedSecurity/clipboard/collectClipboardEvidence.js +82 -0
- package/dist/src/mobile/android/advancedSecurity/clipboard/index.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/clipboard/types.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/checkResult.js +255 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/index.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintSarif.js +37 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintXml.js +36 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/artifacts.js +95 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/boundedOutput.js +33 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/checkResult.js +173 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/command.js +33 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/index.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/parseDependencyCheckJson.js +133 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/discoverExecutable.js +13 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/index.js +8 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/minimalEnvironment.js +26 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/mutation.js +77 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/osv/checkResult.js +161 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/osv/command.js +25 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/osv/index.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/osv/parseOsvJson.js +136 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/runBoundedExternalTool.js +51 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/runRequestedAndroidExternalTools.js +54 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/checkResult.js +170 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/command.js +28 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/index.js +2 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/parseSemgrepJson.js +141 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/rules.js +105 -0
- package/dist/src/mobile/android/advancedSecurity/externalTools/types.js +107 -0
- package/dist/src/mobile/android/advancedSecurity/fileProvider/checkResult.js +188 -0
- package/dist/src/mobile/android/advancedSecurity/fileProvider/index.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/analyzeFirebaseGoogleServices.js +197 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/checkResult.js +77 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/collectFirebaseServiceEvidence.js +193 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/discoverFirebaseArtifacts.js +128 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/index.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseDatabaseRules.js +55 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirebaseConfig.js +75 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirestoreStorageRules.js +88 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseGoogleServicesConfig.js +55 -0
- package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/types.js +6 -0
- package/dist/src/mobile/android/advancedSecurity/networkSecurity/analyzeNetworkSecurity.js +394 -0
- package/dist/src/mobile/android/advancedSecurity/networkSecurity/checkResult.js +47 -0
- package/dist/src/mobile/android/advancedSecurity/networkSecurity/deriveEffectiveNetworkPolicy.js +52 -0
- package/dist/src/mobile/android/advancedSecurity/networkSecurity/index.js +14 -0
- package/dist/src/mobile/android/advancedSecurity/networkSecurity/manifestEvidence.js +109 -0
- package/dist/src/mobile/android/advancedSecurity/networkSecurity/parseNetworkSecurityConfig.js +186 -0
- package/dist/src/mobile/android/advancedSecurity/networkSecurity/types.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/ordering.js +44 -0
- package/dist/src/mobile/android/advancedSecurity/redaction.js +54 -0
- package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/analyzeReleaseConfiguration.js +221 -0
- package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/checkResult.js +39 -0
- package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/index.js +7 -0
- package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/manifestEvidence.js +19 -0
- package/dist/src/mobile/android/advancedSecurity/resourceReference.js +69 -0
- package/dist/src/mobile/android/advancedSecurity/resourceResolution.js +118 -0
- package/dist/src/mobile/android/advancedSecurity/ruleIds.js +141 -0
- package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +116 -0
- package/dist/src/mobile/android/advancedSecurity/secretCandidates/checkResult.js +80 -0
- package/dist/src/mobile/android/advancedSecurity/secretCandidates/discoverSecretSourceFiles.js +152 -0
- package/dist/src/mobile/android/advancedSecurity/secretCandidates/index.js +9 -0
- package/dist/src/mobile/android/advancedSecurity/secretCandidates/matchSecretCandidates.js +136 -0
- package/dist/src/mobile/android/advancedSecurity/secretCandidates/suppressValue.js +61 -0
- package/dist/src/mobile/android/advancedSecurity/secretCandidates/types.js +11 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveCategories.js +34 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifyDirectExpression.js +99 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifySensitiveIdentifier.js +111 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveData/index.js +3 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveData/localSourceContext.js +90 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveData/splitArguments.js +59 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/analyzeSensitiveLogging.js +117 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/checkResult.js +74 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/collectLoggingEvidence.js +87 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/index.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/types.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/analyzeSensitiveStorage.js +182 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/checkResult.js +74 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/collectStorageEvidence.js +109 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/index.js +1 -0
- package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/types.js +4 -0
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +270 -0
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/checkResult.js +119 -0
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/discoverKeystoreCandidates.js +55 -0
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +81 -0
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/index.js +8 -0
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/types.js +20 -0
- package/dist/src/mobile/android/advancedSecurity/sourceLocation.js +15 -0
- package/dist/src/mobile/android/advancedSecurity/webview/checkResult.js +210 -0
- package/dist/src/mobile/android/advancedSecurity/webview/index.js +1 -0
- package/dist/src/mobile/android/gradle/moduleMetadataExtractor.js +123 -17
- package/dist/src/mobile/android/manifest/parseAndroidManifest.js +27 -4
- package/dist/src/mobile/android/manifest/xml/parseXml.js +25 -9
- package/dist/src/mobile/android/report/model.js +32 -1
- package/dist/src/mobile/android/report/renderAndroidReport.js +63 -0
- package/dist/src/mobile/android/validate/validateAndroidTarget.js +146 -3
- package/dist/src/mobile/android/validation/checkResult.js +7 -0
- package/dist/src/securityValidation/commandRunner.js +10 -1
- package/dist/src/securityValidation/validate/cliOptions.js +46 -0
- package/package.json +1 -1
- package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/debug/res/xml/ambiguous_backup_rules.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/ambiguous_backup_rules.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/backup_rules.xml +5 -0
- package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/broad_backup_rules.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/data_extraction_rules.xml +10 -0
- package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/unsupported_domain_rules.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/malformed_backup_rules.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/unsupported_root.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/debug/res/xml/ambiguous_config.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/ambiguous_config.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/base_cleartext_false.xml +8 -0
- package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/broad_and_duplicate_domains.xml +8 -0
- package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/empty_domain.xml +6 -0
- package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/malformed_pin.xml +11 -0
- package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/network_security_config.xml +26 -0
- package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/malformed_network_security_config.xml +6 -0
- package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/unsupported_root.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-dynamic.gradle +7 -0
- package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-false.gradle +9 -0
- package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-true.gradle +13 -0
- package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-false.gradle.kts +7 -0
- package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-true.gradle.kts +10 -0
- package/tests/fixtures/android/advanced-security-fixtures/release-configuration/missing-release-build-type.gradle.kts +7 -0
- package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/debug/res/xml/network_security_config.xml +8 -0
- package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/backup_rules.xml +4 -0
- package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/network_security_config.xml +8 -0
- package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-b/src/main/res/xml/malformed_network_security_config.xml +7 -0
- package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.java +8 -0
- package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.kt +9 -0
- package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle +11 -0
- package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle.kts +11 -0
- package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/google-services.json +16 -0
- package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/local.properties +3 -0
- package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/binary.properties +0 -0
- package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/oversized.json +1 -0
- package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/app/fake-release-keystore.jks +1 -0
- 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 =
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|