@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
|
@@ -11,7 +11,10 @@ import { extractBuildFileEvidence } from "../detect/buildFileEvidence.js";
|
|
|
11
11
|
// excerpt" strategy for each key. Comments are stripped first so a
|
|
12
12
|
// commented-out assignment never becomes active metadata (ANDROID-B4-05).
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
|
-
|
|
14
|
+
// v0.4.1 Batch 4 — exported (was module-private) so the new signing-
|
|
15
|
+
// configuration module can reuse the exact same comment-stripping/literal-
|
|
16
|
+
// extraction/brace-matching technique instead of a second implementation.
|
|
17
|
+
export function stripComments(text) {
|
|
15
18
|
// Replaces comment characters with spaces (not empty string) so any
|
|
16
19
|
// surviving line/column-independent offsets are not shifted — this batch
|
|
17
20
|
// does not track per-field locations, but keeping length stable is cheap
|
|
@@ -21,7 +24,7 @@ function stripComments(text) {
|
|
|
21
24
|
.replace(/\/\/[^\n]*/g, (match) => " ".repeat(match.length));
|
|
22
25
|
}
|
|
23
26
|
const STRING_OR_INT = `(?:'([^']*)'|"([^"]*)"|(-?\\d+))`;
|
|
24
|
-
function extractLiteral(text, keyAlternatives) {
|
|
27
|
+
export function extractLiteral(text, keyAlternatives) {
|
|
25
28
|
const keyPattern = keyAlternatives.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
26
29
|
// Anchored to the start of a line (allowing leading indentation) so a key
|
|
27
30
|
// name that merely appears as a word inside an unrelated string literal —
|
|
@@ -46,32 +49,45 @@ function extractLiteral(text, keyAlternatives) {
|
|
|
46
49
|
}
|
|
47
50
|
return {};
|
|
48
51
|
}
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
// full parser — nested unrelated blocks inside are not descended into for
|
|
55
|
-
// name extraction beyond the immediate children.
|
|
56
|
-
function findNamedBlockEntries(text, blockKeyword) {
|
|
52
|
+
// Returns the inner text of the first `<blockKeyword> { ... }` block found,
|
|
53
|
+
// bounded by brace-depth matching (not a full parser). Shared by
|
|
54
|
+
// findNamedBlockEntries (names only) and extractNamedSubBlocks (v0.4.1
|
|
55
|
+
// Batch 3 — names plus each entry's own block content).
|
|
56
|
+
export function findBlockBody(text, blockKeyword) {
|
|
57
57
|
const blockStart = text.search(new RegExp(`\\b${blockKeyword}\\s*\\{`));
|
|
58
58
|
if (blockStart === -1)
|
|
59
|
-
return
|
|
59
|
+
return undefined;
|
|
60
60
|
const openBraceIndex = text.indexOf("{", blockStart);
|
|
61
|
+
return sliceBlockAt(text, openBraceIndex);
|
|
62
|
+
}
|
|
63
|
+
// Given the index of an opening `{`, returns the text between it and its
|
|
64
|
+
// matching `}` (brace-depth bounded), or undefined if unterminated.
|
|
65
|
+
export function sliceBlockAt(text, openBraceIndex) {
|
|
66
|
+
if (text[openBraceIndex] !== "{")
|
|
67
|
+
return undefined;
|
|
61
68
|
let depth = 0;
|
|
62
|
-
let endIndex = text.length;
|
|
63
69
|
for (let i = openBraceIndex; i < text.length; i++) {
|
|
64
70
|
if (text[i] === "{")
|
|
65
71
|
depth += 1;
|
|
66
72
|
else if (text[i] === "}") {
|
|
67
73
|
depth -= 1;
|
|
68
|
-
if (depth === 0)
|
|
69
|
-
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
74
|
+
if (depth === 0)
|
|
75
|
+
return text.slice(openBraceIndex + 1, i);
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
|
-
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
// Finds the top-level named entries inside a `<blockKeyword> { ... }` block
|
|
81
|
+
// (e.g. `buildTypes { debug { ... } release { ... } }` or
|
|
82
|
+
// `sourceSets { main { ... } androidTest { ... } }`), supporting both the
|
|
83
|
+
// bare-identifier Groovy/Kotlin-DSL form and the Kotlin DSL
|
|
84
|
+
// getByName/create/named(...) form. Bounded by brace-depth matching, not a
|
|
85
|
+
// full parser — nested unrelated blocks inside are not descended into for
|
|
86
|
+
// name extraction beyond the immediate children.
|
|
87
|
+
function findNamedBlockEntries(text, blockKeyword) {
|
|
88
|
+
const block = findBlockBody(text, blockKeyword);
|
|
89
|
+
if (block === undefined)
|
|
90
|
+
return [];
|
|
75
91
|
const names = new Set();
|
|
76
92
|
for (const match of block.matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\{/g)) {
|
|
77
93
|
names.add(match[1]);
|
|
@@ -81,6 +97,95 @@ function findNamedBlockEntries(text, blockKeyword) {
|
|
|
81
97
|
}
|
|
82
98
|
return [...names].sort((a, b) => a.localeCompare(b));
|
|
83
99
|
}
|
|
100
|
+
// v0.4.1 Batch 3 — like findNamedBlockEntries, but also returns each named
|
|
101
|
+
// entry's own block content so per-build-type literal fields (debuggable,
|
|
102
|
+
// minifyEnabled, etc.) can be extracted. Reuses the exact same bounded
|
|
103
|
+
// brace-matching technique and the same two name-pattern forms — not a
|
|
104
|
+
// second parser, just returning more of what was already being scanned.
|
|
105
|
+
export function extractNamedSubBlocks(text, blockKeyword) {
|
|
106
|
+
const block = findBlockBody(text, blockKeyword);
|
|
107
|
+
if (block === undefined)
|
|
108
|
+
return [];
|
|
109
|
+
const results = [];
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
for (const match of block.matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\{/g)) {
|
|
112
|
+
const name = match[1];
|
|
113
|
+
if (seen.has(name))
|
|
114
|
+
continue;
|
|
115
|
+
const openBraceIndex = match.index + match[0].length - 1;
|
|
116
|
+
const content = sliceBlockAt(block, openBraceIndex);
|
|
117
|
+
if (content !== undefined) {
|
|
118
|
+
results.push({ name, content });
|
|
119
|
+
seen.add(name);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const match of block.matchAll(/\b(?:getByName|create|named)\(\s*["']([\w-]+)["']\s*\)\s*\{/g)) {
|
|
123
|
+
const name = match[1];
|
|
124
|
+
if (seen.has(name))
|
|
125
|
+
continue;
|
|
126
|
+
const openBraceIndex = match.index + match[0].length - 1;
|
|
127
|
+
const content = sliceBlockAt(block, openBraceIndex);
|
|
128
|
+
if (content !== undefined) {
|
|
129
|
+
results.push({ name, content });
|
|
130
|
+
seen.add(name);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return results.sort((a, b) => a.name.localeCompare(b.name));
|
|
134
|
+
}
|
|
135
|
+
const BOOLEAN_TOKEN = `(true|false)`;
|
|
136
|
+
// v0.4.1 Batch 3 — boolean-literal counterpart to extractLiteral (which only
|
|
137
|
+
// matches quoted-string/integer literals — Gradle boolean assignments like
|
|
138
|
+
// `debuggable true` or `isMinifyEnabled = true` are bare tokens, not quoted).
|
|
139
|
+
// Same anchoring/fallback strategy as extractLiteral for consistency.
|
|
140
|
+
export function extractBooleanLiteral(text, keyAlternatives) {
|
|
141
|
+
const keyPattern = keyAlternatives.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
142
|
+
const literalPattern = new RegExp(`^[ \\t]*(?:${keyPattern})\\b\\s*[=(]?\\s*${BOOLEAN_TOKEN}\\)?`, "m");
|
|
143
|
+
const literalMatch = text.match(literalPattern);
|
|
144
|
+
if (literalMatch) {
|
|
145
|
+
return { value: literalMatch[1] === "true" };
|
|
146
|
+
}
|
|
147
|
+
const rawPattern = new RegExp(`^[ \\t]*(?:${keyPattern})\\b\\s*[=(]?\\s*([^\\n]{1,120})`, "m");
|
|
148
|
+
const rawMatch = text.match(rawPattern);
|
|
149
|
+
if (rawMatch) {
|
|
150
|
+
const raw = rawMatch[1].trim();
|
|
151
|
+
if (raw.length > 0)
|
|
152
|
+
return { raw };
|
|
153
|
+
}
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
function booleanLiteralState(result) {
|
|
157
|
+
if (result.value !== undefined)
|
|
158
|
+
return result.value ? "literal-true" : "literal-false";
|
|
159
|
+
if (result.raw !== undefined)
|
|
160
|
+
return "dynamic";
|
|
161
|
+
return "missing";
|
|
162
|
+
}
|
|
163
|
+
// v0.4.1 Batch 3 — builds bounded literal evidence for one named build-type
|
|
164
|
+
// block. Never evaluates Gradle; signingConfig/matchingFallbacks/initWith
|
|
165
|
+
// are always reference expressions, so only a bounded raw excerpt is kept.
|
|
166
|
+
function buildBuildTypeInfo(name, content) {
|
|
167
|
+
const debuggable = extractBooleanLiteral(content, ["debuggable", "isDebuggable"]);
|
|
168
|
+
const minifyEnabled = extractBooleanLiteral(content, ["minifyEnabled", "isMinifyEnabled"]);
|
|
169
|
+
const shrinkResources = extractBooleanLiteral(content, ["shrinkResources", "isShrinkResources"]);
|
|
170
|
+
const signingConfig = extractLiteral(content, ["signingConfig"]);
|
|
171
|
+
const matchingFallbacks = extractLiteral(content, ["matchingFallbacks"]);
|
|
172
|
+
const initWith = extractLiteral(content, ["initWith"]);
|
|
173
|
+
return {
|
|
174
|
+
name,
|
|
175
|
+
debuggable: debuggable.value,
|
|
176
|
+
debuggableRaw: debuggable.raw,
|
|
177
|
+
debuggableState: booleanLiteralState(debuggable),
|
|
178
|
+
minifyEnabled: minifyEnabled.value,
|
|
179
|
+
minifyEnabledRaw: minifyEnabled.raw,
|
|
180
|
+
minifyEnabledState: booleanLiteralState(minifyEnabled),
|
|
181
|
+
shrinkResources: shrinkResources.value,
|
|
182
|
+
shrinkResourcesRaw: shrinkResources.raw,
|
|
183
|
+
shrinkResourcesState: booleanLiteralState(shrinkResources),
|
|
184
|
+
signingConfigRef: signingConfig.value ?? signingConfig.raw,
|
|
185
|
+
matchingFallbacksRaw: matchingFallbacks.value ?? matchingFallbacks.raw,
|
|
186
|
+
initWithRaw: initWith.value ?? initWith.raw,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
84
189
|
// Extracts bounded static metadata for one module's build file text. `path`
|
|
85
190
|
// is intentionally omitted from the returned info — the caller (the
|
|
86
191
|
// aggregator, which already knows the module path from detection) attaches
|
|
@@ -128,6 +233,7 @@ export function extractModuleGradleMetadata(rawText) {
|
|
|
128
233
|
compileSdk: compileSdk.value,
|
|
129
234
|
compileSdkRaw: compileSdk.raw,
|
|
130
235
|
buildTypes: findNamedBlockEntries(text, "buildTypes"),
|
|
236
|
+
buildTypeDetails: extractNamedSubBlocks(text, "buildTypes").map(({ name, content }) => buildBuildTypeInfo(name, content)),
|
|
131
237
|
composeEnabled: evidence.composeBuildFeatureEvidence || evidence.composeDependencyEvidence || undefined,
|
|
132
238
|
sourceSetEvidence: findNamedBlockEntries(text, "sourceSets").map((name) => `declared sourceSet: ${name}`),
|
|
133
239
|
testSourceSetEvidence: [],
|
|
@@ -108,8 +108,17 @@ function parseComponent(kind, element, warnings) {
|
|
|
108
108
|
component.readPermission = androidAttr(element, "readPermission");
|
|
109
109
|
component.writePermission = androidAttr(element, "writePermission");
|
|
110
110
|
const authoritiesRaw = androidAttr(element, "authorities");
|
|
111
|
+
component.authoritiesRaw = authoritiesRaw;
|
|
111
112
|
component.authorities = authoritiesRaw ? authoritiesRaw.split(";").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
112
|
-
|
|
113
|
+
const grantUriPermissions = parseBooleanAttr(element, "grantUriPermissions", warnings, context);
|
|
114
|
+
component.grantUriPermissions = grantUriPermissions.value;
|
|
115
|
+
component.grantUriPermissionsRaw = grantUriPermissions.raw;
|
|
116
|
+
component.metadata = findChildren(element, "meta-data").map((metadata) => ({
|
|
117
|
+
name: androidAttr(metadata, "name"),
|
|
118
|
+
resource: androidAttr(metadata, "resource"),
|
|
119
|
+
value: androidAttr(metadata, "value"),
|
|
120
|
+
location: toLocation(metadata.location),
|
|
121
|
+
}));
|
|
113
122
|
}
|
|
114
123
|
return component;
|
|
115
124
|
}
|
|
@@ -202,6 +211,12 @@ export function parseAndroidManifestSource(xmlText, manifestPath) {
|
|
|
202
211
|
if (applicationElements.length > 1) {
|
|
203
212
|
parseWarnings.push(`Manifest declares ${applicationElements.length} <application> elements; only the first was used.`);
|
|
204
213
|
}
|
|
214
|
+
const usesCleartextTrafficResult = applicationEl
|
|
215
|
+
? parseBooleanAttr(applicationEl, "usesCleartextTraffic", parseWarnings, "<application>")
|
|
216
|
+
: undefined;
|
|
217
|
+
const allowBackupResult = applicationEl ? parseBooleanAttr(applicationEl, "allowBackup", parseWarnings, "<application>") : undefined;
|
|
218
|
+
const debuggableResult = applicationEl ? parseBooleanAttr(applicationEl, "debuggable", parseWarnings, "<application>") : undefined;
|
|
219
|
+
const testOnlyResult = applicationEl ? parseBooleanAttr(applicationEl, "testOnly", parseWarnings, "<application>") : undefined;
|
|
205
220
|
const application = applicationEl
|
|
206
221
|
? {
|
|
207
222
|
name: androidAttr(applicationEl, "name"),
|
|
@@ -209,10 +224,18 @@ export function parseAndroidManifestSource(xmlText, manifestPath) {
|
|
|
209
224
|
iconRef: androidAttr(applicationEl, "icon"),
|
|
210
225
|
enabled: parseBooleanAttr(applicationEl, "enabled", parseWarnings, "<application>").value,
|
|
211
226
|
permission: androidAttr(applicationEl, "permission"),
|
|
212
|
-
allowBackup:
|
|
213
|
-
|
|
214
|
-
|
|
227
|
+
allowBackup: allowBackupResult?.value,
|
|
228
|
+
allowBackupRaw: allowBackupResult?.raw,
|
|
229
|
+
debuggable: debuggableResult?.value,
|
|
230
|
+
debuggableRaw: debuggableResult?.raw,
|
|
231
|
+
usesCleartextTraffic: usesCleartextTrafficResult?.value,
|
|
232
|
+
usesCleartextTrafficRaw: usesCleartextTrafficResult?.raw,
|
|
215
233
|
networkSecurityConfigRef: androidAttr(applicationEl, "networkSecurityConfig"),
|
|
234
|
+
fullBackupContentRef: androidAttr(applicationEl, "fullBackupContent"),
|
|
235
|
+
dataExtractionRulesRef: androidAttr(applicationEl, "dataExtractionRules"),
|
|
236
|
+
testOnly: testOnlyResult?.value,
|
|
237
|
+
testOnlyRaw: testOnlyResult?.raw,
|
|
238
|
+
location: toLocation(applicationEl.location),
|
|
216
239
|
}
|
|
217
240
|
: {};
|
|
218
241
|
const activities = applicationEl ? findChildren(applicationEl, "activity").map((el) => parseComponent("activity", el, parseWarnings)) : [];
|
|
@@ -92,6 +92,18 @@ class Scanner {
|
|
|
92
92
|
}
|
|
93
93
|
this.expect(literal);
|
|
94
94
|
}
|
|
95
|
+
// Like skipUntil, but returns the consumed text before `literal` instead of
|
|
96
|
+
// discarding it. Used to capture CDATA section content as element text.
|
|
97
|
+
captureUntil(literal) {
|
|
98
|
+
let captured = "";
|
|
99
|
+
while (!this.eof() && !this.startsWith(literal))
|
|
100
|
+
captured += this.advance();
|
|
101
|
+
if (this.eof()) {
|
|
102
|
+
throw new XmlSyntaxError(`Unterminated construct, expected "${literal}"`, this.loc());
|
|
103
|
+
}
|
|
104
|
+
this.expect(literal);
|
|
105
|
+
return captured;
|
|
106
|
+
}
|
|
95
107
|
}
|
|
96
108
|
function resolveNamespace(prefix, scope) {
|
|
97
109
|
return scope.get(prefix ?? "");
|
|
@@ -224,12 +236,13 @@ function parseElement(scanner, parentScope, warnings) {
|
|
|
224
236
|
scanner.skipWhitespace();
|
|
225
237
|
if (scanner.startsWith("/>")) {
|
|
226
238
|
scanner.expect("/>");
|
|
227
|
-
return { tagName, prefix, localName, namespaceUri, attributes, children: [], location };
|
|
239
|
+
return { tagName, prefix, localName, namespaceUri, attributes, children: [], location, textContent: "" };
|
|
228
240
|
}
|
|
229
241
|
scanner.expect(">");
|
|
230
242
|
const children = [];
|
|
243
|
+
const textParts = [];
|
|
231
244
|
for (;;) {
|
|
232
|
-
skipTextAndMisc(scanner, warnings);
|
|
245
|
+
skipTextAndMisc(scanner, warnings, textParts);
|
|
233
246
|
if (scanner.startsWith("</")) {
|
|
234
247
|
scanner.expect("</");
|
|
235
248
|
const closingName = readName(scanner);
|
|
@@ -251,16 +264,22 @@ function parseElement(scanner, parentScope, warnings) {
|
|
|
251
264
|
// element/closing-tag pattern) is treated as malformed input.
|
|
252
265
|
throw new XmlSyntaxError("Unexpected content while looking for a child element or closing tag", scanner.loc());
|
|
253
266
|
}
|
|
254
|
-
return { tagName, prefix, localName, namespaceUri, attributes, children, location };
|
|
267
|
+
return { tagName, prefix, localName, namespaceUri, attributes, children, location, textContent: decodeXmlEntities(textParts.join("")) };
|
|
255
268
|
}
|
|
256
|
-
|
|
269
|
+
// v0.4.1 Batch 2 — `textParts` accumulates direct text/CDATA content so
|
|
270
|
+
// callers needing element text content (e.g. Network Security Config's
|
|
271
|
+
// <domain>/<pin> elements) can read it via XmlElement.textContent. Comments,
|
|
272
|
+
// processing instructions, and DOCTYPE are still skipped, never counted as
|
|
273
|
+
// text — only true character data and CDATA sections are captured.
|
|
274
|
+
function skipTextAndMisc(scanner, warnings, textParts) {
|
|
257
275
|
for (;;) {
|
|
258
276
|
if (scanner.startsWith("<!--")) {
|
|
259
277
|
scanner.skipUntil("-->");
|
|
260
278
|
continue;
|
|
261
279
|
}
|
|
262
280
|
if (scanner.startsWith("<![CDATA[")) {
|
|
263
|
-
scanner.
|
|
281
|
+
scanner.expect("<![CDATA[");
|
|
282
|
+
textParts.push(scanner.captureUntil("]]>"));
|
|
264
283
|
continue;
|
|
265
284
|
}
|
|
266
285
|
if (scanner.startsWith("<?")) {
|
|
@@ -271,10 +290,7 @@ function skipTextAndMisc(scanner, warnings) {
|
|
|
271
290
|
break;
|
|
272
291
|
if (scanner.eof())
|
|
273
292
|
break;
|
|
274
|
-
|
|
275
|
-
// structure, so it is discarded rather than stored.
|
|
276
|
-
scanner.advance();
|
|
277
|
-
continue;
|
|
293
|
+
textParts.push(scanner.advance());
|
|
278
294
|
}
|
|
279
295
|
}
|
|
280
296
|
// Parses `xmlText` into a small element tree. Never throws: all failures are
|
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import { androidVerdictToHumanLabel } from "../validate/androidVerdict.js";
|
|
2
2
|
const GRADLE_OPERATION_CHECK_PREFIX = "android-gradle-";
|
|
3
3
|
const GRADLE_METADATA_CHECK_ID = "android-gradle-metadata";
|
|
4
|
+
// v0.4.1 Batch 8 — fixed check-id order for the eleven internal advanced
|
|
5
|
+
// checks and four external tools (agents.txt Batch 8 sections 9.1/10.5).
|
|
6
|
+
// Re-declared here (not imported from each check's checkResult.ts) to avoid
|
|
7
|
+
// a report -> advancedSecurity/externalTools import-fan-out; each id is a
|
|
8
|
+
// stable literal already asserted by that check's own regression tests.
|
|
9
|
+
const ADVANCED_SECURITY_CHECK_ID_ORDER = [
|
|
10
|
+
"android-network-security-audit",
|
|
11
|
+
"android-backup-configuration-audit",
|
|
12
|
+
"android-release-configuration-audit",
|
|
13
|
+
"android-secret-candidates-audit",
|
|
14
|
+
"android-signing-configuration-audit",
|
|
15
|
+
"android-webview-security-audit",
|
|
16
|
+
"android-file-provider-audit",
|
|
17
|
+
"android-sensitive-storage-audit",
|
|
18
|
+
"android-sensitive-logging-audit",
|
|
19
|
+
"android-clipboard-security-audit",
|
|
20
|
+
"android-firebase-google-services-audit",
|
|
21
|
+
];
|
|
22
|
+
const EXTERNAL_TOOL_CHECK_ID_ORDER = ["android-semgrep-audit", "android-osv-audit", "android-lint-audit", "android-dependency-check-audit"];
|
|
4
23
|
const STATIC_ANALYSIS_LIMITATIONS = [
|
|
5
24
|
"Static Android project analysis does not prove runtime behavior.",
|
|
6
25
|
"Manifests are parsed independently and are not merged.",
|
|
7
|
-
"
|
|
26
|
+
"Selected local resource references are resolved conservatively; manifest placeholders, full resource merging, and overlay precedence are not resolved.",
|
|
8
27
|
"Gradle files are not evaluated (no Groovy/Kotlin execution).",
|
|
9
28
|
"Dynamic Gradle values may remain unresolved.",
|
|
10
29
|
"Same-file duplicate Gradle assignments use first-match extraction (a known limitation — see agents.txt Batch 4/5 notes).",
|
|
@@ -33,6 +52,10 @@ export function toAndroidReportModel(result, options = {}) {
|
|
|
33
52
|
const deepLinkCount = result.manifests.reduce((sum, m) => sum + m.deepLinks.length, 0);
|
|
34
53
|
const gradleOperationResults = result.checks.filter((c) => c.id.startsWith(GRADLE_OPERATION_CHECK_PREFIX) && c.id !== GRADLE_METADATA_CHECK_ID);
|
|
35
54
|
const gradleMetadataCheck = result.checks.find((c) => c.id === GRADLE_METADATA_CHECK_ID);
|
|
55
|
+
const checksById = new Map(result.checks.map((c) => [c.id, c]));
|
|
56
|
+
const advancedSecurityChecks = ADVANCED_SECURITY_CHECK_ID_ORDER.map((id) => checksById.get(id)).filter((c) => Boolean(c));
|
|
57
|
+
const externalToolChecks = EXTERNAL_TOOL_CHECK_ID_ORDER.map((id) => checksById.get(id)).filter((c) => Boolean(c));
|
|
58
|
+
const candidateEvidenceCount = result.checks.reduce((sum, c) => sum + (c.candidateEvidence?.length ?? 0), 0);
|
|
36
59
|
const verdict = String(result.verdict);
|
|
37
60
|
const isRealVerdict = result.verdict !== "not-calculated";
|
|
38
61
|
return {
|
|
@@ -42,6 +65,8 @@ export function toAndroidReportModel(result, options = {}) {
|
|
|
42
65
|
target: result.target,
|
|
43
66
|
profile: options.profile ?? "android",
|
|
44
67
|
requestedGradleOperations: options.requestedGradleOperations ?? [],
|
|
68
|
+
requestedExternalTools: options.requestedExternalTools ?? [],
|
|
69
|
+
externalNetworkPolicy: options.externalNetworkPolicy ?? "deny",
|
|
45
70
|
generatedAt: result.finishedAt,
|
|
46
71
|
totalDurationMs: result.durationMs,
|
|
47
72
|
},
|
|
@@ -65,6 +90,12 @@ export function toAndroidReportModel(result, options = {}) {
|
|
|
65
90
|
},
|
|
66
91
|
gradleCheckSummary: gradleMetadataCheck ? { title: "Gradle metadata check", summary: `status: ${gradleMetadataCheck.status}` } : undefined,
|
|
67
92
|
gradleOperationResults,
|
|
93
|
+
advancedSecurityChecks,
|
|
94
|
+
externalToolChecks,
|
|
95
|
+
candidateEvidenceSummary: {
|
|
96
|
+
title: "Candidate evidence (review required, not confirmed findings)",
|
|
97
|
+
summary: `${candidateEvidenceCount} candidate evidence item(s) across all checks`,
|
|
98
|
+
},
|
|
68
99
|
releaseMetadataSummary: {
|
|
69
100
|
title: "Release metadata",
|
|
70
101
|
summary: result.releaseMetadata
|
|
@@ -17,6 +17,27 @@ function formatDuration(ms) {
|
|
|
17
17
|
return `${ms}ms`;
|
|
18
18
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
19
19
|
}
|
|
20
|
+
// v0.4.1 Batch 8 — shared per-check rendering for the new ADVANCED SECURITY
|
|
21
|
+
// CHECKS and EXTERNAL TOOL RESULTS sections. Bounded: prints counts and
|
|
22
|
+
// identifiers, never raw stdout/stderr/environment/complete source (that
|
|
23
|
+
// bound is already enforced by each check builder before evidence reaches
|
|
24
|
+
// this report — this function does not itself receive raw tool output).
|
|
25
|
+
function renderCheckSummaryLines(lines, c) {
|
|
26
|
+
lines.push(` ${c.id} [${c.status}] — ${sanitize(c.title)}`);
|
|
27
|
+
lines.push(` category: ${c.category} findings: ${c.findings.length} candidates: ${c.candidateEvidence?.length ?? 0}`);
|
|
28
|
+
if (c.evidence.length > 0)
|
|
29
|
+
lines.push(` evidence: ${c.evidence.map(sanitize).join(" | ")}`);
|
|
30
|
+
if (c.durationMs !== undefined)
|
|
31
|
+
lines.push(` duration: ${formatDuration(c.durationMs)}`);
|
|
32
|
+
if (c.skipInfo)
|
|
33
|
+
lines.push(` skip reason: ${sanitize(c.skipInfo.reason)}`);
|
|
34
|
+
for (const w of c.warnings)
|
|
35
|
+
lines.push(` warning: ${sanitize(w)}`);
|
|
36
|
+
for (const e of c.errors)
|
|
37
|
+
lines.push(` error: ${sanitize(e)}`);
|
|
38
|
+
if (c.environmentRequirements.length > 0)
|
|
39
|
+
lines.push(` limitations: ${c.environmentRequirements.map(sanitize).join(" | ")}`);
|
|
40
|
+
}
|
|
20
41
|
export function renderAndroidTextReport(model) {
|
|
21
42
|
const lines = [];
|
|
22
43
|
// 1. Summary
|
|
@@ -143,6 +164,48 @@ export function renderAndroidTextReport(model) {
|
|
|
143
164
|
}
|
|
144
165
|
}
|
|
145
166
|
lines.push("");
|
|
167
|
+
// 15b. Advanced security checks (v0.4.1 Batch 8)
|
|
168
|
+
lines.push(divider());
|
|
169
|
+
lines.push("ADVANCED SECURITY CHECKS");
|
|
170
|
+
lines.push(divider());
|
|
171
|
+
if (model.advancedSecurityChecks.length === 0) {
|
|
172
|
+
lines.push("(none ran — not applicable to this target)");
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
for (const c of model.advancedSecurityChecks) {
|
|
176
|
+
renderCheckSummaryLines(lines, c);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
lines.push("");
|
|
180
|
+
// 15c. External tool results (v0.4.1 Batch 8)
|
|
181
|
+
lines.push(divider());
|
|
182
|
+
lines.push("EXTERNAL TOOL RESULTS");
|
|
183
|
+
lines.push(divider());
|
|
184
|
+
lines.push(`Requested external tools: ${model.metadata.requestedExternalTools.length > 0 ? model.metadata.requestedExternalTools.join(", ") : "(none — zero external-tool process execution)"}`);
|
|
185
|
+
if (model.metadata.requestedExternalTools.length > 0) {
|
|
186
|
+
lines.push(`Effective network policy: ${model.metadata.externalNetworkPolicy}`);
|
|
187
|
+
}
|
|
188
|
+
if (model.externalToolChecks.length === 0) {
|
|
189
|
+
lines.push("No external tools were requested.");
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
for (const c of model.externalToolChecks) {
|
|
193
|
+
renderCheckSummaryLines(lines, c);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
lines.push("");
|
|
197
|
+
// 15d. Candidate evidence (review required, distinct from confirmed findings)
|
|
198
|
+
lines.push(divider());
|
|
199
|
+
lines.push("CANDIDATE EVIDENCE (REVIEW REQUIRED — NOT CONFIRMED FINDINGS)");
|
|
200
|
+
lines.push(divider());
|
|
201
|
+
lines.push(sanitize(model.candidateEvidenceSummary.summary));
|
|
202
|
+
for (const c of [...model.advancedSecurityChecks, ...model.externalToolChecks]) {
|
|
203
|
+
for (const candidate of c.candidateEvidence ?? []) {
|
|
204
|
+
lines.push(` [${candidate.confidence}/${candidate.resolutionState}] ${candidate.ruleId} — ${sanitize(candidate.summary)}`);
|
|
205
|
+
lines.push(` location: ${candidate.location.path}${candidate.location.line !== undefined ? `:${candidate.location.line}` : ""}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
lines.push("");
|
|
146
209
|
// 16. Release metadata
|
|
147
210
|
lines.push(divider());
|
|
148
211
|
lines.push("RELEASE METADATA");
|
|
@@ -18,6 +18,80 @@ import { buildAndroidPlayReadinessChecklist } from "./playReadinessChecklist.js"
|
|
|
18
18
|
import { calculateAndroidVerdict } from "./androidVerdict.js";
|
|
19
19
|
import { buildAndroidDetectionCheckResult, buildAndroidManifestParsingCheckResult, buildAndroidTargetImmutabilityCheckResult, } from "./detectionAndImmutabilityCheckResults.js";
|
|
20
20
|
import { ANDROID_VALIDATION_ARTIFACT_TYPE, ANDROID_VALIDATION_SCHEMA_VERSION, } from "../validation/result.js";
|
|
21
|
+
import { auditAndroidNetworkSecurity } from "../advancedSecurity/networkSecurity/checkResult.js";
|
|
22
|
+
import { auditAndroidBackupConfiguration } from "../advancedSecurity/backupConfiguration/checkResult.js";
|
|
23
|
+
import { auditAndroidReleaseConfiguration } from "../advancedSecurity/releaseConfiguration/checkResult.js";
|
|
24
|
+
import { auditAndroidSecretCandidates } from "../advancedSecurity/secretCandidates/checkResult.js";
|
|
25
|
+
import { auditAndroidSigningConfiguration } from "../advancedSecurity/signingConfiguration/checkResult.js";
|
|
26
|
+
import { auditAndroidWebViewSecurity } from "../advancedSecurity/webview/checkResult.js";
|
|
27
|
+
import { auditAndroidFileProviders } from "../advancedSecurity/fileProvider/checkResult.js";
|
|
28
|
+
import { auditAndroidSensitiveStorage } from "../advancedSecurity/sensitiveStorage/checkResult.js";
|
|
29
|
+
import { auditAndroidSensitiveLogging } from "../advancedSecurity/sensitiveLogging/checkResult.js";
|
|
30
|
+
import { auditAndroidClipboardSecurity } from "../advancedSecurity/clipboard/checkResult.js";
|
|
31
|
+
import { auditAndroidFirebaseGoogleServices } from "../advancedSecurity/firebaseGoogle/checkResult.js";
|
|
32
|
+
import { runRequestedAndroidExternalTools } from "../advancedSecurity/externalTools/runRequestedAndroidExternalTools.js";
|
|
33
|
+
import { createRealExternalToolExecutor } from "../advancedSecurity/externalTools/runBoundedExternalTool.js";
|
|
34
|
+
import { createRealGradleCommandExecutor } from "../gradle/validate/executor.js";
|
|
35
|
+
function runIsolatedCheck(descriptor) {
|
|
36
|
+
try {
|
|
37
|
+
return descriptor.run();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
41
|
+
return {
|
|
42
|
+
id: descriptor.id,
|
|
43
|
+
category: descriptor.category,
|
|
44
|
+
title: descriptor.title,
|
|
45
|
+
status: "error",
|
|
46
|
+
requirementLevel: "optional",
|
|
47
|
+
ran: false,
|
|
48
|
+
skipped: false,
|
|
49
|
+
evidence: [],
|
|
50
|
+
findings: [],
|
|
51
|
+
warnings: [],
|
|
52
|
+
errors: [`Advanced check raised an unexpected error: ${message}`],
|
|
53
|
+
sourcePaths: [],
|
|
54
|
+
confidence: "unknown",
|
|
55
|
+
environmentRequirements: [],
|
|
56
|
+
candidateEvidence: [],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function buildAdvancedSecurityChecks(targetRoot, detection, manifestEntries, gradleModules) {
|
|
61
|
+
// Fixed order (agents.txt Batch 8 section 9.1) — never alphabetical, never
|
|
62
|
+
// derived from filesystem/object iteration.
|
|
63
|
+
const descriptors = [
|
|
64
|
+
{ id: "android-network-security-audit", category: "android-network-security", title: "Android Network Security Config audit", run: () => auditAndroidNetworkSecurity(targetRoot, detection, manifestEntries) },
|
|
65
|
+
{ id: "android-backup-configuration-audit", category: "android-backup-configuration", title: "Android backup configuration audit", run: () => auditAndroidBackupConfiguration(targetRoot, detection, manifestEntries) },
|
|
66
|
+
{ id: "android-release-configuration-audit", category: "android-release-configuration", title: "Android release configuration audit", run: () => auditAndroidReleaseConfiguration(targetRoot, detection, manifestEntries, gradleModules) },
|
|
67
|
+
{ id: "android-secret-candidates-audit", category: "android-secret-candidates", title: "Android hardcoded secret-candidate audit", run: () => auditAndroidSecretCandidates(targetRoot, detection) },
|
|
68
|
+
{ id: "android-signing-configuration-audit", category: "android-signing-configuration", title: "Android signing configuration audit", run: () => auditAndroidSigningConfiguration(targetRoot, detection, gradleModules) },
|
|
69
|
+
{ id: "android-webview-security-audit", category: "android-webview", title: "Android WebView security audit", run: () => auditAndroidWebViewSecurity(targetRoot, detection) },
|
|
70
|
+
{ id: "android-file-provider-audit", category: "android-file-provider", title: "Android FileProvider security audit", run: () => auditAndroidFileProviders(targetRoot, detection, manifestEntries) },
|
|
71
|
+
{ id: "android-sensitive-storage-audit", category: "android-sensitive-storage", title: "Android sensitive-storage audit", run: () => auditAndroidSensitiveStorage(targetRoot, detection) },
|
|
72
|
+
{ id: "android-sensitive-logging-audit", category: "android-sensitive-logging", title: "Android sensitive-logging audit", run: () => auditAndroidSensitiveLogging(targetRoot, detection) },
|
|
73
|
+
{ id: "android-clipboard-security-audit", category: "android-clipboard", title: "Android clipboard security audit", run: () => auditAndroidClipboardSecurity(targetRoot, detection) },
|
|
74
|
+
{ id: "android-firebase-google-services-audit", category: "android-firebase-google-services", title: "Android Firebase/Google services audit", run: () => auditAndroidFirebaseGoogleServices(targetRoot, detection) },
|
|
75
|
+
];
|
|
76
|
+
return descriptors.map(runIsolatedCheck);
|
|
77
|
+
}
|
|
78
|
+
// Memoizes by Gradle operation id: the fixed allowlist (operations.ts) maps
|
|
79
|
+
// each operation id to fixed args, so within one validation run the same
|
|
80
|
+
// operation id always means the same command — caching by id is safe and
|
|
81
|
+
// prevents running `lintDebug` twice when both --android-gradle-operations
|
|
82
|
+
// lint-debug and --android-external-tools android-lint are requested
|
|
83
|
+
// together (agents.txt Batch 8 section 10.6).
|
|
84
|
+
function createDedupingGradleExecutor(base) {
|
|
85
|
+
const cache = new Map();
|
|
86
|
+
return (plan) => {
|
|
87
|
+
const cached = cache.get(plan.operationId);
|
|
88
|
+
if (cached)
|
|
89
|
+
return cached;
|
|
90
|
+
const promise = base(plan);
|
|
91
|
+
cache.set(plan.operationId, promise);
|
|
92
|
+
return promise;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
21
95
|
function readToolPackageMetadata(toolRoot) {
|
|
22
96
|
try {
|
|
23
97
|
const pkgRaw = fs.readFileSync(path.join(toolRoot, "package.json"), "utf8");
|
|
@@ -75,6 +149,11 @@ export async function validateAndroidTarget(options) {
|
|
|
75
149
|
// batch owner).
|
|
76
150
|
const detectionCheck = buildAndroidDetectionCheckResult(detection);
|
|
77
151
|
const manifestParsingCheck = buildAndroidManifestParsingCheckResult(detection, manifestEntries);
|
|
152
|
+
// Shared, deduping Gradle executor: reused for both --android-gradle-
|
|
153
|
+
// operations and (if android-lint is also externally requested) the
|
|
154
|
+
// Batch 7 Android Lint adapter, so `lintDebug` never runs twice for one
|
|
155
|
+
// validation run (agents.txt Batch 8 section 10.6).
|
|
156
|
+
const sharedGradleExecutor = createDedupingGradleExecutor(options.gradleExecutor ?? createRealGradleCommandExecutor());
|
|
78
157
|
// 15-16. Optional Gradle operations — never run unless explicitly requested.
|
|
79
158
|
const requestedOperationIds = options.requestedGradleOperationIds ?? [];
|
|
80
159
|
const gradleOperationResults = requestedOperationIds.length > 0
|
|
@@ -83,17 +162,79 @@ export async function validateAndroidTarget(options) {
|
|
|
83
162
|
targetRoot: resolvedTargetRoot,
|
|
84
163
|
detection,
|
|
85
164
|
operationIds: requestedOperationIds,
|
|
86
|
-
executor:
|
|
165
|
+
executor: sharedGradleExecutor,
|
|
87
166
|
allowWithoutTaskDiscovery: options.allowGradleWithoutTaskDiscovery,
|
|
88
167
|
platform: options.platform,
|
|
89
168
|
})
|
|
90
169
|
: [];
|
|
91
|
-
//
|
|
170
|
+
// v0.4.1 Batch 8 — eleven internal advanced Android checks, active by
|
|
171
|
+
// default (agents.txt Batch 8 section 9.1). Sequential, fixed order,
|
|
172
|
+
// isolated per-check via runIsolatedCheck. Never executes a process or
|
|
173
|
+
// touches the network.
|
|
174
|
+
const advancedSecurityChecks = buildAdvancedSecurityChecks(resolvedTargetRoot, detection, manifestEntries, gradle.modules);
|
|
175
|
+
// v0.4.1 Batch 8 — optional external-tool integration. Never executes
|
|
176
|
+
// unless requestedExternalToolIds is non-empty (same "no request, no
|
|
177
|
+
// execution" convention as Gradle operations). Wrapped in try/catch so a
|
|
178
|
+
// dispatcher-level failure (e.g. an unknown tool id slipping past CLI
|
|
179
|
+
// validation in a programmatic caller) cannot discard the checks already
|
|
180
|
+
// gathered above.
|
|
181
|
+
const requestedExternalToolIds = options.requestedExternalToolIds ?? [];
|
|
182
|
+
let externalToolChecks = [];
|
|
183
|
+
if (requestedExternalToolIds.length > 0) {
|
|
184
|
+
try {
|
|
185
|
+
externalToolChecks = await runRequestedAndroidExternalTools({
|
|
186
|
+
request: {
|
|
187
|
+
requestedTools: requestedExternalToolIds,
|
|
188
|
+
targetRoot: resolvedTargetRoot,
|
|
189
|
+
artifactRoot: options.externalToolArtifactRoot ?? path.join(options.toolRoot, "reports", "security", "external-tools"),
|
|
190
|
+
networkPolicy: options.externalNetworkPolicy ?? "deny",
|
|
191
|
+
},
|
|
192
|
+
detection,
|
|
193
|
+
executors: {
|
|
194
|
+
semgrep: options.externalToolExecutors?.semgrep ?? createRealExternalToolExecutor(),
|
|
195
|
+
osv: options.externalToolExecutors?.osv ?? createRealExternalToolExecutor(),
|
|
196
|
+
androidLint: options.externalToolExecutors?.androidLint ?? sharedGradleExecutor,
|
|
197
|
+
dependencyCheck: options.externalToolExecutors?.dependencyCheck ?? createRealExternalToolExecutor(),
|
|
198
|
+
},
|
|
199
|
+
discover: options.externalToolDiscover,
|
|
200
|
+
javaAvailable: options.javaAvailable,
|
|
201
|
+
lintTaskAvailable: options.lintTaskAvailable,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
206
|
+
externalToolChecks = [
|
|
207
|
+
{
|
|
208
|
+
id: "android-external-tools-dispatch",
|
|
209
|
+
category: "android-semgrep",
|
|
210
|
+
title: "External security-tool dispatch",
|
|
211
|
+
status: "error",
|
|
212
|
+
requirementLevel: "optional",
|
|
213
|
+
ran: false,
|
|
214
|
+
skipped: false,
|
|
215
|
+
evidence: [],
|
|
216
|
+
findings: [],
|
|
217
|
+
warnings: [],
|
|
218
|
+
errors: [`External-tool dispatch raised an unexpected error: ${message}`],
|
|
219
|
+
sourcePaths: [],
|
|
220
|
+
confidence: "unknown",
|
|
221
|
+
environmentRequirements: [],
|
|
222
|
+
candidateEvidence: [],
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// 17. Target mutation evidence — captured after every process this run may
|
|
228
|
+
// have executed (Gradle operations, advanced checks, and external tools),
|
|
92
229
|
// so the static-only path also proves it made no changes.
|
|
93
230
|
const snapshotAfter = captureTargetSnapshot(resolvedTargetRoot, hasGit);
|
|
94
231
|
const targetMutation = buildTargetMutationReport(snapshotBefore, snapshotAfter);
|
|
95
232
|
const immutabilityCheck = buildAndroidTargetImmutabilityCheckResult(targetMutation);
|
|
96
|
-
// 18. Assemble checks/findings in deterministic order (agents.txt
|
|
233
|
+
// 18. Assemble checks/findings in deterministic order (agents.txt Batch 8
|
|
234
|
+
// section 11.1): the existing v0.4.0 check order is preserved exactly
|
|
235
|
+
// (ending with immutabilityCheck, as before), and the eleven advanced
|
|
236
|
+
// checks and any requested external tools are APPENDED after it — never
|
|
237
|
+
// interleaved into the existing order.
|
|
97
238
|
const checks = [
|
|
98
239
|
detectionCheck,
|
|
99
240
|
manifestParsingCheck,
|
|
@@ -104,6 +245,8 @@ export async function validateAndroidTarget(options) {
|
|
|
104
245
|
gradleMetadataCheck,
|
|
105
246
|
...gradleOperationResults,
|
|
106
247
|
immutabilityCheck,
|
|
248
|
+
...advancedSecurityChecks,
|
|
249
|
+
...externalToolChecks,
|
|
107
250
|
];
|
|
108
251
|
const findingsById = new Map();
|
|
109
252
|
for (const check of checks) {
|