@dailephd/my-dev-kit-lab 0.4.0 → 0.4.1
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 +10 -61
- package/dist/scripts/security/validate.js +38 -5
- 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/README.md
CHANGED
|
@@ -1,78 +1,27 @@
|
|
|
1
1
|
# my-dev-kit-lab
|
|
2
2
|
|
|
3
|
-
my-dev-kit-lab is the experiment, evidence, reporting, visualization, and security-validation companion for my-dev-kit. It provides controlled
|
|
3
|
+
my-dev-kit-lab is the experiment, evidence, reporting, visualization, audit, and security-validation companion for my-dev-kit. It provides controlled retrieval experiments, deterministic demonstrations, reports, plots, galleries, generic audits, and local security-validation workflows.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Version state
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Controlled experiment plugins, reports, plots, screenshots, and gallery artifacts.
|
|
10
|
-
- Generic code-rot and general security audit types through `npm run audit`.
|
|
11
|
-
- Standalone CLI/package security validation through `npm run security:validate`.
|
|
12
|
-
- Android security validation through the canonical `android` profile on the v0.4.0 feature branch.
|
|
7
|
+
`v0.4.1` is the published baseline, with the advanced Android implementation released. `v0.4.0` is the previous published baseline. `v0.4.2` remains future work.
|
|
13
8
|
|
|
14
9
|
## Android validation
|
|
15
10
|
|
|
16
|
-
Run static Android validation with:
|
|
17
|
-
|
|
18
11
|
```powershell
|
|
19
12
|
npm run security:validate -- --target "<android-project-path>" --profile android
|
|
20
13
|
```
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Reports use the existing `reports/security/` root unless `--out` is supplied. Android report files use the `<prefix>-android-security-validation.txt` and `.json` convention.
|
|
25
|
-
|
|
26
|
-
Optional Gradle validation is explicit:
|
|
27
|
-
|
|
28
|
-
```powershell
|
|
29
|
-
npm run security:validate -- --target "<android-project-path>" --profile android --android-gradle-operations wrapper-version,tasks
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
The only allowed operation IDs are:
|
|
33
|
-
|
|
34
|
-
- `wrapper-version`
|
|
35
|
-
- `tasks`
|
|
36
|
-
- `assemble-debug`
|
|
37
|
-
- `unit-test-debug`
|
|
38
|
-
- `lint-debug`
|
|
15
|
+
The default Android profile runs nineteen checks: the original eight checks plus eleven advanced internal checks for Network Security Config, backup/data extraction, release configuration, secret candidates, signing configuration, WebView, FileProvider, sensitive storage, sensitive logging, clipboard, and Firebase/Google services. It runs zero Gradle operations, zero external tools, and zero network operations by default.
|
|
39
16
|
|
|
40
|
-
|
|
17
|
+
`SecurityFinding` records confirmed conservative evidence. `CandidateEvidence` remains separate review or unresolved evidence and is not a confirmed vulnerability. Text and JSON reports under `reports/security/` include advanced-security checks, external-tool results, candidate-evidence summaries, artifacts, mutation evidence, verdict reasons, and limitations.
|
|
41
18
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- `not-ready-security-blocker-remains`: a blocker remains.
|
|
45
|
-
- `inconclusive-audit-environment-incomplete`: required evidence/environment is incomplete.
|
|
46
|
-
- `ready-except-optional-manual-checks`: advisory or manual follow-up remains.
|
|
47
|
-
- `ready-for-release-preparation`: required static evidence passed.
|
|
48
|
-
|
|
49
|
-
These are validation-evidence states, not claims that an app is published, Play-ready, Play-compliant, runtime-secure, or signing-valid.
|
|
50
|
-
|
|
51
|
-
### Important Android limitations
|
|
52
|
-
|
|
53
|
-
- Static analysis does not prove runtime behavior.
|
|
54
|
-
- Manifests are not merged; placeholders and resource references are not resolved.
|
|
55
|
-
- Gradle scripts are not evaluated; dynamic metadata can stay unresolved; same-file duplicate assignment extraction uses the first match.
|
|
56
|
-
- Optional Gradle execution is disabled by default and environment-dependent.
|
|
57
|
-
- APK/AAB contents, signing, emulator/device behavior, Digital Asset Links, domain ownership, and live Google Play policy are not validated.
|
|
58
|
-
- Play Console items such as privacy policy, Data Safety, permissions declarations, store listing, release notes, content rating, and policy review remain manual placeholders.
|
|
59
|
-
|
|
60
|
-
## Development
|
|
61
|
-
|
|
62
|
-
```powershell
|
|
63
|
-
npm run typecheck
|
|
64
|
-
npm run build
|
|
65
|
-
npm run test
|
|
66
|
-
npm run verify
|
|
67
|
-
```
|
|
19
|
+
Optional Gradle operations use `--android-gradle-operations` and only accept `wrapper-version`, `tasks`, `assemble-debug`, `unit-test-debug`, and `lint-debug`. They are environment-dependent, record mutations, and never perform cleanup or arbitrary tasks.
|
|
68
20
|
|
|
69
|
-
|
|
21
|
+
Optional external tools use `--android-external-tools semgrep,osv,android-lint,dependency-check`. Network defaults to `--android-external-network deny`; `allow-requested` authorizes only requested supported network-capable tools. Semgrep uses package-owned local rules, Android Lint reuses the closed offline Gradle path, Dependency-Check disables updates, and OSV requires explicit network authorization. Tools are never installed automatically; unavailable requested tools are skipped or inconclusive, not clean passes.
|
|
70
22
|
|
|
71
|
-
|
|
23
|
+
Verdicts are `not-ready-security-blocker-remains`, `inconclusive-audit-environment-incomplete`, `ready-except-optional-manual-checks`, and `ready-for-release-preparation`. They are evidence states, not runtime, Play, publication, or pentest guarantees.
|
|
72
24
|
|
|
73
|
-
|
|
74
|
-
- `v0.4.1`: planned advanced Android security checks.
|
|
75
|
-
- `v0.4.2`: planned Android extension of the existing security audit adapter; it will not replace `security:validate`.
|
|
76
|
-
- Manual pentest: deferred post-v1 / version TBD; not part of v0.4.x.
|
|
25
|
+
Selected local references such as supported `@xml` resources are resolved conservatively. Manifests are not merged, full overlay precedence and Gradle evaluation are not implemented, whole-program flow is not performed, and APK/AAB, signing, emulator/device, remote Firebase, live Play policy, and manual pentest validation remain outside scope.
|
|
77
26
|
|
|
78
|
-
See [
|
|
27
|
+
See [Project overview](docs/PROJECT_OVERVIEW.md), [Commands](docs/COMMANDS.md), [Workflows](docs/WORKFLOWS.md), [Architecture](docs/ARCHITECTURE.md), and [Roadmap](docs/ROADMAP.md).
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { runSecurityValidation } from "../../src/securityValidation/validate/runSecurityValidation.js";
|
|
3
4
|
import { resolveValidationTarget, reportFilenamePrefix } from "../../src/securityValidation/validate/resolveTarget.js";
|
|
4
5
|
import { buildSecurityReportFromSummary } from "../../src/securityValidation/report/buildSecurityReport.js";
|
|
@@ -15,18 +16,35 @@ import { writeAndroidReportFiles } from "../../src/mobile/android/report/writeAn
|
|
|
15
16
|
const USAGE = "Usage: npm run security:validate -- [--target <path>] [--out <dir>] [--report-prefix <name>] " +
|
|
16
17
|
"[--checks deps,package,static,cli-adversarial,fuzz,boundary,subprocess,secrets,network] " +
|
|
17
18
|
"[--profile node-cli-package|local-tool|npm-package|android] [--format text,json] [--fail-on blocker|high|medium|low] " +
|
|
18
|
-
"[--android-gradle-operations wrapper-version,tasks,assemble-debug,unit-test-debug,lint-debug]
|
|
19
|
+
"[--android-gradle-operations wrapper-version,tasks,assemble-debug,unit-test-debug,lint-debug] " +
|
|
20
|
+
"[--android-external-tools semgrep,osv,android-lint,dependency-check] " +
|
|
21
|
+
"[--android-external-network deny|allow-requested]\n" +
|
|
19
22
|
"\n" +
|
|
20
23
|
"Android profile:\n" +
|
|
21
24
|
" --profile android performs static, read-only Android project detection, manifest parsing,\n" +
|
|
22
|
-
" permission/exported-component/intent-filter/deep-link audits,
|
|
23
|
-
" extraction.
|
|
25
|
+
" permission/exported-component/intent-filter/deep-link audits, static Gradle metadata\n" +
|
|
26
|
+
" extraction, and (as of v0.4.1 Batch 8) eleven internal advanced security checks (network\n" +
|
|
27
|
+
" security config, backup/release configuration, hardcoded secrets, signing configuration,\n" +
|
|
28
|
+
" WebView, FileProvider, sensitive storage/logging, clipboard, and Firebase/Google services).\n" +
|
|
29
|
+
" Gradle and external tools are never executed by default.\n" +
|
|
24
30
|
" --android-gradle-operations explicitly opts into running ONLY the listed allowlisted Gradle\n" +
|
|
25
31
|
" operations (wrapper-version, tasks, assemble-debug, unit-test-debug, lint-debug) via the\n" +
|
|
26
32
|
" project's own Gradle wrapper. Arbitrary Gradle tasks cannot be passed. This option is only\n" +
|
|
27
33
|
" valid together with --profile android.\n" +
|
|
34
|
+
" --android-external-tools explicitly opts into running ONLY the listed allowlisted optional\n" +
|
|
35
|
+
" external security tools (semgrep, osv, android-lint, dependency-check). No arbitrary tool\n" +
|
|
36
|
+
" names, commands, or arguments can be passed. This option is only valid together with\n" +
|
|
37
|
+
" --profile android.\n" +
|
|
38
|
+
" --android-external-network controls whether a requested tool may use network access.\n" +
|
|
39
|
+
" Defaults to deny (osv is skipped unless supported offline data is available; semgrep,\n" +
|
|
40
|
+
" android-lint, and dependency-check always run offline/no-update regardless of this flag).\n" +
|
|
41
|
+
" allow-requested authorizes network only for an explicitly requested tool whose Batch 7\n" +
|
|
42
|
+
" contract supports it (currently only osv). This option is only valid together with\n" +
|
|
43
|
+
" --profile android.\n" +
|
|
28
44
|
" Example: npm run security:validate -- --target \"<android-project-path>\" --profile android\n" +
|
|
29
|
-
" Example: npm run security:validate -- --target \"<android-project-path>\" --profile android --android-gradle-operations wrapper-version,tasks"
|
|
45
|
+
" Example: npm run security:validate -- --target \"<android-project-path>\" --profile android --android-gradle-operations wrapper-version,tasks\n" +
|
|
46
|
+
" Example: npm run security:validate -- --target \"<android-project-path>\" --profile android --android-external-tools semgrep,android-lint\n" +
|
|
47
|
+
" Example: npm run security:validate -- --target \"<android-project-path>\" --profile android --android-external-tools osv --android-external-network allow-requested";
|
|
30
48
|
// Parse CLI arguments from process.argv (after the node/tsx and script path).
|
|
31
49
|
const rawArgs = process.argv.slice(2);
|
|
32
50
|
const toolRoot = resolveToolRoot(import.meta.url);
|
|
@@ -99,13 +117,27 @@ if (config.profile === "android") {
|
|
|
99
117
|
else {
|
|
100
118
|
console.log(`Gradle ops : (none requested — static-only validation, zero Gradle process execution)`);
|
|
101
119
|
}
|
|
120
|
+
// v0.4.1 Batch 8 — external tools remain explicit opt-in, mirroring Gradle
|
|
121
|
+
// operations exactly. "allow-requested" (the CLI-facing value) maps to the
|
|
122
|
+
// Batch 7 dispatcher's "allow-for-requested-tool" network policy value.
|
|
123
|
+
const externalNetworkPolicy = config.androidExternalNetworkPolicy === "allow-requested" ? "allow-for-requested-tool" : "deny";
|
|
124
|
+
if (config.androidExternalToolIds.length > 0) {
|
|
125
|
+
console.log(`External : ${config.androidExternalToolIds.join(", ")} (explicitly requested — network policy: ${config.androidExternalNetworkPolicy})`);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.log(`External : (none requested — zero external-tool process execution)`);
|
|
129
|
+
}
|
|
102
130
|
console.log("");
|
|
131
|
+
const reportsDir = config.out;
|
|
103
132
|
let result;
|
|
104
133
|
try {
|
|
105
134
|
result = await validateAndroidTarget({
|
|
106
135
|
toolRoot,
|
|
107
136
|
targetPath: args.target,
|
|
108
137
|
requestedGradleOperationIds: config.androidGradleOperationIds,
|
|
138
|
+
requestedExternalToolIds: config.androidExternalToolIds,
|
|
139
|
+
externalNetworkPolicy: externalNetworkPolicy,
|
|
140
|
+
externalToolArtifactRoot: path.join(reportsDir, "external-tools"),
|
|
109
141
|
});
|
|
110
142
|
}
|
|
111
143
|
catch (err) {
|
|
@@ -117,9 +149,10 @@ if (config.profile === "android") {
|
|
|
117
149
|
const reportModel = toAndroidReportModel(result, {
|
|
118
150
|
profile: config.profile,
|
|
119
151
|
requestedGradleOperations: config.androidGradleOperationIds,
|
|
152
|
+
requestedExternalTools: config.androidExternalToolIds,
|
|
153
|
+
externalNetworkPolicy: config.androidExternalNetworkPolicy,
|
|
120
154
|
});
|
|
121
155
|
const textReport = renderAndroidTextReport(reportModel);
|
|
122
|
-
const reportsDir = config.out;
|
|
123
156
|
const prefix = args.reportPrefix ??
|
|
124
157
|
reportFilenamePrefix({
|
|
125
158
|
isSelf: result.target.local.isSelf,
|
package/dist/src/mobile/android/advancedSecurity/backupConfiguration/analyzeBackupConfiguration.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
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 { extractManifestBackupEvidence } from "./manifestEvidence.js";
|
|
6
|
+
function loc(targetRoot, absolutePath, position) {
|
|
7
|
+
return buildAndroidSourceLocation(targetRoot, absolutePath, position);
|
|
8
|
+
}
|
|
9
|
+
// A conservative, narrow "broad inclusion" signal: the entire root, or an
|
|
10
|
+
// entire sensitive domain with no path narrowing ("." — the whole domain).
|
|
11
|
+
function isBroadInclude(rule) {
|
|
12
|
+
if (rule.kind !== "include" || rule.malformed)
|
|
13
|
+
return false;
|
|
14
|
+
if (rule.domain === "root" || rule.domain === "device_root")
|
|
15
|
+
return true;
|
|
16
|
+
const sensitiveDomains = new Set(["sharedpref", "database", "external", "device_sharedpref", "device_database", "device_external"]);
|
|
17
|
+
return rule.domain !== undefined && sensitiveDomains.has(rule.domain) && rule.path === ".";
|
|
18
|
+
}
|
|
19
|
+
function analyzeRuleEntries(rules, ruleId, scopeLabel, sourceAbsolutePath, targetRoot, modulePath, sourceRelativePath) {
|
|
20
|
+
const candidates = [];
|
|
21
|
+
const evidenceText = [];
|
|
22
|
+
let hasBroadInclude = false;
|
|
23
|
+
for (const rule of rules) {
|
|
24
|
+
const location = loc(targetRoot, sourceAbsolutePath, { line: rule.location.line, column: rule.location.column });
|
|
25
|
+
if (rule.malformed) {
|
|
26
|
+
candidates.push(makeCandidateEvidence({
|
|
27
|
+
ruleId,
|
|
28
|
+
category: "android-backup-configuration",
|
|
29
|
+
confidence: "low",
|
|
30
|
+
modulePath,
|
|
31
|
+
location,
|
|
32
|
+
summary: `Malformed ${scopeLabel} rule: ${rule.malformedReason}`,
|
|
33
|
+
rawValue: `${rule.domainRaw ?? ""} ${rule.path ?? ""}`.trim(),
|
|
34
|
+
resolutionState: "malformed",
|
|
35
|
+
}));
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (rule.domain === undefined) {
|
|
39
|
+
candidates.push(makeCandidateEvidence({
|
|
40
|
+
ruleId,
|
|
41
|
+
category: "android-backup-configuration",
|
|
42
|
+
confidence: "low",
|
|
43
|
+
modulePath,
|
|
44
|
+
location,
|
|
45
|
+
summary: `Unsupported backup domain value in ${scopeLabel}: "${rule.domainRaw}"`,
|
|
46
|
+
rawValue: rule.domainRaw,
|
|
47
|
+
resolutionState: "unsupported",
|
|
48
|
+
}));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (rule.kind === "exclude") {
|
|
52
|
+
evidenceText.push(`${sourceRelativePath}: ${scopeLabel} restrictive exclusion — domain=${rule.domain}, path=${rule.path}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (isBroadInclude(rule)) {
|
|
56
|
+
hasBroadInclude = true;
|
|
57
|
+
candidates.push(makeCandidateEvidence({
|
|
58
|
+
ruleId,
|
|
59
|
+
category: "android-backup-configuration",
|
|
60
|
+
confidence: "medium",
|
|
61
|
+
modulePath,
|
|
62
|
+
location,
|
|
63
|
+
summary: `Broad ${scopeLabel} inclusion: domain=${rule.domain}, path=${rule.path}`,
|
|
64
|
+
rawValue: `${rule.domain}:${rule.path}`,
|
|
65
|
+
resolutionState: "resolved",
|
|
66
|
+
staticAnalysisLimitations: [
|
|
67
|
+
"Domain/path naming is conservative evidence only; it does not prove the included data is actually sensitive or that a backup is ever performed.",
|
|
68
|
+
],
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { candidates, evidenceText, hasBroadInclude };
|
|
73
|
+
}
|
|
74
|
+
function analyzeBackupRulesParseResult(parseResult, ruleId, sourceRelativePath, targetRoot, modulePath) {
|
|
75
|
+
const sourceAbsolutePath = path.join(targetRoot, sourceRelativePath);
|
|
76
|
+
if (parseResult.state === "malformed-xml") {
|
|
77
|
+
return {
|
|
78
|
+
candidates: [
|
|
79
|
+
makeCandidateEvidence({
|
|
80
|
+
ruleId,
|
|
81
|
+
category: "android-backup-configuration",
|
|
82
|
+
confidence: "medium",
|
|
83
|
+
modulePath,
|
|
84
|
+
location: loc(targetRoot, sourceAbsolutePath, parseResult.location ? { line: parseResult.location.line, column: parseResult.location.column } : undefined),
|
|
85
|
+
summary: `Referenced backup rules file is malformed XML: ${parseResult.reason}`,
|
|
86
|
+
rawValue: parseResult.reason,
|
|
87
|
+
resolutionState: "malformed",
|
|
88
|
+
}),
|
|
89
|
+
],
|
|
90
|
+
evidenceText: [],
|
|
91
|
+
hasBroadCloudOrLegacyInclude: false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (parseResult.state === "unsupported-root") {
|
|
95
|
+
return {
|
|
96
|
+
candidates: [
|
|
97
|
+
makeCandidateEvidence({
|
|
98
|
+
ruleId,
|
|
99
|
+
category: "android-backup-configuration",
|
|
100
|
+
confidence: "medium",
|
|
101
|
+
modulePath,
|
|
102
|
+
location: loc(targetRoot, sourceAbsolutePath),
|
|
103
|
+
summary: `Referenced XML file has an unsupported root element <${parseResult.rootTagName}>`,
|
|
104
|
+
rawValue: parseResult.rootTagName,
|
|
105
|
+
resolutionState: "unsupported",
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
108
|
+
evidenceText: [],
|
|
109
|
+
hasBroadCloudOrLegacyInclude: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (parseResult.state === "parsed-full-backup-content") {
|
|
113
|
+
const result = analyzeRuleEntries(parseResult.model.rules, ruleId, "full-backup-content", sourceAbsolutePath, targetRoot, modulePath, sourceRelativePath);
|
|
114
|
+
return { candidates: result.candidates, evidenceText: result.evidenceText, hasBroadCloudOrLegacyInclude: result.hasBroadInclude };
|
|
115
|
+
}
|
|
116
|
+
// parsed-data-extraction-rules
|
|
117
|
+
const cloudResult = parseResult.model.cloudBackup
|
|
118
|
+
? analyzeRuleEntries(parseResult.model.cloudBackup.rules, ruleId, "cloud-backup", sourceAbsolutePath, targetRoot, modulePath, sourceRelativePath)
|
|
119
|
+
: { candidates: [], evidenceText: [], hasBroadInclude: false };
|
|
120
|
+
const transferResult = parseResult.model.deviceTransfer
|
|
121
|
+
? analyzeRuleEntries(parseResult.model.deviceTransfer.rules, ruleId, "device-transfer", sourceAbsolutePath, targetRoot, modulePath, sourceRelativePath)
|
|
122
|
+
: { candidates: [], evidenceText: [], hasBroadInclude: false };
|
|
123
|
+
// Device-transfer broadness stays a review candidate only — it is local
|
|
124
|
+
// device-to-device transfer, not cloud exfiltration, so it never
|
|
125
|
+
// contributes to the allowBackup-level "major" escalation.
|
|
126
|
+
return {
|
|
127
|
+
candidates: [...cloudResult.candidates, ...transferResult.candidates],
|
|
128
|
+
evidenceText: [...cloudResult.evidenceText, ...transferResult.evidenceText],
|
|
129
|
+
hasBroadCloudOrLegacyInclude: cloudResult.hasBroadInclude,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function analyzeXmlReferenceEvidence(evidence, ruleId, targetRoot, manifestAbsolutePath, modulePath, applicationLocation) {
|
|
133
|
+
switch (evidence.state) {
|
|
134
|
+
case "absent":
|
|
135
|
+
return { candidates: [], evidenceText: [], hasBroadInclude: false };
|
|
136
|
+
case "resolved": {
|
|
137
|
+
const result = analyzeBackupRulesParseResult(evidence.parseResult, ruleId, evidence.candidate.relativePath, targetRoot, modulePath);
|
|
138
|
+
return { candidates: result.candidates, evidenceText: result.evidenceText, hasBroadInclude: result.hasBroadCloudOrLegacyInclude };
|
|
139
|
+
}
|
|
140
|
+
case "ambiguous": {
|
|
141
|
+
const candidates = [
|
|
142
|
+
makeCandidateEvidence({
|
|
143
|
+
ruleId,
|
|
144
|
+
category: "android-backup-configuration",
|
|
145
|
+
confidence: "low",
|
|
146
|
+
modulePath,
|
|
147
|
+
location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
|
|
148
|
+
summary: `Backup rules reference "${evidence.raw}" resolved to ${evidence.candidates.length} ambiguous candidates across source sets; no candidate was arbitrarily selected`,
|
|
149
|
+
rawValue: evidence.raw,
|
|
150
|
+
resolutionState: "unresolved",
|
|
151
|
+
}),
|
|
152
|
+
];
|
|
153
|
+
let hasBroadInclude = false;
|
|
154
|
+
for (const candidate of evidence.candidates) {
|
|
155
|
+
const result = analyzeBackupRulesParseResult(candidate.parseResult, ruleId, candidate.candidate.relativePath, targetRoot, modulePath);
|
|
156
|
+
candidates.push(...result.candidates);
|
|
157
|
+
hasBroadInclude = hasBroadInclude || result.hasBroadCloudOrLegacyInclude;
|
|
158
|
+
}
|
|
159
|
+
return { candidates, evidenceText: [], hasBroadInclude };
|
|
160
|
+
}
|
|
161
|
+
case "missing":
|
|
162
|
+
return {
|
|
163
|
+
candidates: [
|
|
164
|
+
makeCandidateEvidence({
|
|
165
|
+
ruleId,
|
|
166
|
+
category: "android-backup-configuration",
|
|
167
|
+
confidence: "low",
|
|
168
|
+
modulePath,
|
|
169
|
+
location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
|
|
170
|
+
summary: `Backup rules reference "${evidence.raw}" did not resolve to any file in the searched source sets`,
|
|
171
|
+
rawValue: evidence.raw,
|
|
172
|
+
resolutionState: "missing",
|
|
173
|
+
staticAnalysisLimitations: ["A missing referenced rules file is not automatically a vulnerability; runtime behavior cannot be proven statically."],
|
|
174
|
+
}),
|
|
175
|
+
],
|
|
176
|
+
evidenceText: [],
|
|
177
|
+
hasBroadInclude: false,
|
|
178
|
+
};
|
|
179
|
+
case "placeholder":
|
|
180
|
+
return {
|
|
181
|
+
candidates: [
|
|
182
|
+
makeCandidateEvidence({
|
|
183
|
+
ruleId,
|
|
184
|
+
category: "android-backup-configuration",
|
|
185
|
+
confidence: "low",
|
|
186
|
+
modulePath,
|
|
187
|
+
location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
|
|
188
|
+
summary: "Backup rules reference is an unresolved build-time placeholder",
|
|
189
|
+
rawValue: evidence.raw,
|
|
190
|
+
resolutionState: "unresolved",
|
|
191
|
+
}),
|
|
192
|
+
],
|
|
193
|
+
evidenceText: [],
|
|
194
|
+
hasBroadInclude: false,
|
|
195
|
+
};
|
|
196
|
+
case "malformed":
|
|
197
|
+
return {
|
|
198
|
+
candidates: [
|
|
199
|
+
makeCandidateEvidence({
|
|
200
|
+
ruleId,
|
|
201
|
+
category: "android-backup-configuration",
|
|
202
|
+
confidence: "low",
|
|
203
|
+
modulePath,
|
|
204
|
+
location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
|
|
205
|
+
summary: `Backup rules reference is malformed: ${evidence.reason}`,
|
|
206
|
+
rawValue: evidence.raw,
|
|
207
|
+
resolutionState: "malformed",
|
|
208
|
+
}),
|
|
209
|
+
],
|
|
210
|
+
evidenceText: [],
|
|
211
|
+
hasBroadInclude: false,
|
|
212
|
+
};
|
|
213
|
+
case "unsupported-type":
|
|
214
|
+
case "package-qualified":
|
|
215
|
+
case "module-unknown":
|
|
216
|
+
return {
|
|
217
|
+
candidates: [
|
|
218
|
+
makeCandidateEvidence({
|
|
219
|
+
ruleId,
|
|
220
|
+
category: "android-backup-configuration",
|
|
221
|
+
confidence: "low",
|
|
222
|
+
modulePath,
|
|
223
|
+
location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
|
|
224
|
+
summary: evidence.state === "module-unknown"
|
|
225
|
+
? "Backup rules reference could not be resolved because this manifest's module could not be determined"
|
|
226
|
+
: `Backup rules reference is not a resolvable target-contained @xml/... resource (${evidence.state})`,
|
|
227
|
+
rawValue: evidence.raw,
|
|
228
|
+
resolutionState: "unsupported",
|
|
229
|
+
}),
|
|
230
|
+
],
|
|
231
|
+
evidenceText: [],
|
|
232
|
+
hasBroadInclude: false,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Analyzes one already-parsed manifest's backup/data-extraction evidence.
|
|
237
|
+
// Standalone until a later integration batch.
|
|
238
|
+
export function analyzeManifestBackupConfiguration(targetRoot, entry) {
|
|
239
|
+
const evidence = extractManifestBackupEvidence(targetRoot, entry);
|
|
240
|
+
const candidates = [];
|
|
241
|
+
const findings = [];
|
|
242
|
+
const evidenceText = [];
|
|
243
|
+
const modulePath = entry.modulePath;
|
|
244
|
+
const manifestAbsolutePath = path.join(targetRoot, entry.manifestPath);
|
|
245
|
+
const applicationLocation = evidence.applicationLocation;
|
|
246
|
+
let hasBroadInclude = false;
|
|
247
|
+
// fullBackupContent — legacy literal states never touch the resource
|
|
248
|
+
// resolver/parser (there is no file to analyze).
|
|
249
|
+
const fullBackupContent = evidence.fullBackupContent;
|
|
250
|
+
if (fullBackupContent.state === "legacy-literal-true" || fullBackupContent.state === "legacy-literal-false") {
|
|
251
|
+
evidenceText.push(`${entry.manifestPath}: fullBackupContent is a legacy literal (${fullBackupContent.raw})`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const result = analyzeXmlReferenceEvidence(fullBackupContent, "android-backup-full-backup-content", targetRoot, manifestAbsolutePath, modulePath, applicationLocation);
|
|
255
|
+
candidates.push(...result.candidates);
|
|
256
|
+
evidenceText.push(...result.evidenceText);
|
|
257
|
+
hasBroadInclude = hasBroadInclude || result.hasBroadInclude;
|
|
258
|
+
}
|
|
259
|
+
const dataExtractionResult = analyzeXmlReferenceEvidence(evidence.dataExtractionRules, "android-backup-data-extraction-rules", targetRoot, manifestAbsolutePath, modulePath, applicationLocation);
|
|
260
|
+
candidates.push(...dataExtractionResult.candidates);
|
|
261
|
+
evidenceText.push(...dataExtractionResult.evidenceText);
|
|
262
|
+
hasBroadInclude = hasBroadInclude || dataExtractionResult.hasBroadInclude;
|
|
263
|
+
// allowBackup analysis — the only source of an actual SecurityFinding in
|
|
264
|
+
// this analyzer (agents.txt Batch 3 section 9.7/9.17).
|
|
265
|
+
if (evidence.allowBackup.state === "explicit-true") {
|
|
266
|
+
findings.push(makeAndroidFinding({
|
|
267
|
+
ruleId: "android-backup-allow-backup",
|
|
268
|
+
title: hasBroadInclude
|
|
269
|
+
? "Application allows backup with a broad backup-rule inclusion"
|
|
270
|
+
: "Application manifest explicitly permits backup (android:allowBackup=\"true\")",
|
|
271
|
+
severity: hasBroadInclude ? "major" : "minor",
|
|
272
|
+
confidence: hasBroadInclude ? "high" : "medium",
|
|
273
|
+
description: hasBroadInclude
|
|
274
|
+
? "The manifest sets android:allowBackup=\"true\" and the resolved backup rules include a broad root/domain inclusion with no narrowing. This is high-confidence static evidence of a broad backup surface; it does not prove any specific file is actually backed up or that backup ever runs."
|
|
275
|
+
: "The manifest sets android:allowBackup=\"true\" with no broad-rule evidence found. This alone is not a vulnerability — it is the platform's own default behavior when the attribute is unset on old targetSdk versions, and is a common, legitimate configuration.",
|
|
276
|
+
manifestPath: entry.manifestPath,
|
|
277
|
+
location: applicationLocation,
|
|
278
|
+
evidenceDetails: [`source=manifest-attribute`, `broadRuleEvidence=${hasBroadInclude}`],
|
|
279
|
+
recommendation: hasBroadInclude
|
|
280
|
+
? "Narrow the backup rules to exclude sensitive domains, or set android:allowBackup=\"false\" if backup is not required."
|
|
281
|
+
: "Review whether backup should remain enabled and whether backup rules narrow the included data appropriately.",
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
else if (evidence.allowBackup.state === "malformed") {
|
|
285
|
+
candidates.push(makeCandidateEvidence({
|
|
286
|
+
ruleId: "android-backup-allow-backup",
|
|
287
|
+
category: "android-backup-configuration",
|
|
288
|
+
confidence: "low",
|
|
289
|
+
modulePath,
|
|
290
|
+
location: loc(targetRoot, manifestAbsolutePath, applicationLocation),
|
|
291
|
+
summary: "android:allowBackup has an unresolved or malformed value",
|
|
292
|
+
rawValue: evidence.allowBackup.raw,
|
|
293
|
+
resolutionState: "malformed",
|
|
294
|
+
}));
|
|
295
|
+
}
|
|
296
|
+
else if (evidence.allowBackup.state === "explicit-false") {
|
|
297
|
+
evidenceText.push(`${entry.manifestPath}: allowBackup is explicitly false (restrictive)`);
|
|
298
|
+
}
|
|
299
|
+
return { evidence, candidates, findings, evidenceText };
|
|
300
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { buildAndroidManifestCheckResult } from "../../audit/checkResultBuilder.js";
|
|
2
|
+
import { sortCandidateEvidence } from "../ordering.js";
|
|
3
|
+
import { analyzeManifestBackupConfiguration } from "./analyzeBackupConfiguration.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// v0.4.1 Batch 3 — standalone Android backup/data-extraction check result.
|
|
6
|
+
//
|
|
7
|
+
// Reuses the existing manifest-audit status-decision policy
|
|
8
|
+
// (buildAndroidManifestCheckResult), mirroring Batch 2's checkResult.ts.
|
|
9
|
+
// Standalone: not called from validateAndroidTarget / any active
|
|
10
|
+
// orchestration, not rendered in reports, no CLI effect. Batch 8 integrates.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
export const ANDROID_BACKUP_CONFIGURATION_AUDIT_CHECK_ID = "android-backup-configuration-audit";
|
|
13
|
+
export function auditAndroidBackupConfiguration(targetRoot, detection, manifests) {
|
|
14
|
+
const findings = [];
|
|
15
|
+
const candidateEvidence = [];
|
|
16
|
+
const evidence = [];
|
|
17
|
+
const warnings = [];
|
|
18
|
+
for (const entry of manifests) {
|
|
19
|
+
const result = analyzeManifestBackupConfiguration(targetRoot, entry);
|
|
20
|
+
findings.push(...result.findings);
|
|
21
|
+
candidateEvidence.push(...result.candidates);
|
|
22
|
+
evidence.push(`${entry.manifestPath}: allowBackup=${result.evidence.allowBackup.state}, fullBackupContent=${result.evidence.fullBackupContent.state}, dataExtractionRules=${result.evidence.dataExtractionRules.state}`, ...result.evidenceText);
|
|
23
|
+
}
|
|
24
|
+
const base = buildAndroidManifestCheckResult({
|
|
25
|
+
id: ANDROID_BACKUP_CONFIGURATION_AUDIT_CHECK_ID,
|
|
26
|
+
category: "android-backup-configuration",
|
|
27
|
+
title: "Android backup and data-extraction configuration audit",
|
|
28
|
+
detection,
|
|
29
|
+
manifests,
|
|
30
|
+
findings,
|
|
31
|
+
evidence,
|
|
32
|
+
warnings: [
|
|
33
|
+
...warnings,
|
|
34
|
+
"Static analysis only: does not evaluate Gradle manifest merging, Android resource overlays, or whether a backup actually occurs at runtime.",
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
...base,
|
|
39
|
+
candidateEvidence: sortCandidateEvidence(candidateEvidence),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 3 — public exports for the standalone Android backup/data-
|
|
3
|
+
// extraction audit. Mirrors Batch 2's networkSecurity/index.ts convention.
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
export { parseBackupRules } from "./parseBackupRules.js";
|
|
6
|
+
export { extractManifestBackupEvidence } from "./manifestEvidence.js";
|
|
7
|
+
export { analyzeManifestBackupConfiguration } from "./analyzeBackupConfiguration.js";
|
|
8
|
+
export { auditAndroidBackupConfiguration, ANDROID_BACKUP_CONFIGURATION_AUDIT_CHECK_ID } from "./checkResult.js";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseAndroidResourceReference } from "../resourceReference.js";
|
|
4
|
+
import { resolveAndroidXmlResourceReference } from "../resourceResolution.js";
|
|
5
|
+
import { parseBackupRules } from "./parseBackupRules.js";
|
|
6
|
+
function classifyBooleanAttribute(value, raw) {
|
|
7
|
+
if (value === true)
|
|
8
|
+
return { state: "explicit-true", raw: raw ?? "true" };
|
|
9
|
+
if (value === false)
|
|
10
|
+
return { state: "explicit-false", raw: raw ?? "false" };
|
|
11
|
+
if (raw !== undefined)
|
|
12
|
+
return { state: "malformed", raw };
|
|
13
|
+
return { state: "missing" };
|
|
14
|
+
}
|
|
15
|
+
function parseBackupRulesFile(absolutePath) {
|
|
16
|
+
let xmlText;
|
|
17
|
+
try {
|
|
18
|
+
xmlText = readFileSync(absolutePath, "utf8");
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return { state: "malformed-xml", reason: error instanceof Error ? error.message : "Unable to read the resolved backup rules file" };
|
|
22
|
+
}
|
|
23
|
+
return parseBackupRules(xmlText);
|
|
24
|
+
}
|
|
25
|
+
function classifyResolution(raw, resolution, targetRoot) {
|
|
26
|
+
switch (resolution.state) {
|
|
27
|
+
case "resolved": {
|
|
28
|
+
const candidate = resolution.candidates[0];
|
|
29
|
+
return {
|
|
30
|
+
state: "resolved",
|
|
31
|
+
raw,
|
|
32
|
+
modulePath: resolution.modulePath,
|
|
33
|
+
candidate,
|
|
34
|
+
parseResult: parseBackupRulesFile(path.join(targetRoot, candidate.relativePath)),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
case "ambiguous":
|
|
38
|
+
return {
|
|
39
|
+
state: "ambiguous",
|
|
40
|
+
raw,
|
|
41
|
+
modulePath: resolution.modulePath,
|
|
42
|
+
candidates: resolution.candidates.map((candidate) => ({
|
|
43
|
+
candidate,
|
|
44
|
+
parseResult: parseBackupRulesFile(path.join(targetRoot, candidate.relativePath)),
|
|
45
|
+
})),
|
|
46
|
+
};
|
|
47
|
+
case "missing":
|
|
48
|
+
return { state: "missing", raw, modulePath: resolution.modulePath, searchedSourceSets: resolution.searchedSourceSets };
|
|
49
|
+
case "malformed-reference":
|
|
50
|
+
return { state: "malformed", raw, reason: resolution.reason };
|
|
51
|
+
case "unsupported-reference":
|
|
52
|
+
// Unreachable in practice — see Batch 2's manifestEvidence.ts for the
|
|
53
|
+
// identical rationale (this function only calls the resolver once a
|
|
54
|
+
// "parsed" xml-type reference is confirmed).
|
|
55
|
+
return { state: "unsupported-type", raw, resourceType: "type" in resolution.parsed ? resolution.parsed.type : "unknown" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function classifyXmlReference(targetRoot, entry, raw) {
|
|
59
|
+
if (raw === undefined)
|
|
60
|
+
return { state: "absent" };
|
|
61
|
+
const parsedRef = parseAndroidResourceReference(raw);
|
|
62
|
+
if (parsedRef.state === "placeholder")
|
|
63
|
+
return { state: "placeholder", raw };
|
|
64
|
+
if (parsedRef.state === "malformed")
|
|
65
|
+
return { state: "malformed", raw, reason: parsedRef.reason };
|
|
66
|
+
if (parsedRef.state === "empty")
|
|
67
|
+
return { state: "malformed", raw, reason: "Reference is empty" };
|
|
68
|
+
if (parsedRef.state === "unsupported-type")
|
|
69
|
+
return { state: "unsupported-type", raw, resourceType: parsedRef.type };
|
|
70
|
+
if (parsedRef.state === "package-qualified")
|
|
71
|
+
return { state: "package-qualified", raw, packageQualifier: parsedRef.packageQualifier };
|
|
72
|
+
if (entry.modulePath === undefined)
|
|
73
|
+
return { state: "module-unknown", raw };
|
|
74
|
+
const moduleAbsolutePath = path.join(targetRoot, entry.modulePath);
|
|
75
|
+
const resolution = resolveAndroidXmlResourceReference(targetRoot, moduleAbsolutePath, raw);
|
|
76
|
+
return classifyResolution(raw, resolution, targetRoot);
|
|
77
|
+
}
|
|
78
|
+
function classifyFullBackupContent(targetRoot, entry) {
|
|
79
|
+
const raw = entry.manifest.application.fullBackupContentRef;
|
|
80
|
+
if (raw === "true")
|
|
81
|
+
return { state: "legacy-literal-true", raw };
|
|
82
|
+
if (raw === "false")
|
|
83
|
+
return { state: "legacy-literal-false", raw };
|
|
84
|
+
return classifyXmlReference(targetRoot, entry, raw);
|
|
85
|
+
}
|
|
86
|
+
// Extracts and classifies backup/data-extraction/debuggable/testOnly
|
|
87
|
+
// evidence for one already-parsed manifest. `targetRoot` must be the
|
|
88
|
+
// absolute Android target root.
|
|
89
|
+
export function extractManifestBackupEvidence(targetRoot, entry) {
|
|
90
|
+
const app = entry.manifest.application;
|
|
91
|
+
return {
|
|
92
|
+
manifestPath: entry.manifestPath,
|
|
93
|
+
modulePath: entry.modulePath,
|
|
94
|
+
sourceSetKind: entry.sourceSetKind,
|
|
95
|
+
applicationLocation: app.location,
|
|
96
|
+
allowBackup: classifyBooleanAttribute(app.allowBackup, app.allowBackupRaw),
|
|
97
|
+
debuggable: classifyBooleanAttribute(app.debuggable, app.debuggableRaw),
|
|
98
|
+
testOnly: classifyBooleanAttribute(app.testOnly, app.testOnlyRaw),
|
|
99
|
+
fullBackupContent: classifyFullBackupContent(targetRoot, entry),
|
|
100
|
+
dataExtractionRules: classifyXmlReference(targetRoot, entry, app.dataExtractionRulesRef),
|
|
101
|
+
};
|
|
102
|
+
}
|