@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,188 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseXmlDocument } from "../../manifest/xml/parseXml.js";
|
|
4
|
+
import { makeAndroidFinding } from "../../audit/androidFinding.js";
|
|
5
|
+
import { makeCandidateEvidence } from "../candidateEvidence.js";
|
|
6
|
+
import { sortCandidateEvidence } from "../ordering.js";
|
|
7
|
+
import { resolveAndroidXmlResourceReference } from "../resourceResolution.js";
|
|
8
|
+
import { buildAndroidSourceLocation } from "../sourceLocation.js";
|
|
9
|
+
import { discoverSecretSourceFiles } from "../secretCandidates/discoverSecretSourceFiles.js";
|
|
10
|
+
export const ANDROID_FILE_PROVIDER_AUDIT_CHECK_ID = "android-file-provider-audit";
|
|
11
|
+
const FILE_PROVIDER_METADATA = "android.support.FILE_PROVIDER_PATHS";
|
|
12
|
+
const PATH_TAGS = new Set(["root-path", "files-path", "cache-path", "external-path", "external-files-path", "external-cache-path", "external-media-path"]);
|
|
13
|
+
const EXTERNAL_TAGS = new Set(["external-path", "external-files-path", "external-cache-path", "external-media-path"]);
|
|
14
|
+
const MAX_PATH_ENTRIES = 256;
|
|
15
|
+
const MAX_EVIDENCE_VALUE = 240;
|
|
16
|
+
function sourceLocation(root, relativePath, line) {
|
|
17
|
+
return buildAndroidSourceLocation(root, path.join(root, relativePath), { line });
|
|
18
|
+
}
|
|
19
|
+
function standardProvider(name) {
|
|
20
|
+
return name === "androidx.core.content.FileProvider" || name === "android.support.v4.content.FileProvider";
|
|
21
|
+
}
|
|
22
|
+
function directCustomProviders(root, detection) {
|
|
23
|
+
const byModule = new Map();
|
|
24
|
+
const discovery = discoverSecretSourceFiles(root, detection.modules.map((module) => module.path));
|
|
25
|
+
for (const file of discovery.files.filter((candidate) => /\.(java|kt)$/.test(candidate.relativePath))) {
|
|
26
|
+
const moduleKey = file.modulePath ?? "";
|
|
27
|
+
const names = byModule.get(moduleKey) ?? new Set();
|
|
28
|
+
const executable = file.content.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\r\n]*/g, " ");
|
|
29
|
+
for (const match of executable.matchAll(/\bclass\s+([A-Za-z_]\w*)\s*(?:extends\s+|:\s*)(?:androidx\.core\.content\.|android\.support\.v4\.content\.)?FileProvider\s*(?:\(\s*\))?/g))
|
|
30
|
+
names.add(match[1]);
|
|
31
|
+
byModule.set(moduleKey, names);
|
|
32
|
+
}
|
|
33
|
+
return byModule;
|
|
34
|
+
}
|
|
35
|
+
function finding(ruleId, title, artifactPath, line, identity, severity) {
|
|
36
|
+
return makeAndroidFinding({ ruleId, title, severity, confidence: "high", description: `${title}. This is static manifest/XML evidence and does not prove runtime reachability, URI access, or file exposure.`, manifestPath: artifactPath, identity, location: line === undefined ? undefined : { line }, recommendation: "Keep FileProvider non-exported and restrict configured roots to the narrowest required paths." });
|
|
37
|
+
}
|
|
38
|
+
function candidate(ruleId, root, artifactPath, modulePath, line, summary, raw, state = "resolved", confidence = "medium") {
|
|
39
|
+
return makeCandidateEvidence({ ruleId, category: "android-file-provider", confidence, modulePath, location: sourceLocation(root, artifactPath, line), summary, rawValue: (raw ?? "(missing)").slice(0, MAX_EVIDENCE_VALUE), resolutionState: state, staticAnalysisLimitations: ["Manifest sources are not merged; resource overlays, installed authorities, URI grants, permission protection levels, and runtime filesystem behavior are not evaluated."] });
|
|
40
|
+
}
|
|
41
|
+
function protectedProvider(provider) {
|
|
42
|
+
return Boolean(provider.permission || provider.readPermission || provider.writePermission);
|
|
43
|
+
}
|
|
44
|
+
function broadPath(tag, rawPath) {
|
|
45
|
+
const value = (rawPath ?? "").trim().replace(/\\/g, "/").replace(/\/{2,}/g, "/");
|
|
46
|
+
return tag === "root-path" || value === "" || value === "." || value === "/" || /(^|\/)\.\.(\/|$)/.test(value) || /^[A-Za-z]:\//.test(value) || value.startsWith("/") || (EXTERNAL_TAGS.has(tag) && value.split("/").filter(Boolean).length <= 1);
|
|
47
|
+
}
|
|
48
|
+
function analyzeAuthorities(root, entry, provider, seen, out) {
|
|
49
|
+
const raw = provider.authoritiesRaw;
|
|
50
|
+
if (raw === undefined || raw.trim() === "") {
|
|
51
|
+
out.push(candidate("android-file-provider-exported", root, entry.manifestPath, entry.modulePath, provider.location?.line, "FileProvider authority is missing or empty", raw, raw === undefined ? "missing" : "malformed"));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const local = new Set();
|
|
55
|
+
for (const authority of raw.split(";")) {
|
|
56
|
+
const normalized = authority.trim();
|
|
57
|
+
if (!normalized) {
|
|
58
|
+
out.push(candidate("android-file-provider-exported", root, entry.manifestPath, entry.modulePath, provider.location?.line, "FileProvider authority contains an empty entry", authority, "malformed"));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (/\$\{[^}]+\}/.test(normalized)) {
|
|
62
|
+
out.push(candidate("android-file-provider-exported", root, entry.manifestPath, entry.modulePath, provider.location?.line, "FileProvider authority contains an unresolved placeholder", normalized, "unresolved"));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(normalized)) {
|
|
66
|
+
out.push(candidate("android-file-provider-exported", root, entry.manifestPath, entry.modulePath, provider.location?.line, "FileProvider authority is malformed", normalized, "malformed"));
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const key = normalized.toLowerCase();
|
|
70
|
+
if (local.has(key) || seen.has(key))
|
|
71
|
+
out.push(candidate("android-file-provider-exported", root, entry.manifestPath, entry.modulePath, provider.location?.line, `Duplicate FileProvider authority${seen.has(key) ? " across providers/modules" : " within provider"}`, normalized, "unresolved"));
|
|
72
|
+
local.add(key);
|
|
73
|
+
seen.set(key, `${entry.modulePath ?? ""}:${entry.manifestPath}:${provider.name}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function auditAndroidFileProviders(targetRoot, detection, manifests) {
|
|
77
|
+
if (detection.projectKind === "non-android")
|
|
78
|
+
return { id: ANDROID_FILE_PROVIDER_AUDIT_CHECK_ID, category: "android-file-provider", title: "Android FileProvider audit", status: "unsupported", requirementLevel: "optional", ran: false, skipped: true, skipInfo: { checkId: ANDROID_FILE_PROVIDER_AUDIT_CHECK_ID, reason: "Target was not detected as Android.", requirementLevel: "optional", verdictImpact: "does not apply", recommendedNextAction: "Run against an Android project." }, evidence: [], findings: [], warnings: [], errors: [], sourcePaths: [], confidence: "unknown", environmentRequirements: [], candidateEvidence: [] };
|
|
79
|
+
const findings = [];
|
|
80
|
+
const candidates = [];
|
|
81
|
+
const sources = new Set();
|
|
82
|
+
const seenAuthorities = new Map();
|
|
83
|
+
const custom = directCustomProviders(targetRoot, detection);
|
|
84
|
+
for (const entry of manifests)
|
|
85
|
+
for (const provider of entry.manifest.providers) {
|
|
86
|
+
const simpleName = provider.name.split(".").at(-1) ?? provider.name;
|
|
87
|
+
const confirmed = standardProvider(provider.name) || (custom.get(entry.modulePath ?? "")?.has(simpleName) ?? false);
|
|
88
|
+
if (!confirmed) {
|
|
89
|
+
if (/FileProvider$/i.test(provider.name))
|
|
90
|
+
candidates.push(candidate("android-file-provider-exported", targetRoot, entry.manifestPath, entry.modulePath, provider.location?.line, "FileProvider-like class is not a directly proven FileProvider subclass", provider.name, "unresolved", "low"));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
sources.add(entry.manifestPath);
|
|
94
|
+
const library = entry.modulePath !== undefined && detection.libraryModules.includes(entry.modulePath);
|
|
95
|
+
analyzeAuthorities(targetRoot, entry, provider, seenAuthorities, candidates);
|
|
96
|
+
if (provider.exported === true) {
|
|
97
|
+
if (library)
|
|
98
|
+
candidates.push(candidate("android-file-provider-exported", targetRoot, entry.manifestPath, entry.modulePath, provider.location?.line, "Library manifest explicitly exports a FileProvider; application merged behavior requires review", provider.name, "resolved"));
|
|
99
|
+
else
|
|
100
|
+
findings.push(finding("android-file-provider-exported", "Confirmed FileProvider is explicitly exported", entry.manifestPath, provider.location?.line, provider.name, "major"));
|
|
101
|
+
// The generic exported-component audit already owns the exact unprotected-provider finding.
|
|
102
|
+
if (!protectedProvider(provider))
|
|
103
|
+
candidates.push(candidate("android-file-provider-missing-protection", targetRoot, entry.manifestPath, entry.modulePath, provider.location?.line, "Exported FileProvider has no provider/read/write permission (generic exported audit owns the equivalent finding)", provider.name, "resolved", "high"));
|
|
104
|
+
}
|
|
105
|
+
else if (provider.exported === undefined)
|
|
106
|
+
candidates.push(candidate("android-file-provider-exported", targetRoot, entry.manifestPath, entry.modulePath, provider.location?.line, "FileProvider exported state is missing or unresolved", provider.exportedRaw, provider.exportedRaw === undefined ? "missing" : "unresolved"));
|
|
107
|
+
if (provider.grantUriPermissions === undefined)
|
|
108
|
+
candidates.push(candidate("android-file-provider-exported", targetRoot, entry.manifestPath, entry.modulePath, provider.location?.line, "FileProvider grantUriPermissions is missing or unresolved", provider.grantUriPermissionsRaw, provider.grantUriPermissionsRaw === undefined ? "missing" : "unresolved", "low"));
|
|
109
|
+
for (const value of [provider.permission, provider.readPermission, provider.writePermission].filter((item) => item !== undefined))
|
|
110
|
+
if (value.trim() === "" || /\$\{[^}]+\}/.test(value))
|
|
111
|
+
candidates.push(candidate("android-file-provider-missing-protection", targetRoot, entry.manifestPath, entry.modulePath, provider.location?.line, "Provider permission value is empty or unresolved", value, value.trim() === "" ? "malformed" : "unresolved"));
|
|
112
|
+
const metadata = (provider.metadata ?? []).filter((item) => item.name === FILE_PROVIDER_METADATA);
|
|
113
|
+
if (metadata.length === 0) {
|
|
114
|
+
candidates.push(candidate("android-file-provider-missing-paths-xml", targetRoot, entry.manifestPath, entry.modulePath, provider.location?.line, "FileProvider has no FILE_PROVIDER_PATHS metadata", undefined, "missing"));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const references = [...new Set(metadata.map((item) => item.resource ?? item.value ?? ""))];
|
|
118
|
+
if (references.length > 1) {
|
|
119
|
+
candidates.push(candidate("android-file-provider-unresolved-reference", targetRoot, entry.manifestPath, entry.modulePath, metadata[0].location?.line, "Conflicting FILE_PROVIDER_PATHS metadata entries were not selected arbitrarily", references.join(";"), "unresolved"));
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const reference = references[0];
|
|
123
|
+
if (!reference || metadata.some((item) => !item.resource)) {
|
|
124
|
+
candidates.push(candidate("android-file-provider-missing-paths-xml", targetRoot, entry.manifestPath, entry.modulePath, metadata[0].location?.line, "FILE_PROVIDER_PATHS metadata has no concrete android:resource", reference, reference ? "unsupported" : "missing"));
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const resolution = resolveAndroidXmlResourceReference(targetRoot, path.join(targetRoot, entry.modulePath ?? "."), reference);
|
|
128
|
+
if (resolution.state !== "resolved") {
|
|
129
|
+
const state = resolution.state === "missing" ? "missing" : resolution.state === "malformed-reference" ? (/\$\{/.test(reference) ? "unresolved" : "malformed") : resolution.state === "unsupported-reference" ? "unsupported" : "unresolved";
|
|
130
|
+
candidates.push(candidate(resolution.state === "missing" ? "android-file-provider-missing-paths-xml" : "android-file-provider-unresolved-reference", targetRoot, entry.manifestPath, entry.modulePath, metadata[0].location?.line, `FILE_PROVIDER_PATHS resource is ${resolution.state}${resolution.state === "ambiguous" ? ` (${resolution.candidates.length} candidates)` : ""}`, reference, state));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const resource = resolution.candidates[0];
|
|
134
|
+
sources.add(resource.relativePath);
|
|
135
|
+
let xml;
|
|
136
|
+
try {
|
|
137
|
+
xml = fs.readFileSync(path.join(targetRoot, resource.relativePath), "utf8");
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
candidates.push(candidate("android-file-provider-missing-paths-xml", targetRoot, resource.relativePath, entry.modulePath, undefined, "Resolved paths XML could not be read", resource.relativePath, "missing"));
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const parsed = parseXmlDocument(xml);
|
|
144
|
+
if (!parsed.ok) {
|
|
145
|
+
candidates.push(candidate("android-file-provider-malformed-paths-xml", targetRoot, resource.relativePath, entry.modulePath, parsed.error.location.line, "FileProvider paths XML is malformed", parsed.error.message, "malformed"));
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (parsed.root.localName !== "paths") {
|
|
149
|
+
candidates.push(candidate("android-file-provider-malformed-paths-xml", targetRoot, resource.relativePath, entry.modulePath, parsed.root.location.line, `Unsupported FileProvider paths root <${parsed.root.tagName}>`, parsed.root.tagName, "unsupported"));
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (parsed.root.children.length > MAX_PATH_ENTRIES)
|
|
153
|
+
candidates.push(candidate("android-file-provider-malformed-paths-xml", targetRoot, resource.relativePath, entry.modulePath, parsed.root.location.line, `FileProvider paths XML exceeds the ${MAX_PATH_ENTRIES}-entry analysis bound`, String(parsed.root.children.length), "unsupported"));
|
|
154
|
+
const identities = new Map();
|
|
155
|
+
const exact = new Set();
|
|
156
|
+
for (const child of parsed.root.children.slice(0, MAX_PATH_ENTRIES)) {
|
|
157
|
+
const name = child.attributes.find((attribute) => attribute.localName === "name")?.value;
|
|
158
|
+
const rawPath = child.attributes.find((attribute) => attribute.localName === "path")?.value;
|
|
159
|
+
if (!PATH_TAGS.has(child.localName)) {
|
|
160
|
+
candidates.push(candidate("android-file-provider-broad-paths", targetRoot, resource.relativePath, entry.modulePath, child.location.line, "Unsupported FileProvider path element", child.tagName, "unsupported"));
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (name === undefined || name.trim() === "")
|
|
164
|
+
candidates.push(candidate("android-file-provider-broad-paths", targetRoot, resource.relativePath, entry.modulePath, child.location.line, "FileProvider path entry has a missing or empty name", name, "malformed"));
|
|
165
|
+
if (rawPath === undefined)
|
|
166
|
+
candidates.push(candidate("android-file-provider-broad-paths", targetRoot, resource.relativePath, entry.modulePath, child.location.line, "FileProvider path entry is missing path", undefined, "malformed"));
|
|
167
|
+
const normalized = (rawPath ?? "").trim().replace(/\\/g, "/").replace(/\/{2,}/g, "/").slice(0, MAX_EVIDENCE_VALUE);
|
|
168
|
+
const exactKey = `${child.localName}:${name ?? ""}:${normalized}`;
|
|
169
|
+
const prior = identities.get(name ?? "");
|
|
170
|
+
if (exact.has(exactKey))
|
|
171
|
+
candidates.push(candidate("android-file-provider-broad-paths", targetRoot, resource.relativePath, entry.modulePath, child.location.line, "Duplicate FileProvider path entry", exactKey, "unresolved"));
|
|
172
|
+
else if (name !== undefined && prior !== undefined && prior !== `${child.localName}:${normalized}`)
|
|
173
|
+
candidates.push(candidate("android-file-provider-broad-paths", targetRoot, resource.relativePath, entry.modulePath, child.location.line, "Conflicting FileProvider path entries share the same name", `${name}:${prior}:${child.localName}:${normalized}`, "unresolved"));
|
|
174
|
+
exact.add(exactKey);
|
|
175
|
+
if (name !== undefined)
|
|
176
|
+
identities.set(name, `${child.localName}:${normalized}`);
|
|
177
|
+
if (broadPath(child.localName, rawPath)) {
|
|
178
|
+
if (provider.exported === true && !library)
|
|
179
|
+
findings.push(finding("android-file-provider-broad-paths", `Exported FileProvider has broad <${child.localName}> path`, resource.relativePath, child.location.line, `${entry.modulePath ?? ""}:${provider.name}:${name ?? ""}:${normalized}`, "major"));
|
|
180
|
+
else
|
|
181
|
+
candidates.push(candidate("android-file-provider-broad-paths", targetRoot, resource.relativePath, entry.modulePath, child.location.line, `Potentially broad <${child.localName}> path`, normalized, "resolved"));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const uniqueFindings = [...new Map(findings.map((item) => [item.id, item])).values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
186
|
+
const uniqueCandidates = [...new Map(candidates.map((item) => [item.id, item])).values()];
|
|
187
|
+
return { id: ANDROID_FILE_PROVIDER_AUDIT_CHECK_ID, category: "android-file-provider", title: "Android FileProvider audit", status: uniqueFindings.length ? "failed" : "passed", requirementLevel: "required", ran: true, skipped: false, evidence: [`${sources.size} manifest/XML artifact(s) inspected`], findings: uniqueFindings, warnings: ["Static analysis only: manifests are not merged; resource overlays, runtime URI grants, installed authorities, and provider-backed filesystem access are not evaluated."], errors: [], sourcePaths: [...sources].sort((a, b) => a.localeCompare(b)), confidence: "high", environmentRequirements: [], candidateEvidence: sortCandidateEvidence(uniqueCandidates) };
|
|
188
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { auditAndroidFileProviders, ANDROID_FILE_PROVIDER_AUDIT_CHECK_ID } from "./checkResult.js";
|
package/dist/src/mobile/android/advancedSecurity/firebaseGoogle/analyzeFirebaseGoogleServices.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { makeAndroidFinding } from "../../audit/androidFinding.js";
|
|
2
|
+
import { makeCandidateEvidence } from "../candidateEvidence.js";
|
|
3
|
+
import { buildAndroidSourceLocation } from "../sourceLocation.js";
|
|
4
|
+
import { discoverFirebaseArtifacts } from "./discoverFirebaseArtifacts.js";
|
|
5
|
+
import { parseGoogleServicesConfig } from "./parseGoogleServicesConfig.js";
|
|
6
|
+
import { parseFirebaseJson, parseFirebaseRc } from "./parseFirebaseConfig.js";
|
|
7
|
+
import { parseDatabaseRules } from "./parseDatabaseRules.js";
|
|
8
|
+
import { parseFirestoreOrStorageRules } from "./parseFirestoreStorageRules.js";
|
|
9
|
+
import { collectAnalyticsEventEvidence, collectCollectionSetterEvidence, collectGradleServiceEvidence, collectManifestCollectionMetadata, collectSourceServiceEvidence, } from "./collectFirebaseServiceEvidence.js";
|
|
10
|
+
function fileLocationLine(targetRoot, file, line) {
|
|
11
|
+
return buildAndroidSourceLocation(targetRoot, file.absolutePath, { line });
|
|
12
|
+
}
|
|
13
|
+
export function analyzeFirebaseGoogleServices(targetRoot, modulePaths) {
|
|
14
|
+
const candidates = [];
|
|
15
|
+
const findings = [];
|
|
16
|
+
const discovery = discoverFirebaseArtifacts(targetRoot, modulePaths);
|
|
17
|
+
const limitations = [
|
|
18
|
+
"Bounded static analysis of target-contained local files only; does not contact Firebase or Google services, does not verify deployed rules, and does not evaluate Gradle or resolve dependency selection.",
|
|
19
|
+
];
|
|
20
|
+
const candidate = (ruleId, file, summary, rawValue, confidence = "medium", resolutionState = "resolved", line) => candidates.push(makeCandidateEvidence({
|
|
21
|
+
ruleId,
|
|
22
|
+
category: "android-firebase-google-services",
|
|
23
|
+
confidence,
|
|
24
|
+
modulePath: file.modulePath,
|
|
25
|
+
location: fileLocationLine(targetRoot, file, line),
|
|
26
|
+
summary,
|
|
27
|
+
rawValue,
|
|
28
|
+
resolutionState,
|
|
29
|
+
staticAnalysisLimitations: limitations,
|
|
30
|
+
}));
|
|
31
|
+
const permissiveFinding = (ruleId, file, title, line, evidenceDetails) => findings.push(makeAndroidFinding({
|
|
32
|
+
ruleId,
|
|
33
|
+
title,
|
|
34
|
+
severity: "major",
|
|
35
|
+
confidence: "high",
|
|
36
|
+
description: `${title}. This reflects a local rules file only; it is not proof of the currently deployed configuration.`,
|
|
37
|
+
manifestPath: file.relativePath,
|
|
38
|
+
identity: `${ruleId}:${line ?? 0}`,
|
|
39
|
+
location: { line },
|
|
40
|
+
evidenceDetails,
|
|
41
|
+
recommendation: "Restrict the rule to authenticated, authorized access before deploying, and verify the deployed rules match the local file.",
|
|
42
|
+
}));
|
|
43
|
+
// --- google-services.json -------------------------------------------------
|
|
44
|
+
const googleServicesConfigs = discovery.googleServicesFiles.map(parseGoogleServicesConfig);
|
|
45
|
+
for (const config of googleServicesConfigs) {
|
|
46
|
+
if (config.malformed) {
|
|
47
|
+
candidate("android-firebase-google-configuration-evidence", config.file, "google-services.json could not be parsed as JSON", undefined, "low", "malformed");
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
candidate("android-firebase-google-configuration-evidence", config.file, `google-services.json parsed: project=${config.projectId ?? "(none)"}, clients=${config.clients.length}, apiKeys=${config.clients.reduce((n, c) => n + c.apiKeyCount, 0)}`, config.projectId, "low", "not-applicable");
|
|
51
|
+
const manifestPackages = discovery.manifestFiles
|
|
52
|
+
.map((m) => /\bpackage\s*=\s*"([^"]+)"/.exec(m.content)?.[1])
|
|
53
|
+
.filter((v) => Boolean(v));
|
|
54
|
+
if (manifestPackages.length > 0) {
|
|
55
|
+
const matchingClients = config.clients.filter((c) => c.packageName !== undefined && manifestPackages.includes(c.packageName));
|
|
56
|
+
if (matchingClients.length === 0) {
|
|
57
|
+
candidate("android-firebase-google-configuration-evidence", config.file, "No google-services.json client matches the detected application package", undefined, "medium", "unresolved");
|
|
58
|
+
}
|
|
59
|
+
else if (matchingClients.length > 1) {
|
|
60
|
+
candidate("android-firebase-google-configuration-evidence", config.file, "Multiple google-services.json clients match the detected application package", undefined, "medium", "unresolved");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// --- firebase.json / .firebaserc ------------------------------------------
|
|
65
|
+
const firebaseJsonConfigs = discovery.firebaseJsonFiles.map(parseFirebaseJson);
|
|
66
|
+
for (const config of firebaseJsonConfigs) {
|
|
67
|
+
if (config.malformed) {
|
|
68
|
+
candidate("android-firebase-google-configuration-evidence", config.file, "firebase.json could not be parsed as JSON", undefined, "low", "malformed");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const rc of discovery.firebaseRcFiles.map(parseFirebaseRc)) {
|
|
72
|
+
if (rc.malformed) {
|
|
73
|
+
candidate("android-firebase-google-configuration-evidence", rc.file, ".firebaserc could not be parsed as JSON", undefined, "low", "malformed");
|
|
74
|
+
}
|
|
75
|
+
else if (Object.keys(rc.aliases).length > 0) {
|
|
76
|
+
candidate("android-firebase-google-configuration-evidence", rc.file, `.firebaserc project alias metadata: ${Object.keys(rc.aliases).join(", ")}`, rc.defaultProject, "low", "not-applicable");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// --- rules resolution (referenced by firebase.json, or conventional names) --
|
|
80
|
+
const referencedPaths = new Set(firebaseJsonConfigs.flatMap((c) => [...c.databaseRulesPaths, ...c.firestoreRulesPaths, ...c.storageRulesPaths]));
|
|
81
|
+
for (const firebaseJson of discovery.firebaseJsonFiles) {
|
|
82
|
+
const config = firebaseJsonConfigs.find((c) => c.file === firebaseJson);
|
|
83
|
+
if (!config)
|
|
84
|
+
continue;
|
|
85
|
+
for (const rulesPath of [...config.databaseRulesPaths, ...config.firestoreRulesPaths, ...config.storageRulesPaths]) {
|
|
86
|
+
const normalized = rulesPath.replace(/^\.?\//, "");
|
|
87
|
+
const resolved = discovery.databaseRulesJsonFiles.find((f) => f.relativePath.endsWith(normalized)) ??
|
|
88
|
+
discovery.rulesFiles.find((f) => f.relativePath.endsWith(normalized));
|
|
89
|
+
if (!resolved) {
|
|
90
|
+
candidate("android-firebase-missing-local-rules", firebaseJson, `firebase.json references "${rulesPath}", but that rules file was not found in the target`, rulesPath, "medium", "missing");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
void referencedPaths;
|
|
95
|
+
// --- Realtime Database rules -----------------------------------------------
|
|
96
|
+
for (const dbRulesFile of discovery.databaseRulesJsonFiles) {
|
|
97
|
+
const result = parseDatabaseRules(dbRulesFile);
|
|
98
|
+
if (result.malformed) {
|
|
99
|
+
candidate("android-firebase-google-configuration-evidence", dbRulesFile, "database.rules.json could not be parsed as JSON", undefined, "low", "malformed");
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (const entry of result.entries) {
|
|
103
|
+
if ((entry.operation === ".read" || entry.operation === ".write") && entry.literalBoolean === true) {
|
|
104
|
+
permissiveFinding("android-firebase-realtime-database-permissive-rules", dbRulesFile, `Realtime Database ${entry.operation} rule at ${entry.jsonPath} is unconditionally true`, undefined, [`path=${entry.jsonPath}`, `operation=${entry.operation}`]);
|
|
105
|
+
}
|
|
106
|
+
else if (entry.operation === ".read" || entry.operation === ".write") {
|
|
107
|
+
candidate("android-firebase-google-configuration-evidence", dbRulesFile, `Realtime Database ${entry.operation} rule at ${entry.jsonPath}: ${entry.conditionSummary}`, entry.conditionSummary, "low", "not-applicable");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// --- Firestore / Storage rules ----------------------------------------------
|
|
112
|
+
for (const rulesFile of discovery.rulesFiles) {
|
|
113
|
+
const isStorage = /storage\.rules$/i.test(rulesFile.relativePath) || /service\s+firebase\.storage/.test(rulesFile.content);
|
|
114
|
+
const isFirestore = !isStorage && (/firestore\.rules$/i.test(rulesFile.relativePath) || /service\s+cloud\.firestore/.test(rulesFile.content));
|
|
115
|
+
if (!isStorage && !isFirestore)
|
|
116
|
+
continue;
|
|
117
|
+
const service = isStorage ? "storage" : "firestore";
|
|
118
|
+
const result = parseFirestoreOrStorageRules(rulesFile, service);
|
|
119
|
+
const ruleId = isStorage ? "android-firebase-storage-permissive-rules" : "android-firebase-firestore-permissive-rules";
|
|
120
|
+
for (const match of result.matches) {
|
|
121
|
+
if (match.isLiteralTrue && !match.hasAuthCondition) {
|
|
122
|
+
permissiveFinding(ruleId, rulesFile, `${service === "storage" ? "Storage" : "Firestore"} rule at ${match.matchPath} allows ${match.operations.join(", ")} unconditionally`, match.line, [
|
|
123
|
+
`path=${match.matchPath}`,
|
|
124
|
+
`operations=${match.operations.join(",")}`,
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
else if (match.isTestModeTimeCondition) {
|
|
128
|
+
candidate("android-firebase-google-configuration-evidence", rulesFile, `Time-limited test-mode rule at ${match.matchPath}: ${match.conditionSummary}`, match.conditionSummary, "medium", "not-applicable", match.line);
|
|
129
|
+
}
|
|
130
|
+
else if (!match.hasAuthCondition && !match.isLiteralFalse) {
|
|
131
|
+
candidate("android-firebase-google-configuration-evidence", rulesFile, `Unresolved/complex rule condition at ${match.matchPath}: ${match.conditionSummary}`, match.conditionSummary, "low", "unresolved", match.line);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// --- Gradle + source service-use evidence -----------------------------------
|
|
136
|
+
const allServiceEvidence = [
|
|
137
|
+
...discovery.gradleFiles.flatMap(collectGradleServiceEvidence),
|
|
138
|
+
...discovery.sourceFiles.flatMap(collectSourceServiceEvidence),
|
|
139
|
+
];
|
|
140
|
+
const servicesUsed = new Set(allServiceEvidence.map((e) => e.service));
|
|
141
|
+
if (allServiceEvidence.length > 0) {
|
|
142
|
+
const gradleFile = discovery.gradleFiles[0] ?? discovery.sourceFiles[0];
|
|
143
|
+
if (gradleFile) {
|
|
144
|
+
candidate("android-firebase-google-configuration-evidence", gradleFile, `Firebase service-use evidence found for: ${[...servicesUsed].sort().join(", ")}`, undefined, "low", "not-applicable");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const anyRulesResolved = discovery.databaseRulesJsonFiles.length > 0 || discovery.rulesFiles.length > 0;
|
|
148
|
+
const anchorFile = discovery.gradleFiles[0] ?? discovery.sourceFiles[0];
|
|
149
|
+
if (anchorFile) {
|
|
150
|
+
if (servicesUsed.has("database") && discovery.databaseRulesJsonFiles.length === 0) {
|
|
151
|
+
candidate("android-firebase-missing-local-rules", anchorFile, "Realtime Database service use detected but no local database.rules.json was found", undefined, "medium", "missing");
|
|
152
|
+
}
|
|
153
|
+
if (servicesUsed.has("firestore") && !discovery.rulesFiles.some((f) => /firestore\.rules$/i.test(f.relativePath) || /service\s+cloud\.firestore/.test(f.content))) {
|
|
154
|
+
candidate("android-firebase-missing-local-rules", anchorFile, "Firestore service use detected but no local firestore.rules was found", undefined, "medium", "missing");
|
|
155
|
+
}
|
|
156
|
+
if (servicesUsed.has("storage") && !discovery.rulesFiles.some((f) => /storage\.rules$/i.test(f.relativePath) || /service\s+firebase\.storage/.test(f.content))) {
|
|
157
|
+
candidate("android-firebase-missing-local-rules", anchorFile, "Storage service use detected but no local storage.rules was found", undefined, "medium", "missing");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
void anyRulesResolved;
|
|
161
|
+
// --- collection / auto-init metadata -----------------------------------------
|
|
162
|
+
for (const manifest of discovery.manifestFiles) {
|
|
163
|
+
for (const meta of collectManifestCollectionMetadata(manifest)) {
|
|
164
|
+
candidate("android-firebase-google-configuration-evidence", manifest, `${meta.name} manifest metadata is ${meta.state} (${meta.rawValue})`, meta.rawValue, "low", meta.state === "dynamic" ? "unresolved" : "not-applicable", meta.line);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const source of discovery.sourceFiles) {
|
|
168
|
+
for (const setter of collectCollectionSetterEvidence(source)) {
|
|
169
|
+
candidate("android-firebase-google-configuration-evidence", source, `${setter.service} collection setter called with state=${setter.state}`, undefined, "low", setter.state === "dynamic" ? "unresolved" : "not-applicable", setter.line);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// --- analytics event parameter review ------------------------------------------
|
|
173
|
+
for (const source of discovery.sourceFiles) {
|
|
174
|
+
for (const event of collectAnalyticsEventEvidence(source)) {
|
|
175
|
+
if (event.sensitiveParameterKey === undefined)
|
|
176
|
+
continue;
|
|
177
|
+
if (event.isDirectLiteralValue) {
|
|
178
|
+
findings.push(makeAndroidFinding({
|
|
179
|
+
ruleId: "android-firebase-google-configuration-evidence",
|
|
180
|
+
title: "Direct sensitive value sent as a Firebase Analytics event parameter",
|
|
181
|
+
severity: "major",
|
|
182
|
+
confidence: "high",
|
|
183
|
+
description: "A directly visible sensitive parameter key/value was passed to a Firebase Analytics logEvent() Bundle in the same method. Static source evidence only; does not prove the event was uploaded.",
|
|
184
|
+
manifestPath: source.relativePath,
|
|
185
|
+
identity: `analytics-event:${event.line}`,
|
|
186
|
+
location: { line: event.line },
|
|
187
|
+
evidenceDetails: [`key=${event.sensitiveParameterKey}`],
|
|
188
|
+
recommendation: "Do not send credentials or highly sensitive personal data as analytics event parameters.",
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
candidate("android-firebase-google-configuration-evidence", source, `Sensitive-looking analytics event parameter key "${event.sensitiveParameterKey}" with a dynamic value`, event.sensitiveParameterValueExpression, "medium", "unresolved", event.line);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { candidates, findings };
|
|
197
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { sortCandidateEvidence } from "../ordering.js";
|
|
2
|
+
import { analyzeFirebaseGoogleServices } from "./analyzeFirebaseGoogleServices.js";
|
|
3
|
+
import { discoverFirebaseArtifacts } from "./discoverFirebaseArtifacts.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// v0.4.1 Batch 6 — standalone Android Firebase/Google services check result.
|
|
6
|
+
// Follows the exact Batch 4/5 standalone pattern: not called from
|
|
7
|
+
// validateAndroidTarget / any active orchestration, not rendered in reports,
|
|
8
|
+
// no CLI effect. Batch 8 integrates.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export const ANDROID_FIREBASE_GOOGLE_SERVICES_AUDIT_CHECK_ID = "android-firebase-google-services-audit";
|
|
11
|
+
export function auditAndroidFirebaseGoogleServices(targetRoot, detection) {
|
|
12
|
+
if (detection.projectKind === "non-android") {
|
|
13
|
+
return {
|
|
14
|
+
id: ANDROID_FIREBASE_GOOGLE_SERVICES_AUDIT_CHECK_ID,
|
|
15
|
+
category: "android-firebase-google-services",
|
|
16
|
+
title: "Android Firebase/Google services audit",
|
|
17
|
+
status: "unsupported",
|
|
18
|
+
requirementLevel: "optional",
|
|
19
|
+
ran: false,
|
|
20
|
+
skipped: true,
|
|
21
|
+
skipInfo: {
|
|
22
|
+
checkId: ANDROID_FIREBASE_GOOGLE_SERVICES_AUDIT_CHECK_ID,
|
|
23
|
+
reason: "Target was not detected as an Android project.",
|
|
24
|
+
requirementLevel: "optional",
|
|
25
|
+
missingCapability: "android-detection",
|
|
26
|
+
verdictImpact: "does not apply to a non-Android target",
|
|
27
|
+
recommendedNextAction: "Re-run against an Android Gradle project, or verify project detection.",
|
|
28
|
+
},
|
|
29
|
+
evidence: [],
|
|
30
|
+
findings: [],
|
|
31
|
+
warnings: [],
|
|
32
|
+
errors: [],
|
|
33
|
+
sourcePaths: [],
|
|
34
|
+
confidence: "unknown",
|
|
35
|
+
environmentRequirements: [],
|
|
36
|
+
candidateEvidence: [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const modulePaths = detection.modules.map((m) => m.path);
|
|
40
|
+
const discovery = discoverFirebaseArtifacts(targetRoot, modulePaths);
|
|
41
|
+
const { candidates, findings } = analyzeFirebaseGoogleServices(targetRoot, modulePaths);
|
|
42
|
+
const sortedFindings = [...new Map(findings.map((f) => [f.id, f])).values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
43
|
+
const candidateEvidence = sortCandidateEvidence([...new Map(candidates.map((c) => [c.id, c])).values()]);
|
|
44
|
+
const hasReportableFinding = sortedFindings.some((f) => f.severity !== "informational");
|
|
45
|
+
const sourcePaths = [
|
|
46
|
+
...discovery.googleServicesFiles,
|
|
47
|
+
...discovery.firebaseJsonFiles,
|
|
48
|
+
...discovery.firebaseRcFiles,
|
|
49
|
+
...discovery.databaseRulesJsonFiles,
|
|
50
|
+
...discovery.rulesFiles,
|
|
51
|
+
...discovery.gradleFiles,
|
|
52
|
+
...discovery.sourceFiles,
|
|
53
|
+
...discovery.manifestFiles,
|
|
54
|
+
].map((f) => f.relativePath);
|
|
55
|
+
return {
|
|
56
|
+
id: ANDROID_FIREBASE_GOOGLE_SERVICES_AUDIT_CHECK_ID,
|
|
57
|
+
category: "android-firebase-google-services",
|
|
58
|
+
title: "Android Firebase/Google services audit",
|
|
59
|
+
status: hasReportableFinding ? "failed" : "passed",
|
|
60
|
+
requirementLevel: "required",
|
|
61
|
+
ran: true,
|
|
62
|
+
skipped: false,
|
|
63
|
+
evidence: [
|
|
64
|
+
`${discovery.googleServicesFiles.length} google-services.json, ${discovery.firebaseJsonFiles.length} firebase.json, ${discovery.databaseRulesJsonFiles.length + discovery.rulesFiles.length} rules file(s) found`,
|
|
65
|
+
],
|
|
66
|
+
findings: sortedFindings,
|
|
67
|
+
warnings: [
|
|
68
|
+
...discovery.skipped.map((s) => `${s.relativePath}: skipped (${s.reason}${s.detail ? `: ${s.detail}` : ""})`),
|
|
69
|
+
"Static analysis only: local configuration/rules files are not proof of the deployed Firebase project configuration; no remote Firebase or Google service was contacted.",
|
|
70
|
+
],
|
|
71
|
+
errors: [],
|
|
72
|
+
sourcePaths,
|
|
73
|
+
confidence: discovery.skipped.length > 0 ? "medium" : "high",
|
|
74
|
+
environmentRequirements: [],
|
|
75
|
+
candidateEvidence,
|
|
76
|
+
};
|
|
77
|
+
}
|