@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
package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/analyzeReleaseConfiguration.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { makeAndroidFinding } from "../../audit/androidFinding.js";
|
|
3
|
+
import { buildAndroidSourceLocation } from "../sourceLocation.js";
|
|
4
|
+
import { makeCandidateEvidence } from "../candidateEvidence.js";
|
|
5
|
+
import { extractManifestReleaseEvidence } from "./manifestEvidence.js";
|
|
6
|
+
const RELEASE_BUILD_TYPE_NAME = "release";
|
|
7
|
+
function loc(targetRoot, absolutePath, position) {
|
|
8
|
+
return buildAndroidSourceLocation(targetRoot, absolutePath, position);
|
|
9
|
+
}
|
|
10
|
+
// Exact-name match only (agents.txt Batch 3 section 9.11) — no guessing by
|
|
11
|
+
// substring, no flavor/variant evaluation. All matches are preserved (a
|
|
12
|
+
// module could in principle declare more than one via extraction quirks);
|
|
13
|
+
// ambiguity is never resolved arbitrarily.
|
|
14
|
+
function findReleaseLikeBuildTypes(gradleModule) {
|
|
15
|
+
return (gradleModule?.buildTypeDetails ?? []).filter((buildType) => buildType.name === RELEASE_BUILD_TYPE_NAME);
|
|
16
|
+
}
|
|
17
|
+
export function analyzeApplicationReleaseConfiguration(targetRoot, entry, gradleModules) {
|
|
18
|
+
const manifestEvidence = extractManifestReleaseEvidence(entry);
|
|
19
|
+
const candidates = [];
|
|
20
|
+
const findings = [];
|
|
21
|
+
const evidenceText = [];
|
|
22
|
+
const modulePath = entry.modulePath;
|
|
23
|
+
const manifestAbsolutePath = path.join(targetRoot, entry.manifestPath);
|
|
24
|
+
const gradleModule = gradleModules.find((m) => m.path === modulePath);
|
|
25
|
+
// Only suppressed (downgraded to candidate) when a module is confidently
|
|
26
|
+
// library-only — an ambiguous/unknown module type is still treated as an
|
|
27
|
+
// application for conservative reporting purposes.
|
|
28
|
+
const isLibraryOnlyModule = gradleModule?.isLibrary === true && gradleModule?.isApplication !== true;
|
|
29
|
+
// --- manifest debuggable ---
|
|
30
|
+
if (manifestEvidence.debuggable.state === "explicit-true") {
|
|
31
|
+
if (isLibraryOnlyModule) {
|
|
32
|
+
candidates.push(makeCandidateEvidence({
|
|
33
|
+
ruleId: "android-release-debuggable",
|
|
34
|
+
category: "android-release-configuration",
|
|
35
|
+
confidence: "low",
|
|
36
|
+
modulePath,
|
|
37
|
+
location: loc(targetRoot, manifestAbsolutePath, manifestEvidence.applicationLocation),
|
|
38
|
+
summary: 'android:debuggable="true" declared in a library module manifest (not an installable application)',
|
|
39
|
+
rawValue: manifestEvidence.debuggable.raw,
|
|
40
|
+
resolutionState: "not-applicable",
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
findings.push(makeAndroidFinding({
|
|
45
|
+
ruleId: "android-release-debuggable",
|
|
46
|
+
title: 'Manifest explicitly enables debuggable (android:debuggable="true")',
|
|
47
|
+
severity: "major",
|
|
48
|
+
confidence: "high",
|
|
49
|
+
description: 'The manifest\'s <application> element sets android:debuggable="true". This is high-confidence static evidence of a risky configuration if shipped in a release build; it does not prove the published/installed APK is actually debuggable — Gradle build-type configuration and manifest merging are not evaluated here.',
|
|
50
|
+
manifestPath: entry.manifestPath,
|
|
51
|
+
location: manifestEvidence.applicationLocation,
|
|
52
|
+
evidenceDetails: ["source=manifest-attribute"],
|
|
53
|
+
recommendation: 'Set android:debuggable="false" (or omit it) for release builds; confirm no debug manifest overlay reaches production.',
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (manifestEvidence.debuggable.state === "malformed") {
|
|
58
|
+
candidates.push(makeCandidateEvidence({
|
|
59
|
+
ruleId: "android-release-debuggable",
|
|
60
|
+
category: "android-release-configuration",
|
|
61
|
+
confidence: "low",
|
|
62
|
+
modulePath,
|
|
63
|
+
location: loc(targetRoot, manifestAbsolutePath, manifestEvidence.applicationLocation),
|
|
64
|
+
summary: "android:debuggable has an unresolved or malformed value",
|
|
65
|
+
rawValue: manifestEvidence.debuggable.raw,
|
|
66
|
+
resolutionState: "malformed",
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
else if (manifestEvidence.debuggable.state === "explicit-false") {
|
|
70
|
+
evidenceText.push(`${entry.manifestPath}: manifest debuggable is explicitly false (safe)`);
|
|
71
|
+
}
|
|
72
|
+
// --- manifest testOnly ---
|
|
73
|
+
if (manifestEvidence.testOnly.state === "explicit-true") {
|
|
74
|
+
if (isLibraryOnlyModule) {
|
|
75
|
+
candidates.push(makeCandidateEvidence({
|
|
76
|
+
ruleId: "android-release-test-only",
|
|
77
|
+
category: "android-release-configuration",
|
|
78
|
+
confidence: "low",
|
|
79
|
+
modulePath,
|
|
80
|
+
location: loc(targetRoot, manifestAbsolutePath, manifestEvidence.applicationLocation),
|
|
81
|
+
summary: 'android:testOnly="true" declared in a library module manifest',
|
|
82
|
+
rawValue: manifestEvidence.testOnly.raw,
|
|
83
|
+
resolutionState: "not-applicable",
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
findings.push(makeAndroidFinding({
|
|
88
|
+
ruleId: "android-release-test-only",
|
|
89
|
+
title: 'Manifest explicitly marks the application test-only (android:testOnly="true")',
|
|
90
|
+
severity: "major",
|
|
91
|
+
confidence: "high",
|
|
92
|
+
description: 'The manifest\'s <application> element sets android:testOnly="true", which the platform install mechanism (and Play Store) treats as release-blocking. This does not prove the package was ever installed or published.',
|
|
93
|
+
manifestPath: entry.manifestPath,
|
|
94
|
+
location: manifestEvidence.applicationLocation,
|
|
95
|
+
evidenceDetails: ["source=manifest-attribute"],
|
|
96
|
+
recommendation: 'Remove android:testOnly="true" before release; confirm no test/debug manifest overlay reaches production.',
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (manifestEvidence.testOnly.state === "malformed") {
|
|
101
|
+
candidates.push(makeCandidateEvidence({
|
|
102
|
+
ruleId: "android-release-test-only",
|
|
103
|
+
category: "android-release-configuration",
|
|
104
|
+
confidence: "low",
|
|
105
|
+
modulePath,
|
|
106
|
+
location: loc(targetRoot, manifestAbsolutePath, manifestEvidence.applicationLocation),
|
|
107
|
+
summary: "android:testOnly has an unresolved or malformed value",
|
|
108
|
+
rawValue: manifestEvidence.testOnly.raw,
|
|
109
|
+
resolutionState: "malformed",
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
// --- Gradle release build-type ---
|
|
113
|
+
const releaseBuildTypes = findReleaseLikeBuildTypes(gradleModule);
|
|
114
|
+
const buildFileRelativePath = gradleModule?.buildFilePath;
|
|
115
|
+
const buildFileAbsolutePath = buildFileRelativePath !== undefined ? path.join(targetRoot, buildFileRelativePath) : undefined;
|
|
116
|
+
const fallbackLocation = buildFileAbsolutePath !== undefined ? loc(targetRoot, buildFileAbsolutePath) : loc(targetRoot, manifestAbsolutePath);
|
|
117
|
+
if (releaseBuildTypes.length === 0) {
|
|
118
|
+
candidates.push(makeCandidateEvidence({
|
|
119
|
+
ruleId: "android-release-build-type-configuration",
|
|
120
|
+
category: "android-release-configuration",
|
|
121
|
+
confidence: "low",
|
|
122
|
+
modulePath,
|
|
123
|
+
location: fallbackLocation,
|
|
124
|
+
summary: `No "release" build type was statically found for module ${modulePath ?? "(unknown)"}`,
|
|
125
|
+
rawValue: undefined,
|
|
126
|
+
resolutionState: "missing",
|
|
127
|
+
staticAnalysisLimitations: [
|
|
128
|
+
"A missing release build type is not automatically a vulnerability; Gradle default build types, convention plugins, and variant configuration are not evaluated.",
|
|
129
|
+
],
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
let gradleReleaseDebuggableTrue = false;
|
|
133
|
+
let gradleReleaseDebuggableFalse = false;
|
|
134
|
+
for (const buildType of releaseBuildTypes) {
|
|
135
|
+
if (buildType.debuggableState === "literal-true") {
|
|
136
|
+
gradleReleaseDebuggableTrue = true;
|
|
137
|
+
if (!isLibraryOnlyModule) {
|
|
138
|
+
findings.push(makeAndroidFinding({
|
|
139
|
+
ruleId: "android-release-debuggable",
|
|
140
|
+
title: `Release build type "${buildType.name}" sets debuggable=true`,
|
|
141
|
+
severity: "major",
|
|
142
|
+
confidence: "high",
|
|
143
|
+
description: `The Gradle release build type "${buildType.name}" sets a literal debuggable=true. This is high-confidence static evidence; it does not prove a built APK is actually debuggable — build-type/flavor/variant selection and Gradle conventions are not evaluated.`,
|
|
144
|
+
manifestPath: buildFileRelativePath ?? entry.manifestPath,
|
|
145
|
+
identity: buildType.name,
|
|
146
|
+
evidenceDetails: [`buildType=${buildType.name}`, "source=gradle-literal"],
|
|
147
|
+
recommendation: "Set debuggable=false (or remove the override) for the release build type.",
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
candidates.push(makeCandidateEvidence({
|
|
152
|
+
ruleId: "android-release-debuggable",
|
|
153
|
+
category: "android-release-configuration",
|
|
154
|
+
confidence: "low",
|
|
155
|
+
modulePath,
|
|
156
|
+
location: fallbackLocation,
|
|
157
|
+
summary: `Library module's "${buildType.name}" build type sets debuggable=true (not an installable application)`,
|
|
158
|
+
rawValue: buildType.debuggableRaw,
|
|
159
|
+
resolutionState: "not-applicable",
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (buildType.debuggableState === "literal-false") {
|
|
164
|
+
gradleReleaseDebuggableFalse = true;
|
|
165
|
+
evidenceText.push(`${modulePath ?? "(unknown module)"}: release build type "${buildType.name}" debuggable is explicitly false (safe)`);
|
|
166
|
+
}
|
|
167
|
+
else if (buildType.debuggableState === "dynamic") {
|
|
168
|
+
candidates.push(makeCandidateEvidence({
|
|
169
|
+
ruleId: "android-release-debuggable",
|
|
170
|
+
category: "android-release-configuration",
|
|
171
|
+
confidence: "medium",
|
|
172
|
+
modulePath,
|
|
173
|
+
location: fallbackLocation,
|
|
174
|
+
summary: `Release build type "${buildType.name}" debuggable value is a dynamic/unresolved expression`,
|
|
175
|
+
rawValue: buildType.debuggableRaw,
|
|
176
|
+
resolutionState: "unresolved",
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
// Minification/resource-shrinking/signingConfig — release-hardening
|
|
180
|
+
// metadata only, never a finding (agents.txt Batch 3 sections 9.13/9.16).
|
|
181
|
+
evidenceText.push(`${modulePath ?? "(unknown module)"}: release build type "${buildType.name}" minifyEnabled=${buildType.minifyEnabledState}, shrinkResources=${buildType.shrinkResourcesState}`);
|
|
182
|
+
if (buildType.signingConfigRef !== undefined) {
|
|
183
|
+
evidenceText.push(`${modulePath ?? "(unknown module)"}: release build type "${buildType.name}" signingConfig=${buildType.signingConfigRef}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// --- manifest/Gradle correlation (never invents a build-time winner) ---
|
|
187
|
+
if (manifestEvidence.debuggable.state === "explicit-true" && gradleReleaseDebuggableTrue) {
|
|
188
|
+
evidenceText.push(`${entry.manifestPath}: manifest debuggable=true aligns with release build type debuggable=true (aligned risky evidence)`);
|
|
189
|
+
}
|
|
190
|
+
else if (manifestEvidence.debuggable.state === "explicit-true" && gradleReleaseDebuggableFalse) {
|
|
191
|
+
candidates.push(makeCandidateEvidence({
|
|
192
|
+
ruleId: "android-release-debuggable",
|
|
193
|
+
category: "android-release-configuration",
|
|
194
|
+
confidence: "medium",
|
|
195
|
+
modulePath,
|
|
196
|
+
location: loc(targetRoot, manifestAbsolutePath, manifestEvidence.applicationLocation),
|
|
197
|
+
summary: "Manifest debuggable=true conflicts with the release build type's literal debuggable=false",
|
|
198
|
+
rawValue: manifestEvidence.debuggable.raw,
|
|
199
|
+
resolutionState: "resolved",
|
|
200
|
+
staticAnalysisLimitations: [
|
|
201
|
+
"Precedence between the manifest attribute and Gradle build-type configuration was not evaluated; this is conflicting static evidence, not a confirmed build-time value.",
|
|
202
|
+
],
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
else if (manifestEvidence.debuggable.state === "explicit-false" && gradleReleaseDebuggableTrue) {
|
|
206
|
+
candidates.push(makeCandidateEvidence({
|
|
207
|
+
ruleId: "android-release-debuggable",
|
|
208
|
+
category: "android-release-configuration",
|
|
209
|
+
confidence: "medium",
|
|
210
|
+
modulePath,
|
|
211
|
+
location: loc(targetRoot, manifestAbsolutePath, manifestEvidence.applicationLocation),
|
|
212
|
+
summary: "Manifest debuggable=false conflicts with the release build type's literal debuggable=true",
|
|
213
|
+
rawValue: manifestEvidence.debuggable.raw,
|
|
214
|
+
resolutionState: "resolved",
|
|
215
|
+
staticAnalysisLimitations: [
|
|
216
|
+
"Precedence between the manifest attribute and Gradle build-type configuration was not evaluated; this is conflicting static evidence, not a confirmed build-time value.",
|
|
217
|
+
],
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
return { manifestEvidence, candidates, findings, evidenceText };
|
|
221
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { buildAndroidManifestCheckResult } from "../../audit/checkResultBuilder.js";
|
|
2
|
+
import { sortCandidateEvidence } from "../ordering.js";
|
|
3
|
+
import { analyzeApplicationReleaseConfiguration } from "./analyzeReleaseConfiguration.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// v0.4.1 Batch 3 — standalone Android debuggable/testOnly/release build-type
|
|
6
|
+
// check result. Mirrors Batch 2/Batch 3-backup's checkResult.ts convention.
|
|
7
|
+
// Standalone: not called from validateAndroidTarget / any active
|
|
8
|
+
// orchestration, not rendered in reports, no CLI effect. Batch 8 integrates.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export const ANDROID_RELEASE_CONFIGURATION_AUDIT_CHECK_ID = "android-release-configuration-audit";
|
|
11
|
+
export function auditAndroidReleaseConfiguration(targetRoot, detection, manifests, gradleModules) {
|
|
12
|
+
const findings = [];
|
|
13
|
+
const candidateEvidence = [];
|
|
14
|
+
const evidence = [];
|
|
15
|
+
const warnings = [];
|
|
16
|
+
for (const entry of manifests) {
|
|
17
|
+
const result = analyzeApplicationReleaseConfiguration(targetRoot, entry, gradleModules);
|
|
18
|
+
findings.push(...result.findings);
|
|
19
|
+
candidateEvidence.push(...result.candidates);
|
|
20
|
+
evidence.push(`${entry.manifestPath}: debuggable=${result.manifestEvidence.debuggable.state}, testOnly=${result.manifestEvidence.testOnly.state}`, ...result.evidenceText);
|
|
21
|
+
}
|
|
22
|
+
const base = buildAndroidManifestCheckResult({
|
|
23
|
+
id: ANDROID_RELEASE_CONFIGURATION_AUDIT_CHECK_ID,
|
|
24
|
+
category: "android-release-configuration",
|
|
25
|
+
title: "Android debuggable, testOnly, and release build-type configuration audit",
|
|
26
|
+
detection,
|
|
27
|
+
manifests,
|
|
28
|
+
findings,
|
|
29
|
+
evidence,
|
|
30
|
+
warnings: [
|
|
31
|
+
...warnings,
|
|
32
|
+
"Static analysis only: does not evaluate Gradle variant/flavor selection, manifest merging, or whether a published APK is actually debuggable.",
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
...base,
|
|
37
|
+
candidateEvidence: sortCandidateEvidence(candidateEvidence),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 3 — public exports for the standalone Android debuggable/
|
|
3
|
+
// testOnly/release build-type audit.
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
export { extractManifestReleaseEvidence } from "./manifestEvidence.js";
|
|
6
|
+
export { analyzeApplicationReleaseConfiguration } from "./analyzeReleaseConfiguration.js";
|
|
7
|
+
export { auditAndroidReleaseConfiguration, ANDROID_RELEASE_CONFIGURATION_AUDIT_CHECK_ID } from "./checkResult.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function classifyBooleanAttribute(value, raw) {
|
|
2
|
+
if (value === true)
|
|
3
|
+
return { state: "explicit-true", raw: raw ?? "true" };
|
|
4
|
+
if (value === false)
|
|
5
|
+
return { state: "explicit-false", raw: raw ?? "false" };
|
|
6
|
+
if (raw !== undefined)
|
|
7
|
+
return { state: "malformed", raw };
|
|
8
|
+
return { state: "missing" };
|
|
9
|
+
}
|
|
10
|
+
export function extractManifestReleaseEvidence(entry) {
|
|
11
|
+
const app = entry.manifest.application;
|
|
12
|
+
return {
|
|
13
|
+
manifestPath: entry.manifestPath,
|
|
14
|
+
modulePath: entry.modulePath,
|
|
15
|
+
applicationLocation: app.location,
|
|
16
|
+
debuggable: classifyBooleanAttribute(app.debuggable, app.debuggableRaw),
|
|
17
|
+
testOnly: classifyBooleanAttribute(app.testOnly, app.testOnlyRaw),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 1 — Android resource-reference parsing (`@xml/...` and
|
|
3
|
+
// similar). This is genuinely new: no resource-reference resolution exists
|
|
4
|
+
// anywhere in the codebase yet (the manifest parser stores refs like
|
|
5
|
+
// `application.networkSecurityConfigRef` verbatim, by design — see
|
|
6
|
+
// src/mobile/android/manifest/types.ts's comment). This module only parses
|
|
7
|
+
// the reference string into a structured shape; resourceResolution.ts
|
|
8
|
+
// resolves it against the filesystem.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export const SUPPORTED_ANDROID_RESOURCE_TYPES = ["xml"];
|
|
11
|
+
const RESOURCE_NAME_PATTERN = /^[a-z0-9_]+$/;
|
|
12
|
+
// Parses a raw manifest/resource attribute value such as `@xml/network_security_config`.
|
|
13
|
+
// Never throws — every input maps to a structured state.
|
|
14
|
+
export function parseAndroidResourceReference(raw) {
|
|
15
|
+
if (raw.length === 0) {
|
|
16
|
+
return { state: "empty", raw };
|
|
17
|
+
}
|
|
18
|
+
const trimmed = raw.trim();
|
|
19
|
+
if (trimmed.length === 0) {
|
|
20
|
+
return { state: "malformed", raw, reason: "Reference is whitespace-only" };
|
|
21
|
+
}
|
|
22
|
+
// A `@string/app_name`-style placeholder that itself resolves to another
|
|
23
|
+
// resource is out of scope for this parser (only concrete @xml/... value
|
|
24
|
+
// references are supported); an unresolved build-variable placeholder such
|
|
25
|
+
// as `${networkSecurityConfig}` is reported distinctly.
|
|
26
|
+
if (/^\$\{.*\}$/.test(trimmed)) {
|
|
27
|
+
return { state: "placeholder", raw };
|
|
28
|
+
}
|
|
29
|
+
if (!trimmed.startsWith("@")) {
|
|
30
|
+
return { state: "malformed", raw, reason: "Reference does not start with \"@\"" };
|
|
31
|
+
}
|
|
32
|
+
const body = trimmed.slice(1);
|
|
33
|
+
if (body.length === 0) {
|
|
34
|
+
return { state: "malformed", raw, reason: "Reference has no content after \"@\"" };
|
|
35
|
+
}
|
|
36
|
+
// Package-qualified external reference: @com.example.lib:xml/name
|
|
37
|
+
const colonIndex = body.indexOf(":");
|
|
38
|
+
let packageQualifier;
|
|
39
|
+
let rest = body;
|
|
40
|
+
if (colonIndex !== -1) {
|
|
41
|
+
packageQualifier = body.slice(0, colonIndex);
|
|
42
|
+
rest = body.slice(colonIndex + 1);
|
|
43
|
+
if (packageQualifier.length === 0) {
|
|
44
|
+
return { state: "malformed", raw, reason: "Empty package qualifier before \":\"" };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const slashIndex = rest.indexOf("/");
|
|
48
|
+
if (slashIndex === -1) {
|
|
49
|
+
return { state: "malformed", raw, reason: "Reference is missing a \"/\" between resource type and name" };
|
|
50
|
+
}
|
|
51
|
+
const type = rest.slice(0, slashIndex);
|
|
52
|
+
const name = rest.slice(slashIndex + 1);
|
|
53
|
+
if (type.length === 0) {
|
|
54
|
+
return { state: "malformed", raw, reason: "Reference is missing a resource type" };
|
|
55
|
+
}
|
|
56
|
+
if (name.length === 0) {
|
|
57
|
+
return { state: "malformed", raw, reason: "Reference is missing a resource name" };
|
|
58
|
+
}
|
|
59
|
+
if (!RESOURCE_NAME_PATTERN.test(name)) {
|
|
60
|
+
return { state: "malformed", raw, reason: `Resource name "${name}" is not a valid Android resource identifier` };
|
|
61
|
+
}
|
|
62
|
+
if (packageQualifier !== undefined) {
|
|
63
|
+
return { state: "package-qualified", raw, packageQualifier, type, name };
|
|
64
|
+
}
|
|
65
|
+
if (!SUPPORTED_ANDROID_RESOURCE_TYPES.includes(type)) {
|
|
66
|
+
return { state: "unsupported-type", raw, type, name };
|
|
67
|
+
}
|
|
68
|
+
return { state: "parsed", raw, type: type, name };
|
|
69
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { relativeWithinRoot, resolveWithinRoot } from "../../../core/pathSafety.js";
|
|
4
|
+
import { parseAndroidResourceReference } from "./resourceReference.js";
|
|
5
|
+
const DEFAULT_SOURCE_SETS = ["main", "debug", "release"];
|
|
6
|
+
// Returns true only when `candidatePath` both lexically resolves within root
|
|
7
|
+
// (resolveWithinRoot) AND, if it is or passes through a symlink, its real
|
|
8
|
+
// path also resolves within root. Never throws — a filesystem error (e.g.
|
|
9
|
+
// broken symlink) is treated as "does not safely resolve".
|
|
10
|
+
function isSafelyContainedExistingFile(root, candidatePath) {
|
|
11
|
+
try {
|
|
12
|
+
resolveWithinRoot(root, candidatePath);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
let stat;
|
|
18
|
+
try {
|
|
19
|
+
stat = fs.lstatSync(candidatePath);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (!stat.isFile() && !stat.isSymbolicLink()) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (stat.isSymbolicLink()) {
|
|
28
|
+
let realPath;
|
|
29
|
+
try {
|
|
30
|
+
realPath = fs.realpathSync(candidatePath);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
resolveWithinRoot(root, realPath);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
if (!fs.statSync(realPath).isFile())
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
function listCandidateSourceSets(root, modulePath) {
|
|
52
|
+
const srcDir = path.join(modulePath, "src");
|
|
53
|
+
let entries;
|
|
54
|
+
try {
|
|
55
|
+
entries = fs.readdirSync(srcDir);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return [...DEFAULT_SOURCE_SETS];
|
|
59
|
+
}
|
|
60
|
+
const discovered = new Set(DEFAULT_SOURCE_SETS);
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
const entryPath = path.join(srcDir, entry);
|
|
63
|
+
try {
|
|
64
|
+
resolveWithinRoot(root, entryPath);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
let stat;
|
|
70
|
+
try {
|
|
71
|
+
stat = fs.lstatSync(entryPath);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (stat.isDirectory() || stat.isSymbolicLink()) {
|
|
77
|
+
discovered.add(entry);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Deterministic order: independent of readdirSync's filesystem-dependent
|
|
81
|
+
// enumeration order.
|
|
82
|
+
return [...discovered].sort((a, b) => a.localeCompare(b));
|
|
83
|
+
}
|
|
84
|
+
export function resolveAndroidXmlResourceReference(root, modulePath, rawReference) {
|
|
85
|
+
const parsed = parseAndroidResourceReference(rawReference);
|
|
86
|
+
if (parsed.state === "malformed" || parsed.state === "empty" || parsed.state === "placeholder") {
|
|
87
|
+
return {
|
|
88
|
+
state: "malformed-reference",
|
|
89
|
+
reference: rawReference,
|
|
90
|
+
modulePath: relativeWithinRoot(root, modulePath),
|
|
91
|
+
reason: parsed.state === "malformed" ? parsed.reason : `Reference is ${parsed.state}, not a concrete resource value`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (parsed.state === "unsupported-type" || parsed.state === "package-qualified") {
|
|
95
|
+
return {
|
|
96
|
+
state: "unsupported-reference",
|
|
97
|
+
reference: rawReference,
|
|
98
|
+
modulePath: relativeWithinRoot(root, modulePath),
|
|
99
|
+
parsed,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const sourceSets = listCandidateSourceSets(root, modulePath);
|
|
103
|
+
const candidates = [];
|
|
104
|
+
for (const sourceSet of sourceSets) {
|
|
105
|
+
const candidatePath = path.join(modulePath, "src", sourceSet, "res", parsed.type, `${parsed.name}.xml`);
|
|
106
|
+
if (isSafelyContainedExistingFile(root, candidatePath)) {
|
|
107
|
+
candidates.push({ sourceSet, relativePath: relativeWithinRoot(root, candidatePath) });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const relativeModulePath = relativeWithinRoot(root, modulePath);
|
|
111
|
+
if (candidates.length === 0) {
|
|
112
|
+
return { state: "missing", reference: rawReference, modulePath: relativeModulePath, searchedSourceSets: sourceSets };
|
|
113
|
+
}
|
|
114
|
+
if (candidates.length === 1) {
|
|
115
|
+
return { state: "resolved", reference: rawReference, modulePath: relativeModulePath, candidates: [candidates[0]] };
|
|
116
|
+
}
|
|
117
|
+
return { state: "ambiguous", reference: rawReference, modulePath: relativeModulePath, candidates };
|
|
118
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 1 — stable rule identifiers for the advanced Android security
|
|
3
|
+
// checks planned for later v0.4.1 batches.
|
|
4
|
+
//
|
|
5
|
+
// This file defines identifiers only. No detector, finding, or check runs
|
|
6
|
+
// because a rule id exists here — later batches implement the check logic
|
|
7
|
+
// and reference these ids. Follows the existing per-audit-file "android-"
|
|
8
|
+
// prefixed string-literal convention (see src/mobile/android/audit/*.ts:
|
|
9
|
+
// android-permission-*, android-component-*, android-deep-link-*,
|
|
10
|
+
// android-intent-filter-*) rather than inventing a second id scheme.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
export const ANDROID_NETWORK_SECURITY_RULE_IDS = [
|
|
13
|
+
"android-network-cleartext-traffic",
|
|
14
|
+
"android-network-security-config",
|
|
15
|
+
"android-network-user-added-trust-anchor",
|
|
16
|
+
"android-network-debug-trust-override",
|
|
17
|
+
"android-network-broad-domain-config",
|
|
18
|
+
"android-network-pinning-metadata",
|
|
19
|
+
"android-network-pinning-unresolved",
|
|
20
|
+
];
|
|
21
|
+
export const ANDROID_BACKUP_RELEASE_RULE_IDS = [
|
|
22
|
+
"android-backup-allow-backup",
|
|
23
|
+
"android-backup-full-backup-content",
|
|
24
|
+
"android-backup-data-extraction-rules",
|
|
25
|
+
"android-release-debuggable",
|
|
26
|
+
"android-release-test-only",
|
|
27
|
+
"android-release-build-type-configuration",
|
|
28
|
+
];
|
|
29
|
+
export const ANDROID_SECRETS_SIGNING_RULE_IDS = [
|
|
30
|
+
"android-secret-hardcoded-candidate",
|
|
31
|
+
"android-secret-private-key-candidate",
|
|
32
|
+
"android-secret-token-password-candidate",
|
|
33
|
+
"android-signing-password-literal",
|
|
34
|
+
"android-signing-keystore-candidate",
|
|
35
|
+
"android-signing-path-leakage",
|
|
36
|
+
"android-signing-debug-in-release",
|
|
37
|
+
];
|
|
38
|
+
export const ANDROID_WEBVIEW_RULE_IDS = [
|
|
39
|
+
"android-webview-javascript-enabled",
|
|
40
|
+
"android-webview-javascript-interface-exposure",
|
|
41
|
+
"android-webview-file-content-access",
|
|
42
|
+
"android-webview-universal-file-url-access",
|
|
43
|
+
"android-webview-mixed-content",
|
|
44
|
+
"android-webview-ssl-error-proceed",
|
|
45
|
+
"android-webview-debugging-enabled",
|
|
46
|
+
"android-webview-suspicious-url-loading",
|
|
47
|
+
];
|
|
48
|
+
export const ANDROID_FILE_PROVIDER_RULE_IDS = [
|
|
49
|
+
"android-file-provider-exported",
|
|
50
|
+
"android-file-provider-missing-protection",
|
|
51
|
+
"android-file-provider-broad-paths",
|
|
52
|
+
"android-file-provider-missing-paths-xml",
|
|
53
|
+
"android-file-provider-malformed-paths-xml",
|
|
54
|
+
"android-file-provider-unresolved-reference",
|
|
55
|
+
];
|
|
56
|
+
export const ANDROID_SENSITIVE_DATA_RULE_IDS = [
|
|
57
|
+
"android-sensitive-external-storage",
|
|
58
|
+
"android-sensitive-preferences",
|
|
59
|
+
"android-sensitive-unsafe-file-database-storage",
|
|
60
|
+
"android-sensitive-logging",
|
|
61
|
+
"android-sensitive-clipboard-write",
|
|
62
|
+
"android-sensitive-clipboard-read-review",
|
|
63
|
+
];
|
|
64
|
+
export const ANDROID_FIREBASE_GOOGLE_RULE_IDS = [
|
|
65
|
+
"android-firebase-realtime-database-permissive-rules",
|
|
66
|
+
"android-firebase-firestore-permissive-rules",
|
|
67
|
+
"android-firebase-storage-permissive-rules",
|
|
68
|
+
"android-firebase-missing-local-rules",
|
|
69
|
+
"android-firebase-google-configuration-evidence",
|
|
70
|
+
];
|
|
71
|
+
export const ANDROID_OPTIONAL_TOOL_RULE_IDS = [
|
|
72
|
+
"android-optional-tool-semgrep-evidence",
|
|
73
|
+
"android-optional-tool-osv-evidence",
|
|
74
|
+
"android-optional-tool-lint-evidence",
|
|
75
|
+
"android-optional-tool-dependency-check-evidence",
|
|
76
|
+
];
|
|
77
|
+
// All planned v0.4.1 advanced Android rule ids in one deterministic,
|
|
78
|
+
// duplicate-free list. Uniqueness across every family is verified by a
|
|
79
|
+
// regression test (ANDROID-V041-B1-01) rather than assumed.
|
|
80
|
+
export const ANDROID_ADVANCED_RULE_IDS = [
|
|
81
|
+
...ANDROID_NETWORK_SECURITY_RULE_IDS,
|
|
82
|
+
...ANDROID_BACKUP_RELEASE_RULE_IDS,
|
|
83
|
+
...ANDROID_SECRETS_SIGNING_RULE_IDS,
|
|
84
|
+
...ANDROID_WEBVIEW_RULE_IDS,
|
|
85
|
+
...ANDROID_FILE_PROVIDER_RULE_IDS,
|
|
86
|
+
...ANDROID_SENSITIVE_DATA_RULE_IDS,
|
|
87
|
+
...ANDROID_FIREBASE_GOOGLE_RULE_IDS,
|
|
88
|
+
...ANDROID_OPTIONAL_TOOL_RULE_IDS,
|
|
89
|
+
];
|
|
90
|
+
// New v0.4.1 check categories, additive to ANDROID_CHECK_CATEGORIES in
|
|
91
|
+
// src/mobile/android/validation/checkResult.ts. Kept here (rather than only
|
|
92
|
+
// inline in that file) so rule ids and their owning category live next to
|
|
93
|
+
// each other; checkResult.ts re-exports/spreads this list additively.
|
|
94
|
+
export const ANDROID_ADVANCED_CHECK_CATEGORIES = [
|
|
95
|
+
"android-network-security",
|
|
96
|
+
"android-backup-configuration",
|
|
97
|
+
"android-release-configuration",
|
|
98
|
+
"android-secret-candidates",
|
|
99
|
+
"android-signing-configuration",
|
|
100
|
+
"android-webview",
|
|
101
|
+
"android-file-provider",
|
|
102
|
+
"android-sensitive-storage",
|
|
103
|
+
"android-sensitive-logging",
|
|
104
|
+
"android-clipboard",
|
|
105
|
+
"android-firebase-google-services",
|
|
106
|
+
"android-semgrep",
|
|
107
|
+
"android-osv",
|
|
108
|
+
"android-lint",
|
|
109
|
+
"android-dependency-check",
|
|
110
|
+
];
|
|
111
|
+
const RULE_ID_TO_CATEGORY = new Map([
|
|
112
|
+
...ANDROID_NETWORK_SECURITY_RULE_IDS.map((id) => [id, "android-network-security"]),
|
|
113
|
+
...ANDROID_BACKUP_RELEASE_RULE_IDS.map((id) => [id, id.startsWith("android-backup-") ? "android-backup-configuration" : "android-release-configuration"]),
|
|
114
|
+
...ANDROID_SECRETS_SIGNING_RULE_IDS.map((id) => [id, id.startsWith("android-secret-") ? "android-secret-candidates" : "android-signing-configuration"]),
|
|
115
|
+
...ANDROID_WEBVIEW_RULE_IDS.map((id) => [id, "android-webview"]),
|
|
116
|
+
...ANDROID_FILE_PROVIDER_RULE_IDS.map((id) => [id, "android-file-provider"]),
|
|
117
|
+
...ANDROID_SENSITIVE_DATA_RULE_IDS.map((id) => {
|
|
118
|
+
if (id === "android-sensitive-logging")
|
|
119
|
+
return [id, "android-sensitive-logging"];
|
|
120
|
+
if (id.startsWith("android-sensitive-clipboard"))
|
|
121
|
+
return [id, "android-clipboard"];
|
|
122
|
+
return [id, "android-sensitive-storage"];
|
|
123
|
+
}),
|
|
124
|
+
...ANDROID_FIREBASE_GOOGLE_RULE_IDS.map((id) => [id, "android-firebase-google-services"]),
|
|
125
|
+
...ANDROID_OPTIONAL_TOOL_RULE_IDS.map((id) => {
|
|
126
|
+
if (id === "android-optional-tool-semgrep-evidence")
|
|
127
|
+
return [id, "android-semgrep"];
|
|
128
|
+
if (id === "android-optional-tool-osv-evidence")
|
|
129
|
+
return [id, "android-osv"];
|
|
130
|
+
if (id === "android-optional-tool-lint-evidence")
|
|
131
|
+
return [id, "android-lint"];
|
|
132
|
+
return [id, "android-dependency-check"];
|
|
133
|
+
}),
|
|
134
|
+
]);
|
|
135
|
+
export function categoryForAdvancedRuleId(ruleId) {
|
|
136
|
+
const category = RULE_ID_TO_CATEGORY.get(ruleId);
|
|
137
|
+
if (category === undefined) {
|
|
138
|
+
throw new Error(`No category registered for advanced Android rule id: ${ruleId}`);
|
|
139
|
+
}
|
|
140
|
+
return category;
|
|
141
|
+
}
|