@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.
Files changed (147) hide show
  1. package/README.md +10 -61
  2. package/dist/scripts/security/validate.js +38 -5
  3. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/analyzeBackupConfiguration.js +300 -0
  4. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/checkResult.js +41 -0
  5. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/index.js +8 -0
  6. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/manifestEvidence.js +102 -0
  7. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/parseBackupRules.js +110 -0
  8. package/dist/src/mobile/android/advancedSecurity/backupConfiguration/types.js +27 -0
  9. package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +50 -0
  10. package/dist/src/mobile/android/advancedSecurity/clipboard/analyzeClipboardSecurity.js +166 -0
  11. package/dist/src/mobile/android/advancedSecurity/clipboard/checkResult.js +74 -0
  12. package/dist/src/mobile/android/advancedSecurity/clipboard/collectClipboardEvidence.js +82 -0
  13. package/dist/src/mobile/android/advancedSecurity/clipboard/index.js +1 -0
  14. package/dist/src/mobile/android/advancedSecurity/clipboard/types.js +1 -0
  15. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/checkResult.js +255 -0
  16. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/index.js +1 -0
  17. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintSarif.js +37 -0
  18. package/dist/src/mobile/android/advancedSecurity/externalTools/androidLint/parseLintXml.js +36 -0
  19. package/dist/src/mobile/android/advancedSecurity/externalTools/artifacts.js +95 -0
  20. package/dist/src/mobile/android/advancedSecurity/externalTools/boundedOutput.js +33 -0
  21. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/checkResult.js +173 -0
  22. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/command.js +33 -0
  23. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/index.js +1 -0
  24. package/dist/src/mobile/android/advancedSecurity/externalTools/dependencyCheck/parseDependencyCheckJson.js +133 -0
  25. package/dist/src/mobile/android/advancedSecurity/externalTools/discoverExecutable.js +13 -0
  26. package/dist/src/mobile/android/advancedSecurity/externalTools/index.js +8 -0
  27. package/dist/src/mobile/android/advancedSecurity/externalTools/minimalEnvironment.js +26 -0
  28. package/dist/src/mobile/android/advancedSecurity/externalTools/mutation.js +77 -0
  29. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/checkResult.js +161 -0
  30. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/command.js +25 -0
  31. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/index.js +1 -0
  32. package/dist/src/mobile/android/advancedSecurity/externalTools/osv/parseOsvJson.js +136 -0
  33. package/dist/src/mobile/android/advancedSecurity/externalTools/runBoundedExternalTool.js +51 -0
  34. package/dist/src/mobile/android/advancedSecurity/externalTools/runRequestedAndroidExternalTools.js +54 -0
  35. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/checkResult.js +170 -0
  36. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/command.js +28 -0
  37. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/index.js +2 -0
  38. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/parseSemgrepJson.js +141 -0
  39. package/dist/src/mobile/android/advancedSecurity/externalTools/semgrep/rules.js +105 -0
  40. package/dist/src/mobile/android/advancedSecurity/externalTools/types.js +107 -0
  41. package/dist/src/mobile/android/advancedSecurity/fileProvider/checkResult.js +188 -0
  42. package/dist/src/mobile/android/advancedSecurity/fileProvider/index.js +1 -0
  43. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/analyzeFirebaseGoogleServices.js +197 -0
  44. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/checkResult.js +77 -0
  45. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/collectFirebaseServiceEvidence.js +193 -0
  46. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/discoverFirebaseArtifacts.js +128 -0
  47. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/index.js +1 -0
  48. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseDatabaseRules.js +55 -0
  49. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirebaseConfig.js +75 -0
  50. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseFirestoreStorageRules.js +88 -0
  51. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/parseGoogleServicesConfig.js +55 -0
  52. package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/types.js +6 -0
  53. package/dist/src/mobile/android/advancedSecurity/networkSecurity/analyzeNetworkSecurity.js +394 -0
  54. package/dist/src/mobile/android/advancedSecurity/networkSecurity/checkResult.js +47 -0
  55. package/dist/src/mobile/android/advancedSecurity/networkSecurity/deriveEffectiveNetworkPolicy.js +52 -0
  56. package/dist/src/mobile/android/advancedSecurity/networkSecurity/index.js +14 -0
  57. package/dist/src/mobile/android/advancedSecurity/networkSecurity/manifestEvidence.js +109 -0
  58. package/dist/src/mobile/android/advancedSecurity/networkSecurity/parseNetworkSecurityConfig.js +186 -0
  59. package/dist/src/mobile/android/advancedSecurity/networkSecurity/types.js +1 -0
  60. package/dist/src/mobile/android/advancedSecurity/ordering.js +44 -0
  61. package/dist/src/mobile/android/advancedSecurity/redaction.js +54 -0
  62. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/analyzeReleaseConfiguration.js +221 -0
  63. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/checkResult.js +39 -0
  64. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/index.js +7 -0
  65. package/dist/src/mobile/android/advancedSecurity/releaseConfiguration/manifestEvidence.js +19 -0
  66. package/dist/src/mobile/android/advancedSecurity/resourceReference.js +69 -0
  67. package/dist/src/mobile/android/advancedSecurity/resourceResolution.js +118 -0
  68. package/dist/src/mobile/android/advancedSecurity/ruleIds.js +141 -0
  69. package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +116 -0
  70. package/dist/src/mobile/android/advancedSecurity/secretCandidates/checkResult.js +80 -0
  71. package/dist/src/mobile/android/advancedSecurity/secretCandidates/discoverSecretSourceFiles.js +152 -0
  72. package/dist/src/mobile/android/advancedSecurity/secretCandidates/index.js +9 -0
  73. package/dist/src/mobile/android/advancedSecurity/secretCandidates/matchSecretCandidates.js +136 -0
  74. package/dist/src/mobile/android/advancedSecurity/secretCandidates/suppressValue.js +61 -0
  75. package/dist/src/mobile/android/advancedSecurity/secretCandidates/types.js +11 -0
  76. package/dist/src/mobile/android/advancedSecurity/sensitiveCategories.js +34 -0
  77. package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifyDirectExpression.js +99 -0
  78. package/dist/src/mobile/android/advancedSecurity/sensitiveData/classifySensitiveIdentifier.js +111 -0
  79. package/dist/src/mobile/android/advancedSecurity/sensitiveData/index.js +3 -0
  80. package/dist/src/mobile/android/advancedSecurity/sensitiveData/localSourceContext.js +90 -0
  81. package/dist/src/mobile/android/advancedSecurity/sensitiveData/splitArguments.js +59 -0
  82. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/analyzeSensitiveLogging.js +117 -0
  83. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/checkResult.js +74 -0
  84. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/collectLoggingEvidence.js +87 -0
  85. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/index.js +1 -0
  86. package/dist/src/mobile/android/advancedSecurity/sensitiveLogging/types.js +1 -0
  87. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/analyzeSensitiveStorage.js +182 -0
  88. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/checkResult.js +74 -0
  89. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/collectStorageEvidence.js +109 -0
  90. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/index.js +1 -0
  91. package/dist/src/mobile/android/advancedSecurity/sensitiveStorage/types.js +4 -0
  92. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +270 -0
  93. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/checkResult.js +119 -0
  94. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/discoverKeystoreCandidates.js +55 -0
  95. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +81 -0
  96. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/index.js +8 -0
  97. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/types.js +20 -0
  98. package/dist/src/mobile/android/advancedSecurity/sourceLocation.js +15 -0
  99. package/dist/src/mobile/android/advancedSecurity/webview/checkResult.js +210 -0
  100. package/dist/src/mobile/android/advancedSecurity/webview/index.js +1 -0
  101. package/dist/src/mobile/android/gradle/moduleMetadataExtractor.js +123 -17
  102. package/dist/src/mobile/android/manifest/parseAndroidManifest.js +27 -4
  103. package/dist/src/mobile/android/manifest/xml/parseXml.js +25 -9
  104. package/dist/src/mobile/android/report/model.js +32 -1
  105. package/dist/src/mobile/android/report/renderAndroidReport.js +63 -0
  106. package/dist/src/mobile/android/validate/validateAndroidTarget.js +146 -3
  107. package/dist/src/mobile/android/validation/checkResult.js +7 -0
  108. package/dist/src/securityValidation/commandRunner.js +10 -1
  109. package/dist/src/securityValidation/validate/cliOptions.js +46 -0
  110. package/package.json +1 -1
  111. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/debug/res/xml/ambiguous_backup_rules.xml +4 -0
  112. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/ambiguous_backup_rules.xml +4 -0
  113. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/backup_rules.xml +5 -0
  114. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/broad_backup_rules.xml +4 -0
  115. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/data_extraction_rules.xml +10 -0
  116. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/unsupported_domain_rules.xml +4 -0
  117. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/malformed_backup_rules.xml +4 -0
  118. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/unsupported_root.xml +4 -0
  119. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/debug/res/xml/ambiguous_config.xml +4 -0
  120. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/ambiguous_config.xml +4 -0
  121. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/base_cleartext_false.xml +8 -0
  122. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/broad_and_duplicate_domains.xml +8 -0
  123. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/empty_domain.xml +6 -0
  124. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/malformed_pin.xml +11 -0
  125. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/network_security_config.xml +26 -0
  126. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/malformed_network_security_config.xml +6 -0
  127. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/unsupported_root.xml +4 -0
  128. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-dynamic.gradle +7 -0
  129. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-false.gradle +9 -0
  130. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-true.gradle +13 -0
  131. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-false.gradle.kts +7 -0
  132. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-true.gradle.kts +10 -0
  133. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/missing-release-build-type.gradle.kts +7 -0
  134. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/debug/res/xml/network_security_config.xml +8 -0
  135. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/backup_rules.xml +4 -0
  136. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/network_security_config.xml +8 -0
  137. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-b/src/main/res/xml/malformed_network_security_config.xml +7 -0
  138. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.java +8 -0
  139. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.kt +9 -0
  140. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle +11 -0
  141. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle.kts +11 -0
  142. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/google-services.json +16 -0
  143. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/local.properties +3 -0
  144. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/binary.properties +0 -0
  145. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/oversized.json +1 -0
  146. package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/app/fake-release-keystore.jks +1 -0
  147. 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 raw-full-file versus graph-guided retrieval experiments, deterministic fake-agent demonstrations, reports, plots, gallery artifacts, generic audits, and local security-validation workflows.
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
- The current published baseline is `v0.3.4`. The v0.4.0 Android validation implementation is release-prepared on `release/v0.4.0`; publication is pending. It is not published, and the npm registry latest version remains `0.3.4`.
5
+ ## Version state
6
6
 
7
- ## Current capabilities
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
- This default path executes **no Gradle commands**. It detects/classifies Android projects and UI evidence, parses discovered manifests independently, audits permissions/exported components/intent filters/deep links, extracts static Gradle metadata, assembles release metadata and Play-readiness placeholders, calculates an Android verdict, and writes bounded text and JSON reports.
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
- Unknown or empty lists, arbitrary tasks, and use with a non-Android profile are rejected. Optional operations can require Java, Android SDK components, wrapper distributions, and cached dependencies; environment gaps can produce skipped or inconclusive evidence.
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
- ### Verdicts
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
- Use `npm run security:validate -- --help` for command usage. Help does not run validation or write reports.
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
- ## Roadmap status
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
- - `v0.4.0`: Android validation MVP — release-prepared; publication pending.
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 [docs/PROJECT_OVERVIEW.md](docs/PROJECT_OVERVIEW.md), [docs/COMMANDS.md](docs/COMMANDS.md), [docs/WORKFLOWS.md](docs/WORKFLOWS.md), [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), and [docs/ROADMAP.md](docs/ROADMAP.md).
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]\n" +
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, and static Gradle metadata\n" +
23
- " extraction. Gradle is never executed by default.\n" +
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,
@@ -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
+ }