@dailephd/my-dev-kit-lab 0.3.4 → 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 +12 -287
- package/dist/scripts/security/validate.js +210 -70
- package/dist/src/audits/codeRot/detectors/architectureDriftDetector.js +5 -2
- package/dist/src/audits/codeRot/detectors/securityValidationAssumptionRotDetector.js +5 -2
- 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/audit/androidFinding.js +68 -0
- package/dist/src/mobile/android/audit/checkResultBuilder.js +97 -0
- package/dist/src/mobile/android/audit/deepLinkAudit.js +183 -0
- package/dist/src/mobile/android/audit/exportedComponentAudit.js +183 -0
- package/dist/src/mobile/android/audit/intentFilterAudit.js +128 -0
- package/dist/src/mobile/android/audit/permissionAudit.js +142 -0
- package/dist/src/mobile/android/detect/androidTargetDetection.js +18 -0
- package/dist/src/mobile/android/detect/buildFileEvidence.js +36 -0
- package/dist/src/mobile/android/detect/classify.js +150 -0
- package/dist/src/mobile/android/detect/detectAndroidProject.js +252 -0
- package/dist/src/mobile/android/detect/settingsModules.js +60 -0
- package/dist/src/mobile/android/detect/sourceEvidence.js +92 -0
- package/dist/src/mobile/android/detect/traversal.js +79 -0
- package/dist/src/mobile/android/detect/versionCatalogEvidence.js +37 -0
- package/dist/src/mobile/android/detection.js +28 -0
- package/dist/src/mobile/android/gradle/gradleMetadataCheckResult.js +120 -0
- package/dist/src/mobile/android/gradle/moduleMetadataExtractor.js +243 -0
- package/dist/src/mobile/android/gradle/pluginVersionExtractor.js +40 -0
- package/dist/src/mobile/android/gradle/readAndroidGradleMetadata.js +210 -0
- package/dist/src/mobile/android/gradle/releaseMetadataSummary.js +64 -0
- package/dist/src/mobile/android/gradle/settingsMetadata.js +20 -0
- package/dist/src/mobile/android/gradle/types.js +1 -0
- package/dist/src/mobile/android/gradle/validate/executor.js +9 -0
- package/dist/src/mobile/android/gradle/validate/operationCheckResult.js +99 -0
- package/dist/src/mobile/android/gradle/validate/operations.js +68 -0
- package/dist/src/mobile/android/gradle/validate/planner.js +49 -0
- package/dist/src/mobile/android/gradle/validate/runOptionalGradleValidation.js +113 -0
- package/dist/src/mobile/android/gradle/validate/targetMutation.js +45 -0
- package/dist/src/mobile/android/gradle/validate/taskListParser.js +24 -0
- package/dist/src/mobile/android/gradle/versionCatalogMetadata.js +94 -0
- package/dist/src/mobile/android/gradle/wrapperMetadata.js +32 -0
- package/dist/src/mobile/android/manifest/parseAndroidManifest.js +364 -0
- package/dist/src/mobile/android/manifest/types.js +29 -0
- package/dist/src/mobile/android/manifest/xml/parseXml.js +329 -0
- package/dist/src/mobile/android/profile.js +29 -0
- package/dist/src/mobile/android/report/model.js +135 -0
- package/dist/src/mobile/android/report/renderAndroidReport.js +286 -0
- package/dist/src/mobile/android/report/writeAndroidReportFiles.js +23 -0
- package/dist/src/mobile/android/targetMetadata.js +27 -0
- package/dist/src/mobile/android/validate/androidVerdict.js +248 -0
- package/dist/src/mobile/android/validate/detectionAndImmutabilityCheckResults.js +177 -0
- package/dist/src/mobile/android/validate/playReadinessChecklist.js +145 -0
- package/dist/src/mobile/android/validate/validateAndroidTarget.js +315 -0
- package/dist/src/mobile/android/validation/checkResult.js +58 -0
- package/dist/src/mobile/android/validation/result.js +25 -0
- package/dist/src/mobile/index.js +42 -0
- package/dist/src/mobile/types.js +27 -0
- package/dist/src/securityValidation/attackScenarios/attackProfile.js +2 -0
- package/dist/src/securityValidation/attackScenarios/profiles/androidProfile.js +20 -0
- package/dist/src/securityValidation/commandRunner.js +10 -1
- package/dist/src/securityValidation/validate/cliOptions.js +92 -2
- package/package.json +2 -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/tests/fixtures/android/compose-app/app/build.gradle.kts +21 -0
- package/tests/fixtures/android/compose-app/app/src/main/AndroidManifest.xml +22 -0
- package/tests/fixtures/android/compose-app/app/src/main/java/com/example/composeapp/MainActivity.kt +11 -0
- package/tests/fixtures/android/compose-app/build.gradle.kts +4 -0
- package/tests/fixtures/android/compose-app/gradle/wrapper/gradle-wrapper.properties +4 -0
- package/tests/fixtures/android/compose-app/settings.gradle.kts +2 -0
- package/tests/fixtures/android/gradle-metadata-fixtures/catalog-only-plugin/build.gradle.kts +3 -0
- package/tests/fixtures/android/gradle-metadata-fixtures/catalog-only-plugin/gradle/libs.versions.toml +5 -0
- package/tests/fixtures/android/gradle-metadata-fixtures/comment-safety.gradle.kts +21 -0
- package/tests/fixtures/android/gradle-metadata-fixtures/dynamic-values.gradle.kts +17 -0
- package/tests/fixtures/android/gradle-metadata-fixtures/groovy-literal.gradle +23 -0
- package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-malformed.properties +4 -0
- package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-missing-url.properties +3 -0
- package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-with-checksum.properties +5 -0
- package/tests/fixtures/android/java-app/app/build.gradle +16 -0
- package/tests/fixtures/android/java-app/app/src/main/AndroidManifest.xml +19 -0
- package/tests/fixtures/android/java-app/app/src/main/java/com/example/javaapp/MainActivity.java +4 -0
- package/tests/fixtures/android/java-app/build.gradle +3 -0
- package/tests/fixtures/android/java-app/gradle/wrapper/gradle-wrapper.properties +4 -0
- package/tests/fixtures/android/java-app/settings.gradle +2 -0
- package/tests/fixtures/android/library/build.gradle.kts +3 -0
- package/tests/fixtures/android/library/gradle/wrapper/gradle-wrapper.properties +4 -0
- package/tests/fixtures/android/library/mylibrary/build.gradle.kts +13 -0
- package/tests/fixtures/android/library/mylibrary/src/main/AndroidManifest.xml +2 -0
- package/tests/fixtures/android/library/settings.gradle.kts +2 -0
- package/tests/fixtures/android/manifest-audit-fixtures/incomplete-intent-filter.xml +14 -0
- package/tests/fixtures/android/manifest-audit-fixtures/malformed-exported.xml +10 -0
- package/tests/fixtures/android/manifest-audit-fixtures/malformed.xml +5 -0
- package/tests/fixtures/android/manifest-audit-fixtures/missing-application.xml +7 -0
- package/tests/fixtures/android/manifest-audit-fixtures/missing-component-name.xml +11 -0
- package/tests/fixtures/android/manifest-audit-fixtures/permission-coverage.xml +29 -0
- package/tests/fixtures/android/manifest-audit-fixtures/risky-components.xml +62 -0
- package/tests/fixtures/android/manifest-audit-fixtures/risky-deep-links.xml +45 -0
- package/tests/fixtures/android/manifest-audit-fixtures/unresolved-placeholder.xml +14 -0
- package/tests/fixtures/android/manifest-audit-fixtures/unusual-namespace-prefix.xml +11 -0
- package/tests/fixtures/android/multi-module/app/build.gradle.kts +22 -0
- package/tests/fixtures/android/multi-module/app/src/androidTest/java/com/example/multimodule/app/ExampleInstrumentedTest.kt +3 -0
- package/tests/fixtures/android/multi-module/app/src/main/AndroidManifest.xml +11 -0
- package/tests/fixtures/android/multi-module/app/src/test/java/com/example/multimodule/app/ExampleUnitTest.kt +3 -0
- package/tests/fixtures/android/multi-module/build.gradle.kts +5 -0
- package/tests/fixtures/android/multi-module/core/build.gradle.kts +14 -0
- package/tests/fixtures/android/multi-module/core/src/main/AndroidManifest.xml +2 -0
- package/tests/fixtures/android/multi-module/feature-login/build.gradle.kts +18 -0
- package/tests/fixtures/android/multi-module/feature-login/src/main/AndroidManifest.xml +2 -0
- package/tests/fixtures/android/multi-module/gradle/libs.versions.toml +8 -0
- package/tests/fixtures/android/multi-module/gradle/wrapper/gradle-wrapper.properties +4 -0
- package/tests/fixtures/android/multi-module/settings.gradle.kts +4 -0
- package/tests/fixtures/android/non-android-gradle/build.gradle.kts +3 -0
- package/tests/fixtures/android/non-android-gradle/core/build.gradle.kts +3 -0
- package/tests/fixtures/android/non-android-gradle/core/src/main/java/com/example/core/Core.java +4 -0
- package/tests/fixtures/android/non-android-gradle/gradle/wrapper/gradle-wrapper.properties +4 -0
- package/tests/fixtures/android/non-android-gradle/settings.gradle.kts +2 -0
- package/tests/fixtures/android/partial/app/build.gradle.kts +7 -0
- package/tests/fixtures/android/partial/settings.gradle.kts +2 -0
- package/tests/fixtures/android/xml-view-app/app/build.gradle.kts +17 -0
- package/tests/fixtures/android/xml-view-app/app/src/main/AndroidManifest.xml +31 -0
- package/tests/fixtures/android/xml-view-app/app/src/main/kotlin/com/example/xmlviewapp/MainActivity.kt +7 -0
- package/tests/fixtures/android/xml-view-app/app/src/main/res/layout/activity_main.xml +4 -0
- package/tests/fixtures/android/xml-view-app/build.gradle.kts +4 -0
- package/tests/fixtures/android/xml-view-app/gradle/wrapper/gradle-wrapper.properties +4 -0
- package/tests/fixtures/android/xml-view-app/settings.gradle.kts +2 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { captureTargetSnapshot, diffTargetSnapshots } from "../../../../securityValidation/attackScenarios/targetSnapshot.js";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// v0.4.0 Batch 4 — target mutation evidence for optional Gradle execution
|
|
4
|
+
// (agents.txt Batch 4 section 7.24).
|
|
5
|
+
//
|
|
6
|
+
// Reuses the existing read-only git-status snapshot/diff helpers from
|
|
7
|
+
// src/securityValidation/attackScenarios/targetSnapshot.ts (Batch 1's
|
|
8
|
+
// "reuse, don't create a second target-status model" rule) and layers a
|
|
9
|
+
// narrow Android-specific classifier on top of the diff to separate expected
|
|
10
|
+
// generated build output from unexpected tracked-file changes.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
export { captureTargetSnapshot };
|
|
13
|
+
const ANDROID_GENERATED_PATH_PATTERNS = [/^\.gradle\//, /(^|\/)build\//];
|
|
14
|
+
export function isExpectedAndroidGeneratedPath(entryPath) {
|
|
15
|
+
const normalized = entryPath.replace(/\\/g, "/");
|
|
16
|
+
return ANDROID_GENERATED_PATH_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
17
|
+
}
|
|
18
|
+
// Compares a before/after snapshot pair, distinguishing generated build
|
|
19
|
+
// output (untracked/added paths under build/ or .gradle/) from anything
|
|
20
|
+
// else — a tracked-file modification, or a new file outside those
|
|
21
|
+
// directories, is always reported as unexpected regardless of its status
|
|
22
|
+
// code.
|
|
23
|
+
export function buildTargetMutationReport(before, after) {
|
|
24
|
+
const diff = diffTargetSnapshots(before, after);
|
|
25
|
+
if (!diff.comparable) {
|
|
26
|
+
return { comparable: false, reason: diff.reason, expectedGeneratedChanges: [], unexpectedChanges: [], preExistingChangeCount: 0 };
|
|
27
|
+
}
|
|
28
|
+
const expectedGeneratedChanges = [];
|
|
29
|
+
const unexpectedChanges = [];
|
|
30
|
+
for (const entry of diff.newEntries) {
|
|
31
|
+
const isUntrackedOrAdded = entry.code === "??" || entry.code.includes("A");
|
|
32
|
+
if (isUntrackedOrAdded && isExpectedAndroidGeneratedPath(entry.path)) {
|
|
33
|
+
expectedGeneratedChanges.push(entry.path);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
unexpectedChanges.push(entry.path);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
comparable: true,
|
|
41
|
+
expectedGeneratedChanges: expectedGeneratedChanges.sort((a, b) => a.localeCompare(b)),
|
|
42
|
+
unexpectedChanges: unexpectedChanges.sort((a, b) => a.localeCompare(b)),
|
|
43
|
+
preExistingChangeCount: diff.preExistingEntries.length,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.0 Batch 4 — bounded `gradlew tasks --all` output parsing
|
|
3
|
+
// (agents.txt Batch 4 section 7.20).
|
|
4
|
+
//
|
|
5
|
+
// Extracts task names conservatively: only lines shaped like
|
|
6
|
+
// `taskName - description text` are treated as task declarations. Task
|
|
7
|
+
// descriptions are never treated as identifiers, and this parser never
|
|
8
|
+
// treats its output as authoritative security evidence — it is only used to
|
|
9
|
+
// decide whether a later allowlisted operation should be attempted or
|
|
10
|
+
// skipped.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const TASK_LINE_PATTERN = /^([A-Za-z][\w:]*)\s+-\s+.+$/;
|
|
13
|
+
export function parseGradleTaskNames(tasksOutput) {
|
|
14
|
+
const names = new Set();
|
|
15
|
+
for (const line of tasksOutput.split(/\r?\n/)) {
|
|
16
|
+
const match = line.match(TASK_LINE_PATTERN);
|
|
17
|
+
if (match)
|
|
18
|
+
names.add(match[1]);
|
|
19
|
+
}
|
|
20
|
+
return names;
|
|
21
|
+
}
|
|
22
|
+
export function isGradleTaskAvailable(taskNames, taskName) {
|
|
23
|
+
return taskNames.has(taskName);
|
|
24
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.0 Batch 4 — bounded gradle/libs.versions.toml version resolution.
|
|
3
|
+
//
|
|
4
|
+
// Extends Batch 2's alias-name-only evidence (detect/versionCatalogEvidence.ts)
|
|
5
|
+
// with just enough section-aware parsing to resolve a plugin/library alias to
|
|
6
|
+
// its declared `id`/`module` and version literal (including one level of
|
|
7
|
+
// `version.ref` indirection into the `[versions]` table). This is still not
|
|
8
|
+
// a general TOML parser: bundles, rich-version constraints, and nested
|
|
9
|
+
// tables beyond [versions]/[plugins]/[libraries] are not resolved.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const SECTION_HEADER_PATTERN = /^\[(versions|plugins|libraries|bundles)\]\s*$/;
|
|
12
|
+
const SIMPLE_VERSION_PATTERN = /^([A-Za-z0-9_-]+)\s*=\s*"([^"]*)"\s*$/;
|
|
13
|
+
const TABLE_ENTRY_PATTERN = /^([A-Za-z0-9_-]+)\s*=\s*\{([^}]*)\}\s*$/;
|
|
14
|
+
function parseEntryBody(body) {
|
|
15
|
+
const entry = {};
|
|
16
|
+
const idMatch = body.match(/\bid\s*=\s*"([^"]*)"/);
|
|
17
|
+
if (idMatch)
|
|
18
|
+
entry.id = idMatch[1];
|
|
19
|
+
const moduleMatch = body.match(/\bmodule\s*=\s*"([^"]*)"/);
|
|
20
|
+
if (moduleMatch)
|
|
21
|
+
entry.module = moduleMatch[1];
|
|
22
|
+
const versionRefMatch = body.match(/\bversion\.ref\s*=\s*"([^"]*)"/);
|
|
23
|
+
if (versionRefMatch) {
|
|
24
|
+
entry.versionRef = versionRefMatch[1];
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const versionMatch = body.match(/\bversion\s*=\s*"([^"]*)"/);
|
|
28
|
+
if (versionMatch)
|
|
29
|
+
entry.version = versionMatch[1];
|
|
30
|
+
}
|
|
31
|
+
return entry;
|
|
32
|
+
}
|
|
33
|
+
// Parses gradle/libs.versions.toml text into [versions]/[plugins]/[libraries]
|
|
34
|
+
// maps. Unknown sections (e.g. [bundles]) and malformed lines are ignored
|
|
35
|
+
// rather than causing a parse failure.
|
|
36
|
+
export function parseVersionCatalog(text) {
|
|
37
|
+
const versions = {};
|
|
38
|
+
const plugins = {};
|
|
39
|
+
const libraries = {};
|
|
40
|
+
let currentSection;
|
|
41
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
42
|
+
const line = rawLine.trim();
|
|
43
|
+
if (!line || line.startsWith("#"))
|
|
44
|
+
continue;
|
|
45
|
+
const sectionMatch = line.match(SECTION_HEADER_PATTERN);
|
|
46
|
+
if (sectionMatch) {
|
|
47
|
+
currentSection = sectionMatch[1];
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!currentSection)
|
|
51
|
+
continue;
|
|
52
|
+
if (currentSection === "versions") {
|
|
53
|
+
const match = line.match(SIMPLE_VERSION_PATTERN);
|
|
54
|
+
if (match)
|
|
55
|
+
versions[match[1]] = match[2];
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (currentSection === "plugins" || currentSection === "libraries") {
|
|
59
|
+
const tableMatch = line.match(TABLE_ENTRY_PATTERN);
|
|
60
|
+
if (tableMatch) {
|
|
61
|
+
const [, alias, body] = tableMatch;
|
|
62
|
+
(currentSection === "plugins" ? plugins : libraries)[alias] = parseEntryBody(body);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const simpleMatch = line.match(SIMPLE_VERSION_PATTERN);
|
|
66
|
+
if (simpleMatch) {
|
|
67
|
+
// A bare `alias = "id:group:version"` shorthand form (used mainly
|
|
68
|
+
// for libraries) — preserved as the module string, unresolved
|
|
69
|
+
// further, since splitting it safely requires assumptions this
|
|
70
|
+
// batch does not make.
|
|
71
|
+
(currentSection === "plugins" ? plugins : libraries)[simpleMatch[1]] = { module: simpleMatch[2] };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { versions, plugins, libraries };
|
|
76
|
+
}
|
|
77
|
+
// Resolves a catalog entry's version literal, following one level of
|
|
78
|
+
// version.ref indirection into the [versions] table. Returns undefined
|
|
79
|
+
// (unresolved) rather than guessing when the reference is missing.
|
|
80
|
+
export function resolveCatalogVersion(catalog, entry) {
|
|
81
|
+
if (entry.version)
|
|
82
|
+
return entry.version;
|
|
83
|
+
if (entry.versionRef)
|
|
84
|
+
return catalog.versions[entry.versionRef];
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
// Converts a Gradle version-catalog accessor path (e.g. from
|
|
88
|
+
// `libs.plugins.android.application`, the `android.application` suffix) back
|
|
89
|
+
// to its kebab-case alias key convention (`android-application`), the
|
|
90
|
+
// inverse of the accessor-generation convention used elsewhere in this
|
|
91
|
+
// codebase (see detect/detectAndroidProject.ts's catalogAliasToAccessorSuffix).
|
|
92
|
+
export function accessorSuffixToCatalogAlias(accessorSuffix) {
|
|
93
|
+
return accessorSuffix.replace(/\./g, "-");
|
|
94
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const DISTRIBUTION_URL_PATTERN = /^distributionUrl\s*=\s*(.+)$/m;
|
|
2
|
+
const CHECKSUM_PROPERTY_PATTERN = /^distributionSha256Sum\s*=\s*(.+)$/m;
|
|
3
|
+
// Gradle distribution URLs look like:
|
|
4
|
+
// https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
|
5
|
+
// (the colon is escaped because .properties files use `\:` for literal
|
|
6
|
+
// colons in values). Captures the version and bin/all distribution type.
|
|
7
|
+
const GRADLE_VERSION_PATTERN = /gradle-(\d+(?:\.\d+){1,3}(?:-\w+)?)-(bin|all)\.zip/;
|
|
8
|
+
function unescapePropertiesValue(raw) {
|
|
9
|
+
return raw.trim().replace(/\\:/g, ":").replace(/\\\//g, "/");
|
|
10
|
+
}
|
|
11
|
+
export function parseWrapperProperties(text) {
|
|
12
|
+
const warnings = [];
|
|
13
|
+
const urlMatch = text.match(DISTRIBUTION_URL_PATTERN);
|
|
14
|
+
const distributionUrl = urlMatch ? unescapePropertiesValue(urlMatch[1]) : undefined;
|
|
15
|
+
const checksumPropertyPresent = CHECKSUM_PROPERTY_PATTERN.test(text);
|
|
16
|
+
if (!distributionUrl) {
|
|
17
|
+
warnings.push("gradle-wrapper.properties has no distributionUrl property.");
|
|
18
|
+
return { distributionType: "unknown", checksumPropertyPresent, warnings };
|
|
19
|
+
}
|
|
20
|
+
const versionMatch = distributionUrl.match(GRADLE_VERSION_PATTERN);
|
|
21
|
+
if (!versionMatch) {
|
|
22
|
+
warnings.push(`Could not deterministically extract a Gradle version from distributionUrl: "${distributionUrl}".`);
|
|
23
|
+
return { distributionUrl, distributionType: "unknown", checksumPropertyPresent, warnings };
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
distributionUrl,
|
|
27
|
+
gradleVersion: versionMatch[1],
|
|
28
|
+
distributionType: versionMatch[2],
|
|
29
|
+
checksumPropertyPresent,
|
|
30
|
+
warnings,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { relativeWithinRoot, resolveWithinRoot } from "../../../core/pathSafety.js";
|
|
4
|
+
import { ANDROID_NAMESPACE_URI, findChildren, getAttribute, parseXmlDocument, } from "./xml/parseXml.js";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// v0.4.0 Batch 3 — AndroidManifest.xml parser.
|
|
7
|
+
//
|
|
8
|
+
// Maps the bounded XML tree from ./xml/parseXml.ts onto the Batch 1
|
|
9
|
+
// AndroidManifestModel contract. Static, deterministic, read-only: no
|
|
10
|
+
// resource-reference resolution, no manifest-placeholder resolution (values
|
|
11
|
+
// like @string/app_name and ${applicationId} are preserved verbatim), and no
|
|
12
|
+
// manifest merging. Malformed input always produces a valid (possibly
|
|
13
|
+
// mostly-empty) AndroidManifestModel with the problem recorded in
|
|
14
|
+
// `parseWarnings`/`unsupportedConstructs` rather than throwing.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
function toLocation(xmlLocation) {
|
|
17
|
+
return { line: xmlLocation.line, column: xmlLocation.column };
|
|
18
|
+
}
|
|
19
|
+
function getUnprefixedAttribute(element, localName) {
|
|
20
|
+
return element.attributes.find((attr) => attr.prefix === undefined && attr.localName === localName)?.value;
|
|
21
|
+
}
|
|
22
|
+
function androidAttr(element, localName) {
|
|
23
|
+
return getAttribute(element, localName, ANDROID_NAMESPACE_URI)?.value;
|
|
24
|
+
}
|
|
25
|
+
function parseBooleanAttr(element, localName, warnings, context) {
|
|
26
|
+
const attr = getAttribute(element, localName, ANDROID_NAMESPACE_URI);
|
|
27
|
+
if (!attr)
|
|
28
|
+
return {};
|
|
29
|
+
if (attr.value === "true")
|
|
30
|
+
return { value: true, raw: attr.value };
|
|
31
|
+
if (attr.value === "false")
|
|
32
|
+
return { value: false, raw: attr.value };
|
|
33
|
+
warnings.push(`${context}: android:${localName} has an unresolved or malformed value "${attr.value}" (line ${attr.location.line}) — not normalized to true or false.`);
|
|
34
|
+
return { raw: attr.value };
|
|
35
|
+
}
|
|
36
|
+
function parseIntAttr(element, localName, warnings, context) {
|
|
37
|
+
const attr = getAttribute(element, localName, ANDROID_NAMESPACE_URI);
|
|
38
|
+
if (!attr)
|
|
39
|
+
return undefined;
|
|
40
|
+
const parsed = Number.parseInt(attr.value, 10);
|
|
41
|
+
if (Number.isNaN(parsed)) {
|
|
42
|
+
warnings.push(`${context}: android:${localName} is not a resolvable integer ("${attr.value}", line ${attr.location.line}).`);
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
const LAUNCHER_ACTION = "android.intent.action.MAIN";
|
|
48
|
+
const LAUNCHER_CATEGORY = "android.intent.category.LAUNCHER";
|
|
49
|
+
const VIEW_ACTION = "android.intent.action.VIEW";
|
|
50
|
+
const BROWSABLE_CATEGORY = "android.intent.category.BROWSABLE";
|
|
51
|
+
function parseIntentFilter(element, warnings) {
|
|
52
|
+
const actions = findChildren(element, "action")
|
|
53
|
+
.map((a) => androidAttr(a, "name"))
|
|
54
|
+
.filter((v) => Boolean(v));
|
|
55
|
+
const categories = findChildren(element, "category")
|
|
56
|
+
.map((c) => androidAttr(c, "name"))
|
|
57
|
+
.filter((v) => Boolean(v));
|
|
58
|
+
const autoVerifyResult = parseBooleanAttr(element, "autoVerify", warnings, "intent-filter");
|
|
59
|
+
const dataElements = findChildren(element, "data").map((d) => ({
|
|
60
|
+
dataScheme: androidAttr(d, "scheme"),
|
|
61
|
+
dataHost: androidAttr(d, "host"),
|
|
62
|
+
dataPath: androidAttr(d, "path"),
|
|
63
|
+
dataPathPrefix: androidAttr(d, "pathPrefix"),
|
|
64
|
+
dataPathPattern: androidAttr(d, "pathPattern"),
|
|
65
|
+
autoVerify: autoVerifyResult.value,
|
|
66
|
+
location: toLocation(d.location),
|
|
67
|
+
}));
|
|
68
|
+
const isDeepLinkCandidate = actions.includes(VIEW_ACTION) && (categories.includes(BROWSABLE_CATEGORY) || dataElements.length > 0);
|
|
69
|
+
for (const child of element.children) {
|
|
70
|
+
if (child.localName !== "action" && child.localName !== "category" && child.localName !== "data") {
|
|
71
|
+
warnings.push(`Unsupported intent-filter child element <${child.tagName}> at line ${child.location.line} (preserved as a warning, not parsed).`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
filterData: dataElements,
|
|
76
|
+
actions,
|
|
77
|
+
categories,
|
|
78
|
+
dataElements,
|
|
79
|
+
autoVerify: autoVerifyResult.value,
|
|
80
|
+
isDeepLinkCandidate,
|
|
81
|
+
location: toLocation(element.location),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function parseComponent(kind, element, warnings) {
|
|
85
|
+
const context = `<${kind}> at line ${element.location.line}`;
|
|
86
|
+
const name = androidAttr(element, "name");
|
|
87
|
+
if (!name) {
|
|
88
|
+
warnings.push(`${context} is missing android:name.`);
|
|
89
|
+
}
|
|
90
|
+
const exportedResult = parseBooleanAttr(element, "exported", warnings, context);
|
|
91
|
+
const enabledResult = parseBooleanAttr(element, "enabled", warnings, context);
|
|
92
|
+
const permission = androidAttr(element, "permission");
|
|
93
|
+
const intentFilters = findChildren(element, "intent-filter").map((filterEl) => parseIntentFilter(filterEl, warnings));
|
|
94
|
+
const isLauncherActivity = (kind === "activity" || kind === "activity-alias") &&
|
|
95
|
+
intentFilters.some((filter) => filter.actions?.includes(LAUNCHER_ACTION) && filter.categories?.includes(LAUNCHER_CATEGORY));
|
|
96
|
+
const component = {
|
|
97
|
+
kind,
|
|
98
|
+
name: name ?? "",
|
|
99
|
+
exported: exportedResult.value,
|
|
100
|
+
exportedRaw: exportedResult.raw,
|
|
101
|
+
enabled: enabledResult.value,
|
|
102
|
+
permission,
|
|
103
|
+
intentFilters,
|
|
104
|
+
isLauncherActivity: isLauncherActivity || undefined,
|
|
105
|
+
location: toLocation(element.location),
|
|
106
|
+
};
|
|
107
|
+
if (kind === "provider") {
|
|
108
|
+
component.readPermission = androidAttr(element, "readPermission");
|
|
109
|
+
component.writePermission = androidAttr(element, "writePermission");
|
|
110
|
+
const authoritiesRaw = androidAttr(element, "authorities");
|
|
111
|
+
component.authoritiesRaw = authoritiesRaw;
|
|
112
|
+
component.authorities = authoritiesRaw ? authoritiesRaw.split(";").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
113
|
+
const grantUriPermissions = parseBooleanAttr(element, "grantUriPermissions", warnings, context);
|
|
114
|
+
component.grantUriPermissions = grantUriPermissions.value;
|
|
115
|
+
component.grantUriPermissionsRaw = grantUriPermissions.raw;
|
|
116
|
+
component.metadata = findChildren(element, "meta-data").map((metadata) => ({
|
|
117
|
+
name: androidAttr(metadata, "name"),
|
|
118
|
+
resource: androidAttr(metadata, "resource"),
|
|
119
|
+
value: androidAttr(metadata, "value"),
|
|
120
|
+
location: toLocation(metadata.location),
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
return component;
|
|
124
|
+
}
|
|
125
|
+
// Parses AndroidManifest.xml source text into the normalized contract. Pure
|
|
126
|
+
// (no filesystem access) so it can be unit tested directly against inline
|
|
127
|
+
// XML strings.
|
|
128
|
+
export function parseAndroidManifestSource(xmlText, manifestPath) {
|
|
129
|
+
const parseWarnings = [];
|
|
130
|
+
const unsupportedConstructs = [];
|
|
131
|
+
const parsed = parseXmlDocument(xmlText);
|
|
132
|
+
for (const warning of parsed.warnings) {
|
|
133
|
+
parseWarnings.push(`${warning.message} (line ${warning.location.line}, column ${warning.location.column})`);
|
|
134
|
+
}
|
|
135
|
+
if (!parsed.ok) {
|
|
136
|
+
parseWarnings.push(`Malformed XML: ${parsed.error.message} (line ${parsed.error.location.line}, column ${parsed.error.location.column})`);
|
|
137
|
+
return {
|
|
138
|
+
manifestPath,
|
|
139
|
+
application: {},
|
|
140
|
+
permissions: [],
|
|
141
|
+
usesFeatures: [],
|
|
142
|
+
activities: [],
|
|
143
|
+
activityAliases: [],
|
|
144
|
+
services: [],
|
|
145
|
+
receivers: [],
|
|
146
|
+
providers: [],
|
|
147
|
+
deepLinks: [],
|
|
148
|
+
parseWarnings,
|
|
149
|
+
unsupportedConstructs,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const root = parsed.root;
|
|
153
|
+
if (root.localName !== "manifest") {
|
|
154
|
+
parseWarnings.push(`Root element is <${root.tagName}>, not <manifest> — manifest could not be interpreted.`);
|
|
155
|
+
return {
|
|
156
|
+
manifestPath,
|
|
157
|
+
application: {},
|
|
158
|
+
permissions: [],
|
|
159
|
+
usesFeatures: [],
|
|
160
|
+
activities: [],
|
|
161
|
+
activityAliases: [],
|
|
162
|
+
services: [],
|
|
163
|
+
receivers: [],
|
|
164
|
+
providers: [],
|
|
165
|
+
deepLinks: [],
|
|
166
|
+
parseWarnings,
|
|
167
|
+
unsupportedConstructs,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const packageName = getUnprefixedAttribute(root, "package");
|
|
171
|
+
const permissionElementNames = [
|
|
172
|
+
["uses-permission", "uses-permission"],
|
|
173
|
+
["uses-permission-sdk-23", "uses-permission-sdk-23"],
|
|
174
|
+
["uses-permission-sdk-m", "uses-permission-sdk-m"],
|
|
175
|
+
];
|
|
176
|
+
const permissions = [];
|
|
177
|
+
for (const [elementName, sourceElement] of permissionElementNames) {
|
|
178
|
+
for (const el of findChildren(root, elementName)) {
|
|
179
|
+
const name = androidAttr(el, "name");
|
|
180
|
+
if (!name) {
|
|
181
|
+
parseWarnings.push(`<${elementName}> at line ${el.location.line} is missing android:name.`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
permissions.push({
|
|
185
|
+
name,
|
|
186
|
+
maxSdkVersion: parseIntAttr(el, "maxSdkVersion", parseWarnings, `<${elementName}> "${name}"`),
|
|
187
|
+
sourceElement,
|
|
188
|
+
location: toLocation(el.location),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const usesFeatures = findChildren(root, "uses-feature")
|
|
193
|
+
.map((el) => {
|
|
194
|
+
const name = androidAttr(el, "name");
|
|
195
|
+
if (!name) {
|
|
196
|
+
parseWarnings.push(`<uses-feature> at line ${el.location.line} is missing android:name.`);
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
name,
|
|
201
|
+
required: parseBooleanAttr(el, "required", parseWarnings, `<uses-feature> "${name}"`).value,
|
|
202
|
+
location: toLocation(el.location),
|
|
203
|
+
};
|
|
204
|
+
})
|
|
205
|
+
.filter((v) => v !== undefined);
|
|
206
|
+
const applicationElements = findChildren(root, "application");
|
|
207
|
+
const applicationEl = applicationElements[0];
|
|
208
|
+
if (!applicationEl) {
|
|
209
|
+
unsupportedConstructs.push("Manifest has no <application> element.");
|
|
210
|
+
}
|
|
211
|
+
if (applicationElements.length > 1) {
|
|
212
|
+
parseWarnings.push(`Manifest declares ${applicationElements.length} <application> elements; only the first was used.`);
|
|
213
|
+
}
|
|
214
|
+
const usesCleartextTrafficResult = applicationEl
|
|
215
|
+
? parseBooleanAttr(applicationEl, "usesCleartextTraffic", parseWarnings, "<application>")
|
|
216
|
+
: undefined;
|
|
217
|
+
const allowBackupResult = applicationEl ? parseBooleanAttr(applicationEl, "allowBackup", parseWarnings, "<application>") : undefined;
|
|
218
|
+
const debuggableResult = applicationEl ? parseBooleanAttr(applicationEl, "debuggable", parseWarnings, "<application>") : undefined;
|
|
219
|
+
const testOnlyResult = applicationEl ? parseBooleanAttr(applicationEl, "testOnly", parseWarnings, "<application>") : undefined;
|
|
220
|
+
const application = applicationEl
|
|
221
|
+
? {
|
|
222
|
+
name: androidAttr(applicationEl, "name"),
|
|
223
|
+
label: androidAttr(applicationEl, "label"),
|
|
224
|
+
iconRef: androidAttr(applicationEl, "icon"),
|
|
225
|
+
enabled: parseBooleanAttr(applicationEl, "enabled", parseWarnings, "<application>").value,
|
|
226
|
+
permission: androidAttr(applicationEl, "permission"),
|
|
227
|
+
allowBackup: allowBackupResult?.value,
|
|
228
|
+
allowBackupRaw: allowBackupResult?.raw,
|
|
229
|
+
debuggable: debuggableResult?.value,
|
|
230
|
+
debuggableRaw: debuggableResult?.raw,
|
|
231
|
+
usesCleartextTraffic: usesCleartextTrafficResult?.value,
|
|
232
|
+
usesCleartextTrafficRaw: usesCleartextTrafficResult?.raw,
|
|
233
|
+
networkSecurityConfigRef: androidAttr(applicationEl, "networkSecurityConfig"),
|
|
234
|
+
fullBackupContentRef: androidAttr(applicationEl, "fullBackupContent"),
|
|
235
|
+
dataExtractionRulesRef: androidAttr(applicationEl, "dataExtractionRules"),
|
|
236
|
+
testOnly: testOnlyResult?.value,
|
|
237
|
+
testOnlyRaw: testOnlyResult?.raw,
|
|
238
|
+
location: toLocation(applicationEl.location),
|
|
239
|
+
}
|
|
240
|
+
: {};
|
|
241
|
+
const activities = applicationEl ? findChildren(applicationEl, "activity").map((el) => parseComponent("activity", el, parseWarnings)) : [];
|
|
242
|
+
const activityAliases = applicationEl
|
|
243
|
+
? findChildren(applicationEl, "activity-alias").map((el) => parseComponent("activity-alias", el, parseWarnings))
|
|
244
|
+
: [];
|
|
245
|
+
const services = applicationEl ? findChildren(applicationEl, "service").map((el) => parseComponent("service", el, parseWarnings)) : [];
|
|
246
|
+
const receivers = applicationEl ? findChildren(applicationEl, "receiver").map((el) => parseComponent("receiver", el, parseWarnings)) : [];
|
|
247
|
+
const providers = applicationEl ? findChildren(applicationEl, "provider").map((el) => parseComponent("provider", el, parseWarnings)) : [];
|
|
248
|
+
if (applicationEl) {
|
|
249
|
+
const knownChildNames = new Set(["activity", "activity-alias", "service", "receiver", "provider", "meta-data"]);
|
|
250
|
+
for (const child of applicationEl.children) {
|
|
251
|
+
if (!knownChildNames.has(child.localName)) {
|
|
252
|
+
unsupportedConstructs.push(`Unsupported <application> child element <${child.tagName}> at line ${child.location.line}.`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const deepLinks = [];
|
|
257
|
+
let launcherActivityName;
|
|
258
|
+
for (const component of [...activities, ...activityAliases]) {
|
|
259
|
+
if (component.isLauncherActivity && !launcherActivityName) {
|
|
260
|
+
launcherActivityName = component.name;
|
|
261
|
+
}
|
|
262
|
+
for (const filter of component.intentFilters) {
|
|
263
|
+
if (filter.isDeepLinkCandidate) {
|
|
264
|
+
deepLinks.push(...(filter.dataElements ?? filter.filterData));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
manifestPath,
|
|
270
|
+
packageName,
|
|
271
|
+
application,
|
|
272
|
+
permissions,
|
|
273
|
+
usesFeatures,
|
|
274
|
+
activities,
|
|
275
|
+
activityAliases,
|
|
276
|
+
services,
|
|
277
|
+
receivers,
|
|
278
|
+
providers,
|
|
279
|
+
deepLinks,
|
|
280
|
+
launcherActivityName,
|
|
281
|
+
parseWarnings,
|
|
282
|
+
unsupportedConstructs,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
// Reads and parses a manifest file. `manifestRelativePath` must already be
|
|
286
|
+
// target-relative (as returned by AndroidDetectionResult.manifestPaths); this
|
|
287
|
+
// re-validates containment before reading so a caller-supplied path can never
|
|
288
|
+
// escape targetRoot.
|
|
289
|
+
export function parseAndroidManifestFile(targetRoot, manifestRelativePath) {
|
|
290
|
+
const resolvedRoot = path.resolve(targetRoot);
|
|
291
|
+
let absolutePath;
|
|
292
|
+
try {
|
|
293
|
+
absolutePath = resolveWithinRoot(resolvedRoot, manifestRelativePath);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
return {
|
|
297
|
+
manifestPath: manifestRelativePath,
|
|
298
|
+
application: {},
|
|
299
|
+
permissions: [],
|
|
300
|
+
usesFeatures: [],
|
|
301
|
+
activities: [],
|
|
302
|
+
activityAliases: [],
|
|
303
|
+
services: [],
|
|
304
|
+
receivers: [],
|
|
305
|
+
providers: [],
|
|
306
|
+
deepLinks: [],
|
|
307
|
+
parseWarnings: [`Manifest path rejected: ${error instanceof Error ? error.message : "path escapes target root"}`],
|
|
308
|
+
unsupportedConstructs: [],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const normalizedPath = relativeWithinRoot(resolvedRoot, absolutePath);
|
|
312
|
+
let xmlText;
|
|
313
|
+
try {
|
|
314
|
+
xmlText = readFileSync(absolutePath, "utf8");
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
return {
|
|
318
|
+
manifestPath: normalizedPath,
|
|
319
|
+
application: {},
|
|
320
|
+
permissions: [],
|
|
321
|
+
usesFeatures: [],
|
|
322
|
+
activities: [],
|
|
323
|
+
activityAliases: [],
|
|
324
|
+
services: [],
|
|
325
|
+
receivers: [],
|
|
326
|
+
providers: [],
|
|
327
|
+
deepLinks: [],
|
|
328
|
+
parseWarnings: [`Could not read manifest file: ${error instanceof Error ? error.message : "unknown error"}`],
|
|
329
|
+
unsupportedConstructs: [],
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return parseAndroidManifestSource(xmlText, normalizedPath);
|
|
333
|
+
}
|
|
334
|
+
const KNOWN_SOURCE_SET_KINDS = ["main", "debug", "release", "androidTest", "test"];
|
|
335
|
+
function inferSourceSetKind(manifestPath) {
|
|
336
|
+
const segments = manifestPath.split("/");
|
|
337
|
+
const srcIndex = segments.indexOf("src");
|
|
338
|
+
const sourceSetSegment = srcIndex !== -1 ? segments[srcIndex + 1] : undefined;
|
|
339
|
+
return KNOWN_SOURCE_SET_KINDS.includes(sourceSetSegment ?? "")
|
|
340
|
+
? sourceSetSegment
|
|
341
|
+
: "other";
|
|
342
|
+
}
|
|
343
|
+
function inferModulePath(manifestPath) {
|
|
344
|
+
const srcIndex = manifestPath.indexOf("/src/");
|
|
345
|
+
return srcIndex === -1 ? undefined : manifestPath.slice(0, srcIndex);
|
|
346
|
+
}
|
|
347
|
+
// Parses every manifest path from a Batch 2 AndroidDetectionResult
|
|
348
|
+
// independently. One malformed manifest never prevents the others from being
|
|
349
|
+
// parsed. Does not merge manifests — each entry stands alone, tagged with its
|
|
350
|
+
// inferred module and source-set association for later batches to use.
|
|
351
|
+
export function parseAllAndroidManifests(targetRoot, detection) {
|
|
352
|
+
const moduleManifestMap = new Map();
|
|
353
|
+
for (const module of detection.modules) {
|
|
354
|
+
for (const manifestPath of module.manifestPaths) {
|
|
355
|
+
moduleManifestMap.set(manifestPath, module.path);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return [...detection.manifestPaths].sort((a, b) => a.localeCompare(b)).map((manifestPath) => ({
|
|
359
|
+
manifestPath,
|
|
360
|
+
modulePath: moduleManifestMap.get(manifestPath) ?? inferModulePath(manifestPath),
|
|
361
|
+
sourceSetKind: inferSourceSetKind(manifestPath),
|
|
362
|
+
manifest: parseAndroidManifestFile(targetRoot, manifestPath),
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.0 Batch 1 — AndroidManifest model foundation.
|
|
3
|
+
// v0.4.0 Batch 3 — narrow additive inherited-contract corrections needed by
|
|
4
|
+
// manifest parsing and the initial audits (see agents.txt Batch 3 section
|
|
5
|
+
// 6.7). Every change below only adds new optional fields/enum members or a
|
|
6
|
+
// new optional array — no existing required field, type, or enum value was
|
|
7
|
+
// removed or retyped, so all Batch 1/2 object literals remain valid:
|
|
8
|
+
// - AndroidManifestComponentKind gained "activity-alias" (section 7.6).
|
|
9
|
+
// - AndroidManifestComponent gained optional `exportedRaw` so a malformed
|
|
10
|
+
// android:exported value can be distinguished from "unspecified"
|
|
11
|
+
// (`exported` stays undefined in both cases; `exportedRaw` preserves the
|
|
12
|
+
// literal attribute text when present) — section 7.7's invariant that
|
|
13
|
+
// malformed values must never be normalized to false requires this.
|
|
14
|
+
// - AndroidManifestModel gained optional `activityAliases` (section 7.6).
|
|
15
|
+
// - AndroidIntentFilter gained optional `actions`/`categories`/
|
|
16
|
+
// `dataElements` so multiple sibling <action>/<category>/<data> children
|
|
17
|
+
// can be preserved independently (section 7.8) without discarding the
|
|
18
|
+
// original `filterData` field, which Batch 1 tests and any external
|
|
19
|
+
// consumer may already rely on; the parser keeps populating both.
|
|
20
|
+
// - AndroidIntentFilterData gained optional `location` (section 7.11).
|
|
21
|
+
// - AndroidPermissionDeclaration gained optional `sourceElement` so
|
|
22
|
+
// uses-permission / uses-permission-sdk-23 / uses-permission-sdk-m are
|
|
23
|
+
// not conflated (section 7.4, ANDROID-B3-03).
|
|
24
|
+
//
|
|
25
|
+
// Normalized data contracts consumed by the manifest parser and the
|
|
26
|
+
// permission/component/deep-link auditors added in later batches. This file
|
|
27
|
+
// defines shape only — no XML parsing and no finding generation happen here.
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
export {};
|