@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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { sortCandidateEvidence } from "../../ordering.js";
|
|
3
|
+
import { discoverAllowlistedExecutable } from "../discoverExecutable.js";
|
|
4
|
+
import { buildMinimalEnvironment } from "../minimalEnvironment.js";
|
|
5
|
+
import { runBoundedExternalTool, buildCommandSummary, VERSION_PROBE_TIMEOUT_MS } from "../runBoundedExternalTool.js";
|
|
6
|
+
import { buildOsvArgs, osvCommandFamilyForVersion, parseOsvMajorVersion } from "./command.js";
|
|
7
|
+
import { parseOsvJson } from "./parseOsvJson.js";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// v0.4.1 Batch 7 — standalone Android OSV-Scanner check. Not called from
|
|
10
|
+
// validateAndroidTarget or any active orchestration; not rendered in
|
|
11
|
+
// reports; no CLI effect. Batch 8 integrates.
|
|
12
|
+
//
|
|
13
|
+
// OSV is the one Batch 7 tool that may legitimately need network access
|
|
14
|
+
// (its vulnerability database service). Network is denied by default —
|
|
15
|
+
// running OSV against the network requires both an explicit request for
|
|
16
|
+
// "osv" AND networkPolicy "allow-for-requested-tool"; anything else skips.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
export const ANDROID_OSV_AUDIT_CHECK_ID = "android-osv-audit";
|
|
19
|
+
const OSV_CANDIDATES = ["osv-scanner", "osv-scanner.exe"];
|
|
20
|
+
const OSV_TIMEOUT_MS = 5 * 60_000;
|
|
21
|
+
function skippedCheck(reason, environmentRequirements = []) {
|
|
22
|
+
return {
|
|
23
|
+
id: ANDROID_OSV_AUDIT_CHECK_ID,
|
|
24
|
+
category: "android-osv",
|
|
25
|
+
title: "Android OSV-Scanner audit",
|
|
26
|
+
status: "skipped",
|
|
27
|
+
requirementLevel: "optional",
|
|
28
|
+
ran: false,
|
|
29
|
+
skipped: true,
|
|
30
|
+
skipInfo: {
|
|
31
|
+
checkId: ANDROID_OSV_AUDIT_CHECK_ID,
|
|
32
|
+
reason,
|
|
33
|
+
requirementLevel: "optional",
|
|
34
|
+
missingCapability: "osv-scanner-executable-or-network",
|
|
35
|
+
verdictImpact: "does not apply until Batch 8 integration",
|
|
36
|
+
recommendedNextAction: "Install osv-scanner locally and/or authorize network access, or run again after Batch 8 wires optional-tool selection into the CLI.",
|
|
37
|
+
},
|
|
38
|
+
evidence: [],
|
|
39
|
+
findings: [],
|
|
40
|
+
warnings: [],
|
|
41
|
+
errors: [],
|
|
42
|
+
sourcePaths: [],
|
|
43
|
+
confidence: "unknown",
|
|
44
|
+
environmentRequirements,
|
|
45
|
+
candidateEvidence: [],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export async function auditAndroidOsv(options) {
|
|
49
|
+
const targetRoot = path.resolve(options.targetRoot);
|
|
50
|
+
if (options.networkPolicy !== "allow-for-requested-tool") {
|
|
51
|
+
return skippedCheck("Network access was not authorized for OSV-Scanner (networkPolicy=deny), and no supported offline/local database mode was used.");
|
|
52
|
+
}
|
|
53
|
+
const discovered = (options.discover ?? (() => discoverAllowlistedExecutable(OSV_CANDIDATES, targetRoot)))();
|
|
54
|
+
if (!discovered.available) {
|
|
55
|
+
return skippedCheck("osv-scanner executable was not found on PATH.", ["Requires an osv-scanner executable on PATH."]);
|
|
56
|
+
}
|
|
57
|
+
const env = buildMinimalEnvironment();
|
|
58
|
+
const versionOutcome = await runBoundedExternalTool(options.executor, { command: discovered.command, args: ["--version"], cwd: targetRoot, timeoutMs: VERSION_PROBE_TIMEOUT_MS, env });
|
|
59
|
+
if (versionOutcome.result.timedOut || versionOutcome.result.exitCode === null) {
|
|
60
|
+
return skippedCheck("osv-scanner version probe failed or timed out.");
|
|
61
|
+
}
|
|
62
|
+
const rawVersion = versionOutcome.stdout.trim();
|
|
63
|
+
const majorVersion = parseOsvMajorVersion(rawVersion);
|
|
64
|
+
const family = osvCommandFamilyForVersion(majorVersion);
|
|
65
|
+
if (!family) {
|
|
66
|
+
return skippedCheck(`osv-scanner version "${rawVersion || "(unknown)"}" is not a supported version family.`);
|
|
67
|
+
}
|
|
68
|
+
const args = buildOsvArgs(family, targetRoot);
|
|
69
|
+
const commandSummary = buildCommandSummary(discovered.basename, args, targetRoot);
|
|
70
|
+
const analysisOutcome = await runBoundedExternalTool(options.executor, { command: discovered.command, args, cwd: targetRoot, timeoutMs: OSV_TIMEOUT_MS, env });
|
|
71
|
+
const warnings = [];
|
|
72
|
+
if (analysisOutcome.stdoutTruncated)
|
|
73
|
+
warnings.push("stdout was truncated; JSON output was not parsed as complete.");
|
|
74
|
+
if (analysisOutcome.stderrTruncated)
|
|
75
|
+
warnings.push("stderr was truncated.");
|
|
76
|
+
if (analysisOutcome.result.timedOut) {
|
|
77
|
+
return {
|
|
78
|
+
id: ANDROID_OSV_AUDIT_CHECK_ID,
|
|
79
|
+
category: "android-osv",
|
|
80
|
+
title: "Android OSV-Scanner audit",
|
|
81
|
+
status: "inconclusive",
|
|
82
|
+
requirementLevel: "optional",
|
|
83
|
+
ran: true,
|
|
84
|
+
skipped: false,
|
|
85
|
+
evidence: [`version=${rawVersion}`, `commandFamily=${family}`],
|
|
86
|
+
findings: [],
|
|
87
|
+
warnings: [...warnings, `osv-scanner timed out after ${OSV_TIMEOUT_MS}ms.`],
|
|
88
|
+
errors: [],
|
|
89
|
+
sourcePaths: [],
|
|
90
|
+
confidence: "medium",
|
|
91
|
+
environmentRequirements: [],
|
|
92
|
+
candidateEvidence: [],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (analysisOutcome.stdoutTruncated) {
|
|
96
|
+
return {
|
|
97
|
+
id: ANDROID_OSV_AUDIT_CHECK_ID,
|
|
98
|
+
category: "android-osv",
|
|
99
|
+
title: "Android OSV-Scanner audit",
|
|
100
|
+
status: "inconclusive",
|
|
101
|
+
requirementLevel: "optional",
|
|
102
|
+
ran: true,
|
|
103
|
+
skipped: false,
|
|
104
|
+
evidence: [`version=${rawVersion}`, `commandFamily=${family}`],
|
|
105
|
+
findings: [],
|
|
106
|
+
warnings,
|
|
107
|
+
errors: [],
|
|
108
|
+
sourcePaths: [],
|
|
109
|
+
confidence: "low",
|
|
110
|
+
environmentRequirements: [],
|
|
111
|
+
candidateEvidence: [],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const parsed = parseOsvJson(analysisOutcome.stdout, targetRoot);
|
|
115
|
+
// osv-scanner exits 1 when vulnerabilities are found — a service/network
|
|
116
|
+
// failure exits with a different code and typically no valid JSON.
|
|
117
|
+
const isFindingsExitCode = analysisOutcome.result.exitCode === 0 || analysisOutcome.result.exitCode === 1;
|
|
118
|
+
if (parsed.malformed) {
|
|
119
|
+
return {
|
|
120
|
+
id: ANDROID_OSV_AUDIT_CHECK_ID,
|
|
121
|
+
category: "android-osv",
|
|
122
|
+
title: "Android OSV-Scanner audit",
|
|
123
|
+
status: isFindingsExitCode ? "inconclusive" : "failed",
|
|
124
|
+
requirementLevel: "optional",
|
|
125
|
+
ran: true,
|
|
126
|
+
skipped: false,
|
|
127
|
+
evidence: [`version=${rawVersion}`, `commandFamily=${family}`, `exitCode=${analysisOutcome.result.exitCode}`],
|
|
128
|
+
findings: [],
|
|
129
|
+
warnings,
|
|
130
|
+
errors: isFindingsExitCode ? [] : ["osv-scanner exited with a non-findings error and produced no parseable JSON report."],
|
|
131
|
+
sourcePaths: [],
|
|
132
|
+
confidence: "low",
|
|
133
|
+
environmentRequirements: [],
|
|
134
|
+
candidateEvidence: [],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (parsed.truncated)
|
|
138
|
+
warnings.push("Vulnerability count exceeded the bounded limit; results were truncated.");
|
|
139
|
+
const findingsById = new Map(parsed.findings.map((f) => [f.id, f]));
|
|
140
|
+
const candidatesById = new Map(parsed.candidates.map((c) => [c.id, c]));
|
|
141
|
+
const findings = [...findingsById.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
142
|
+
const candidateEvidence = sortCandidateEvidence([...candidatesById.values()]);
|
|
143
|
+
const status = findings.length > 0 ? "failed" : isFindingsExitCode ? "passed" : "error";
|
|
144
|
+
return {
|
|
145
|
+
id: ANDROID_OSV_AUDIT_CHECK_ID,
|
|
146
|
+
category: "android-osv",
|
|
147
|
+
title: "Android OSV-Scanner audit",
|
|
148
|
+
status,
|
|
149
|
+
requirementLevel: "optional",
|
|
150
|
+
ran: true,
|
|
151
|
+
skipped: false,
|
|
152
|
+
evidence: [`version=${rawVersion}`, `commandFamily=${family}`, `vulnerabilities=${parsed.vulnerabilityCount}`, `command=${commandSummary}`, "networkPolicy=allow-for-requested-tool"],
|
|
153
|
+
findings,
|
|
154
|
+
warnings,
|
|
155
|
+
errors: status === "error" ? ["osv-scanner exited with a non-findings error."] : [],
|
|
156
|
+
sourcePaths: [],
|
|
157
|
+
confidence: "high",
|
|
158
|
+
environmentRequirements: [],
|
|
159
|
+
candidateEvidence,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 7 — fixed OSV-Scanner command planning.
|
|
3
|
+
//
|
|
4
|
+
// Exactly two supported command families, selected only by parsed major
|
|
5
|
+
// version. No caller-provided subcommands, endpoints, or scan paths beyond
|
|
6
|
+
// the validated target are ever accepted.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export function osvCommandFamilyForVersion(majorVersion) {
|
|
9
|
+
if (majorVersion === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (majorVersion >= 2)
|
|
12
|
+
return "modern";
|
|
13
|
+
if (majorVersion === 1)
|
|
14
|
+
return "legacy";
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
export function parseOsvMajorVersion(rawVersion) {
|
|
18
|
+
const match = /(\d+)\.\d+\.\d+/.exec(rawVersion);
|
|
19
|
+
if (!match)
|
|
20
|
+
return undefined;
|
|
21
|
+
return Number.parseInt(match[1], 10);
|
|
22
|
+
}
|
|
23
|
+
export function buildOsvArgs(family, targetRoot) {
|
|
24
|
+
return family === "modern" ? ["scan", "source", "--recursive", "--format", "json", targetRoot] : ["--recursive", "--format", "json", targetRoot];
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { auditAndroidOsv, ANDROID_OSV_AUDIT_CHECK_ID } from "./checkResult.js";
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { makeAndroidFinding } from "../../../audit/androidFinding.js";
|
|
3
|
+
import { makeCandidateEvidence } from "../../candidateEvidence.js";
|
|
4
|
+
import { buildAndroidSourceLocation } from "../../sourceLocation.js";
|
|
5
|
+
import { safeJsonParse, DEFAULT_MAX_MESSAGE_LENGTH } from "../boundedOutput.js";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// v0.4.1 Batch 7 — bounded OSV-Scanner JSON normalization.
|
|
8
|
+
//
|
|
9
|
+
// Only the one Batch 1 OSV rule id (android-optional-tool-osv-evidence) is
|
|
10
|
+
// used internally; the OSV vulnerability id and its aliases (CVE/GHSA) are
|
|
11
|
+
// preserved as public identifiers, never redacted or fingerprinted (they
|
|
12
|
+
// are not secrets). Complete advisory details and unbounded affected ranges
|
|
13
|
+
// are never retained.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const MAX_RESULTS = 5_000;
|
|
16
|
+
function severityFromDatabaseSpecific(value) {
|
|
17
|
+
if (typeof value !== "string")
|
|
18
|
+
return undefined;
|
|
19
|
+
const upper = value.toUpperCase();
|
|
20
|
+
if (upper === "CRITICAL" || upper === "HIGH")
|
|
21
|
+
return "major";
|
|
22
|
+
if (upper === "MODERATE" || upper === "MEDIUM")
|
|
23
|
+
return "minor";
|
|
24
|
+
if (upper === "LOW")
|
|
25
|
+
return "informational";
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
function severityFromCvssScore(severityEntries) {
|
|
29
|
+
if (!Array.isArray(severityEntries))
|
|
30
|
+
return undefined;
|
|
31
|
+
for (const entry of severityEntries) {
|
|
32
|
+
const record = entry;
|
|
33
|
+
const score = typeof record.score === "string" ? Number.parseFloat(record.score) : undefined;
|
|
34
|
+
if (score !== undefined && !Number.isNaN(score)) {
|
|
35
|
+
if (score >= 7)
|
|
36
|
+
return "major";
|
|
37
|
+
if (score >= 4)
|
|
38
|
+
return "minor";
|
|
39
|
+
return "informational";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function boundedText(value) {
|
|
45
|
+
const text = typeof value === "string" ? value : "";
|
|
46
|
+
return text.length > DEFAULT_MAX_MESSAGE_LENGTH ? `${text.slice(0, DEFAULT_MAX_MESSAGE_LENGTH)}...` : text;
|
|
47
|
+
}
|
|
48
|
+
export function parseOsvJson(rawStdout, targetRoot) {
|
|
49
|
+
const parsed = safeJsonParse(rawStdout);
|
|
50
|
+
if (!parsed.ok) {
|
|
51
|
+
return { malformed: true, findings: [], candidates: [], vulnerabilityCount: 0, truncated: parsed.truncated };
|
|
52
|
+
}
|
|
53
|
+
const results = Array.isArray(parsed.value.results) ? parsed.value.results : [];
|
|
54
|
+
const findings = [];
|
|
55
|
+
const candidates = [];
|
|
56
|
+
let vulnerabilityCount = 0;
|
|
57
|
+
let truncated = false;
|
|
58
|
+
for (const rawResult of results) {
|
|
59
|
+
const result = rawResult;
|
|
60
|
+
const source = result.source;
|
|
61
|
+
const sourcePath = typeof source?.path === "string" ? source.path : undefined;
|
|
62
|
+
const packages = Array.isArray(result.packages) ? result.packages : [];
|
|
63
|
+
for (const rawPackage of packages) {
|
|
64
|
+
const packageEntry = rawPackage;
|
|
65
|
+
const pkg = packageEntry.package;
|
|
66
|
+
const packageName = typeof pkg?.name === "string" ? pkg.name : "(unknown-package)";
|
|
67
|
+
const ecosystem = typeof pkg?.ecosystem === "string" ? pkg.ecosystem : "(unknown-ecosystem)";
|
|
68
|
+
const packageVersion = typeof pkg?.version === "string" ? pkg.version : undefined;
|
|
69
|
+
const vulnerabilities = Array.isArray(packageEntry.vulnerabilities) ? packageEntry.vulnerabilities : [];
|
|
70
|
+
let location;
|
|
71
|
+
let relativePath;
|
|
72
|
+
if (sourcePath) {
|
|
73
|
+
try {
|
|
74
|
+
const absolutePath = path.resolve(targetRoot, sourcePath);
|
|
75
|
+
location = buildAndroidSourceLocation(targetRoot, absolutePath);
|
|
76
|
+
relativePath = location.path;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
location = undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const rawVuln of vulnerabilities) {
|
|
83
|
+
if (vulnerabilityCount >= MAX_RESULTS) {
|
|
84
|
+
truncated = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
vulnerabilityCount += 1;
|
|
88
|
+
const vuln = rawVuln;
|
|
89
|
+
const vulnId = typeof vuln.id === "string" ? vuln.id : "(unknown-id)";
|
|
90
|
+
const aliases = Array.isArray(vuln.aliases) ? vuln.aliases.filter((a) => typeof a === "string") : [];
|
|
91
|
+
const summary = boundedText(vuln.summary ?? vuln.details);
|
|
92
|
+
const severity = severityFromDatabaseSpecific(vuln.database_specific?.severity) ?? severityFromCvssScore(vuln.severity);
|
|
93
|
+
const identity = `osv:${vulnId}:${ecosystem}:${packageName}:${packageVersion ?? "(unknown-version)"}`;
|
|
94
|
+
if (!relativePath) {
|
|
95
|
+
candidates.push(makeCandidateEvidence({
|
|
96
|
+
ruleId: "android-optional-tool-osv-evidence",
|
|
97
|
+
category: "android-osv",
|
|
98
|
+
confidence: "low",
|
|
99
|
+
location: { path: "(unknown)" },
|
|
100
|
+
summary: `OSV vulnerability ${vulnId} found for ${packageName}@${packageVersion ?? "?"} but no target-contained manifest path was reported`,
|
|
101
|
+
rawValue: undefined,
|
|
102
|
+
resolutionState: "unresolved",
|
|
103
|
+
staticAnalysisLimitations: ["OSV-Scanner evidence only; does not prove dependency reachability or exploitability."],
|
|
104
|
+
}));
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (severity === undefined) {
|
|
108
|
+
candidates.push(makeCandidateEvidence({
|
|
109
|
+
ruleId: "android-optional-tool-osv-evidence",
|
|
110
|
+
category: "android-osv",
|
|
111
|
+
confidence: "medium",
|
|
112
|
+
location: location,
|
|
113
|
+
summary: `OSV vulnerability ${vulnId} (unknown severity) affects ${packageName}@${packageVersion ?? "?"}`,
|
|
114
|
+
rawValue: summary,
|
|
115
|
+
resolutionState: "resolved",
|
|
116
|
+
staticAnalysisLimitations: [`aliases=${aliases.join(",")}`, "OSV-Scanner evidence only; does not prove dependency reachability or exploitability."],
|
|
117
|
+
relatedFindingIds: undefined,
|
|
118
|
+
}));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
findings.push(makeAndroidFinding({
|
|
122
|
+
ruleId: "android-optional-tool-osv-evidence",
|
|
123
|
+
title: `OSV vulnerability ${vulnId} in ${packageName}${packageVersion ? `@${packageVersion}` : ""}`,
|
|
124
|
+
severity,
|
|
125
|
+
confidence: "medium",
|
|
126
|
+
description: summary || `OSV reported a vulnerability for this dependency (ecosystem=${ecosystem}).`,
|
|
127
|
+
manifestPath: relativePath,
|
|
128
|
+
identity,
|
|
129
|
+
evidenceDetails: [`ecosystem=${ecosystem}`, `package=${packageName}`, `version=${packageVersion ?? "(unknown)"}`, `aliases=${aliases.join(",") || "(none)"}`],
|
|
130
|
+
recommendation: "Upgrade the affected dependency to a patched version.",
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { malformed: false, findings, candidates, vulnerabilityCount, truncated };
|
|
136
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { runSecurityCommand } from "../../../../securityValidation/commandRunner.js";
|
|
2
|
+
import { boundedText, DEFAULT_MAX_STDERR_BYTES, DEFAULT_MAX_STDOUT_BYTES } from "./boundedOutput.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// v0.4.1 Batch 7 — bounded external-tool execution.
|
|
5
|
+
//
|
|
6
|
+
// Reuses runSecurityCommand (shell:false, argument array, existing
|
|
7
|
+
// cross-platform Windows .cmd/.bat shim handling via resolveCommand) rather
|
|
8
|
+
// than a second process owner — the only new behavior added here is passing
|
|
9
|
+
// a minimized environment and bounding stdout/stderr before they leave this
|
|
10
|
+
// module, mirroring buildGradleOperationCheckResult's boundedSummary()
|
|
11
|
+
// pattern but generalized for reuse across all four Batch 7 adapters.
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
export const VERSION_PROBE_TIMEOUT_MS = 15_000;
|
|
14
|
+
export function createRealExternalToolExecutor() {
|
|
15
|
+
return (input) => runSecurityCommand({
|
|
16
|
+
command: input.command,
|
|
17
|
+
args: input.args,
|
|
18
|
+
cwd: input.cwd,
|
|
19
|
+
timeoutMs: input.timeoutMs,
|
|
20
|
+
env: input.env,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export async function runBoundedExternalTool(executor, input, maxStdoutBytes = DEFAULT_MAX_STDOUT_BYTES, maxStderrBytes = DEFAULT_MAX_STDERR_BYTES) {
|
|
24
|
+
const result = await executor(input);
|
|
25
|
+
const stdoutBound = boundedText(result.stdout, maxStdoutBytes);
|
|
26
|
+
const stderrBound = boundedText(result.stderr, maxStderrBytes);
|
|
27
|
+
return {
|
|
28
|
+
result,
|
|
29
|
+
stdout: stdoutBound.text,
|
|
30
|
+
stderr: stderrBound.text,
|
|
31
|
+
stdoutTruncated: stdoutBound.truncated,
|
|
32
|
+
stderrTruncated: stderrBound.truncated,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Deterministic, sanitized command summary: fixed tool identity + fixed
|
|
36
|
+
// option names, target/artifact roots replaced with symbolic placeholders,
|
|
37
|
+
// never the resolved absolute executable path or any environment value.
|
|
38
|
+
export function buildCommandSummary(toolBasename, args, targetRoot, artifactRoot) {
|
|
39
|
+
const sanitizedArgs = args.map((arg) => {
|
|
40
|
+
if (arg === targetRoot)
|
|
41
|
+
return "<TARGET>";
|
|
42
|
+
if (artifactRoot && arg === artifactRoot)
|
|
43
|
+
return "<ARTIFACT_ROOT>";
|
|
44
|
+
if (arg.startsWith(targetRoot))
|
|
45
|
+
return `<TARGET>${arg.slice(targetRoot.length).replace(/\\/g, "/")}`;
|
|
46
|
+
if (artifactRoot && arg.startsWith(artifactRoot))
|
|
47
|
+
return `<ARTIFACT_ROOT>${arg.slice(artifactRoot.length).replace(/\\/g, "/")}`;
|
|
48
|
+
return arg;
|
|
49
|
+
});
|
|
50
|
+
return [toolBasename, ...sanitizedArgs].join(" ");
|
|
51
|
+
}
|
package/dist/src/mobile/android/advancedSecurity/externalTools/runRequestedAndroidExternalTools.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { auditAndroidSemgrep } from "./semgrep/checkResult.js";
|
|
2
|
+
import { auditAndroidOsv } from "./osv/checkResult.js";
|
|
3
|
+
import { auditAndroidLint } from "./androidLint/checkResult.js";
|
|
4
|
+
import { auditAndroidDependencyCheck } from "./dependencyCheck/checkResult.js";
|
|
5
|
+
import { normalizeAndroidExternalToolRequest } from "./types.js";
|
|
6
|
+
export async function runRequestedAndroidExternalTools(options) {
|
|
7
|
+
const normalized = normalizeAndroidExternalToolRequest(options.request);
|
|
8
|
+
if (!normalized.ok) {
|
|
9
|
+
throw new Error(normalized.error);
|
|
10
|
+
}
|
|
11
|
+
if (normalized.value.tools.length === 0)
|
|
12
|
+
return [];
|
|
13
|
+
const results = [];
|
|
14
|
+
for (const toolId of normalized.value.tools) {
|
|
15
|
+
if (toolId === "semgrep" && options.executors.semgrep) {
|
|
16
|
+
results.push(await auditAndroidSemgrep({
|
|
17
|
+
targetRoot: normalized.value.targetRoot,
|
|
18
|
+
artifactRoot: normalized.value.artifactRoot,
|
|
19
|
+
executor: options.executors.semgrep,
|
|
20
|
+
discover: options.discover?.semgrep,
|
|
21
|
+
}));
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (toolId === "osv" && options.executors.osv) {
|
|
25
|
+
results.push(await auditAndroidOsv({
|
|
26
|
+
targetRoot: normalized.value.targetRoot,
|
|
27
|
+
executor: options.executors.osv,
|
|
28
|
+
networkPolicy: normalized.value.networkPolicy,
|
|
29
|
+
discover: options.discover?.osv,
|
|
30
|
+
}));
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (toolId === "android-lint" && options.executors.androidLint) {
|
|
34
|
+
results.push(await auditAndroidLint({
|
|
35
|
+
targetRoot: normalized.value.targetRoot,
|
|
36
|
+
detection: options.detection,
|
|
37
|
+
executor: options.executors.androidLint,
|
|
38
|
+
artifactRoot: normalized.value.artifactRoot,
|
|
39
|
+
taskAvailable: options.lintTaskAvailable,
|
|
40
|
+
}));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (toolId === "dependency-check" && options.executors.dependencyCheck) {
|
|
44
|
+
results.push(await auditAndroidDependencyCheck({
|
|
45
|
+
targetRoot: normalized.value.targetRoot,
|
|
46
|
+
artifactRoot: normalized.value.artifactRoot,
|
|
47
|
+
executor: options.executors.dependencyCheck,
|
|
48
|
+
javaAvailable: options.javaAvailable,
|
|
49
|
+
discover: options.discover?.dependencyCheck,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { sortCandidateEvidence } from "../../ordering.js";
|
|
3
|
+
import { discoverAllowlistedExecutable } from "../discoverExecutable.js";
|
|
4
|
+
import { buildMinimalEnvironment } from "../minimalEnvironment.js";
|
|
5
|
+
import { writeExternalToolArtifact } from "../artifacts.js";
|
|
6
|
+
import { captureTargetSnapshot, buildExternalToolMutationReport } from "../mutation.js";
|
|
7
|
+
import { runBoundedExternalTool, buildCommandSummary, VERSION_PROBE_TIMEOUT_MS } from "../runBoundedExternalTool.js";
|
|
8
|
+
import { serializeSemgrepRulePack } from "./rules.js";
|
|
9
|
+
import { buildSemgrepArgs, parseSemgrepMajorVersion, semgrepCommandFamilyForVersion } from "./command.js";
|
|
10
|
+
import { parseSemgrepJson } from "./parseSemgrepJson.js";
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// v0.4.1 Batch 7 — standalone Android Semgrep check. Not called from
|
|
13
|
+
// validateAndroidTarget or any active orchestration; not rendered in
|
|
14
|
+
// reports; no CLI effect. Batch 8 integrates.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
export const ANDROID_SEMGREP_AUDIT_CHECK_ID = "android-semgrep-audit";
|
|
17
|
+
const SEMGREP_CANDIDATES = ["semgrep", "semgrep.exe"];
|
|
18
|
+
const SEMGREP_TIMEOUT_MS = 5 * 60_000;
|
|
19
|
+
function skippedCheck(reason, environmentRequirements = []) {
|
|
20
|
+
return {
|
|
21
|
+
id: ANDROID_SEMGREP_AUDIT_CHECK_ID,
|
|
22
|
+
category: "android-semgrep",
|
|
23
|
+
title: "Android Semgrep audit",
|
|
24
|
+
status: "skipped",
|
|
25
|
+
requirementLevel: "optional",
|
|
26
|
+
ran: false,
|
|
27
|
+
skipped: true,
|
|
28
|
+
skipInfo: {
|
|
29
|
+
checkId: ANDROID_SEMGREP_AUDIT_CHECK_ID,
|
|
30
|
+
reason,
|
|
31
|
+
requirementLevel: "optional",
|
|
32
|
+
missingCapability: "semgrep-executable",
|
|
33
|
+
verdictImpact: "does not apply until Batch 8 integration",
|
|
34
|
+
recommendedNextAction: "Install Semgrep locally, or run again after Batch 8 wires optional-tool selection into the CLI.",
|
|
35
|
+
},
|
|
36
|
+
evidence: [],
|
|
37
|
+
findings: [],
|
|
38
|
+
warnings: [],
|
|
39
|
+
errors: [],
|
|
40
|
+
sourcePaths: [],
|
|
41
|
+
confidence: "unknown",
|
|
42
|
+
environmentRequirements,
|
|
43
|
+
candidateEvidence: [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export async function auditAndroidSemgrep(options) {
|
|
47
|
+
const targetRoot = path.resolve(options.targetRoot);
|
|
48
|
+
const discovered = (options.discover ?? (() => discoverAllowlistedExecutable(SEMGREP_CANDIDATES, targetRoot)))();
|
|
49
|
+
if (!discovered.available) {
|
|
50
|
+
return skippedCheck("Semgrep executable was not found on PATH.", ["Requires a Semgrep executable (semgrep) on PATH."]);
|
|
51
|
+
}
|
|
52
|
+
const env = buildMinimalEnvironment();
|
|
53
|
+
const versionInput = { command: discovered.command, args: ["--version"], cwd: targetRoot, timeoutMs: VERSION_PROBE_TIMEOUT_MS, env };
|
|
54
|
+
const versionOutcome = await runBoundedExternalTool(options.executor, versionInput);
|
|
55
|
+
if (versionOutcome.result.timedOut || versionOutcome.result.exitCode === null) {
|
|
56
|
+
return skippedCheck("Semgrep version probe failed or timed out.");
|
|
57
|
+
}
|
|
58
|
+
const rawVersion = versionOutcome.stdout.trim();
|
|
59
|
+
const majorVersion = parseSemgrepMajorVersion(rawVersion);
|
|
60
|
+
const family = semgrepCommandFamilyForVersion(majorVersion);
|
|
61
|
+
if (!family) {
|
|
62
|
+
return skippedCheck(`Semgrep version "${rawVersion || "(unknown)"}" is not a supported version family.`);
|
|
63
|
+
}
|
|
64
|
+
const configArtifact = writeExternalToolArtifact(options.artifactRoot, "semgrep", "android-rules.yaml", serializeSemgrepRulePack(), "text");
|
|
65
|
+
const configAbsolutePath = path.resolve(options.artifactRoot, configArtifact.relativePath);
|
|
66
|
+
const args = buildSemgrepArgs(family, configAbsolutePath, targetRoot);
|
|
67
|
+
const commandSummary = buildCommandSummary(discovered.basename, args, targetRoot, path.resolve(options.artifactRoot));
|
|
68
|
+
const before = captureTargetSnapshot(targetRoot, true);
|
|
69
|
+
const analysisOutcome = await runBoundedExternalTool(options.executor, { command: discovered.command, args, cwd: targetRoot, timeoutMs: SEMGREP_TIMEOUT_MS, env });
|
|
70
|
+
const after = captureTargetSnapshot(targetRoot, true);
|
|
71
|
+
const mutation = buildExternalToolMutationReport(before, after);
|
|
72
|
+
const warnings = [];
|
|
73
|
+
if (analysisOutcome.stdoutTruncated)
|
|
74
|
+
warnings.push("stdout was truncated; JSON output was not parsed as complete.");
|
|
75
|
+
if (analysisOutcome.stderrTruncated)
|
|
76
|
+
warnings.push("stderr was truncated.");
|
|
77
|
+
if (!mutation.comparable)
|
|
78
|
+
warnings.push(`Target mutation evidence unavailable: ${mutation.reason ?? "unknown reason"}`);
|
|
79
|
+
if (mutation.unexpectedChanges.length > 0)
|
|
80
|
+
warnings.push(`Unexpected target change(s) observed: ${mutation.unexpectedChanges.join(", ")}`);
|
|
81
|
+
if (analysisOutcome.result.timedOut) {
|
|
82
|
+
return {
|
|
83
|
+
id: ANDROID_SEMGREP_AUDIT_CHECK_ID,
|
|
84
|
+
category: "android-semgrep",
|
|
85
|
+
title: "Android Semgrep audit",
|
|
86
|
+
status: "inconclusive",
|
|
87
|
+
requirementLevel: "optional",
|
|
88
|
+
ran: true,
|
|
89
|
+
skipped: false,
|
|
90
|
+
evidence: [`version=${rawVersion}`, `commandFamily=${family}`],
|
|
91
|
+
findings: [],
|
|
92
|
+
warnings: [...warnings, `Semgrep timed out after ${SEMGREP_TIMEOUT_MS}ms.`],
|
|
93
|
+
errors: [],
|
|
94
|
+
sourcePaths: [],
|
|
95
|
+
confidence: "medium",
|
|
96
|
+
environmentRequirements: [],
|
|
97
|
+
candidateEvidence: [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (analysisOutcome.stdoutTruncated) {
|
|
101
|
+
return {
|
|
102
|
+
id: ANDROID_SEMGREP_AUDIT_CHECK_ID,
|
|
103
|
+
category: "android-semgrep",
|
|
104
|
+
title: "Android Semgrep audit",
|
|
105
|
+
status: "inconclusive",
|
|
106
|
+
requirementLevel: "optional",
|
|
107
|
+
ran: true,
|
|
108
|
+
skipped: false,
|
|
109
|
+
evidence: [`version=${rawVersion}`, `commandFamily=${family}`],
|
|
110
|
+
findings: [],
|
|
111
|
+
warnings,
|
|
112
|
+
errors: [],
|
|
113
|
+
sourcePaths: [],
|
|
114
|
+
confidence: "low",
|
|
115
|
+
environmentRequirements: [],
|
|
116
|
+
candidateEvidence: [],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const parsed = parseSemgrepJson(analysisOutcome.stdout, targetRoot, configArtifact.sha256);
|
|
120
|
+
const isFindingsExitCode = analysisOutcome.result.exitCode === 0 || analysisOutcome.result.exitCode === 1;
|
|
121
|
+
if (parsed.malformed) {
|
|
122
|
+
return {
|
|
123
|
+
id: ANDROID_SEMGREP_AUDIT_CHECK_ID,
|
|
124
|
+
category: "android-semgrep",
|
|
125
|
+
title: "Android Semgrep audit",
|
|
126
|
+
status: isFindingsExitCode ? "inconclusive" : "failed",
|
|
127
|
+
requirementLevel: "optional",
|
|
128
|
+
ran: true,
|
|
129
|
+
skipped: false,
|
|
130
|
+
evidence: [`version=${rawVersion}`, `commandFamily=${family}`, `exitCode=${analysisOutcome.result.exitCode}`],
|
|
131
|
+
findings: [],
|
|
132
|
+
warnings,
|
|
133
|
+
errors: isFindingsExitCode ? [] : ["Semgrep exited with a non-findings error and produced no parseable JSON report."],
|
|
134
|
+
sourcePaths: [],
|
|
135
|
+
confidence: "low",
|
|
136
|
+
environmentRequirements: [],
|
|
137
|
+
candidateEvidence: [],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
for (const parserError of parsed.parserErrors) {
|
|
141
|
+
warnings.push(`Semgrep reported a parser error: ${parserError}`);
|
|
142
|
+
}
|
|
143
|
+
if (parsed.truncated)
|
|
144
|
+
warnings.push(`Finding count exceeded the bounded limit; results were truncated.`);
|
|
145
|
+
const findingsById = new Map(parsed.findings.map((f) => [f.id, f]));
|
|
146
|
+
const candidatesById = new Map(parsed.candidates.map((c) => [c.id, c]));
|
|
147
|
+
const findings = [...findingsById.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
148
|
+
const candidateEvidence = sortCandidateEvidence([...candidatesById.values()]);
|
|
149
|
+
// A findings exit code (0 or 1) with valid JSON is never treated as a
|
|
150
|
+
// process crash — Semgrep uses 1 to mean "findings were reported", not
|
|
151
|
+
// "the tool failed" (agents.txt Batch 7 section 10.11).
|
|
152
|
+
const status = findings.length > 0 ? "failed" : isFindingsExitCode ? "passed" : "error";
|
|
153
|
+
return {
|
|
154
|
+
id: ANDROID_SEMGREP_AUDIT_CHECK_ID,
|
|
155
|
+
category: "android-semgrep",
|
|
156
|
+
title: "Android Semgrep audit",
|
|
157
|
+
status,
|
|
158
|
+
requirementLevel: "optional",
|
|
159
|
+
ran: true,
|
|
160
|
+
skipped: false,
|
|
161
|
+
evidence: [`version=${rawVersion}`, `commandFamily=${family}`, `scanned=${parsed.scannedCount}`, `command=${commandSummary}`],
|
|
162
|
+
findings,
|
|
163
|
+
warnings,
|
|
164
|
+
errors: status === "error" ? ["Semgrep exited with a non-findings error."] : [],
|
|
165
|
+
sourcePaths: [],
|
|
166
|
+
confidence: parsed.parserErrors.length > 0 ? "medium" : "high",
|
|
167
|
+
environmentRequirements: [],
|
|
168
|
+
candidateEvidence,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 7 — fixed Semgrep command planning.
|
|
3
|
+
//
|
|
4
|
+
// Exactly two supported command families, selected only by parsed major
|
|
5
|
+
// version — never by trial-and-error after a failed attempt. No caller-
|
|
6
|
+
// provided flags, config identifiers, or excludes are ever accepted.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export const SEMGREP_FIXED_EXCLUDES = [".git", "node_modules", "build", "dist", "out", "coverage", ".gradle", "reports", "lab-output", ".my-dev-kit", "*.apk", "*.aab"];
|
|
9
|
+
export function semgrepCommandFamilyForVersion(majorVersion) {
|
|
10
|
+
if (majorVersion === undefined)
|
|
11
|
+
return undefined;
|
|
12
|
+
if (majorVersion >= 1)
|
|
13
|
+
return "modern";
|
|
14
|
+
if (majorVersion === 0)
|
|
15
|
+
return "legacy";
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
export function parseSemgrepMajorVersion(rawVersion) {
|
|
19
|
+
const match = /(\d+)\.\d+\.\d+/.exec(rawVersion);
|
|
20
|
+
if (!match)
|
|
21
|
+
return undefined;
|
|
22
|
+
return Number.parseInt(match[1], 10);
|
|
23
|
+
}
|
|
24
|
+
export function buildSemgrepArgs(family, configPath, targetRoot) {
|
|
25
|
+
const excludeArgs = SEMGREP_FIXED_EXCLUDES.flatMap((pattern) => ["--exclude", pattern]);
|
|
26
|
+
const base = ["--config", configPath, "--json", "--metrics=off", "--disable-version-check", ...excludeArgs, targetRoot];
|
|
27
|
+
return family === "modern" ? ["scan", ...base] : base;
|
|
28
|
+
}
|