@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,117 @@
|
|
|
1
|
+
import { makeAndroidFinding } from "../../audit/androidFinding.js";
|
|
2
|
+
import { makeCandidateEvidence } from "../candidateEvidence.js";
|
|
3
|
+
import { buildAndroidSourceLocation } from "../sourceLocation.js";
|
|
4
|
+
import { classifyDirectExpression } from "../sensitiveData/classifyDirectExpression.js";
|
|
5
|
+
import { classifySensitiveIdentifier } from "../sensitiveData/classifySensitiveIdentifier.js";
|
|
6
|
+
import { collectLoggingEvidence } from "./collectLoggingEvidence.js";
|
|
7
|
+
// Authorization/Bearer/Cookie/session/token identifiers are already covered
|
|
8
|
+
// by classifySensitiveIdentifier's shared vocabulary (no separate header
|
|
9
|
+
// word list here) — a bare string literal equal to a header *name* (e.g.
|
|
10
|
+
// "Authorization" used as a log tag) is deliberately never inspected for
|
|
11
|
+
// content, so it never produces a finding (see module header note above).
|
|
12
|
+
function sensitiveIdentifierNameFor(expression) {
|
|
13
|
+
if (expression === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
const classification = classifyDirectExpression(expression);
|
|
16
|
+
if (classification.kind !== "identifier" && classification.kind !== "member-access")
|
|
17
|
+
return undefined;
|
|
18
|
+
const name = expression.replace(/^.*\./, "");
|
|
19
|
+
const result = classifySensitiveIdentifier(name);
|
|
20
|
+
return result ? { sensitiveDataCategory: result } : undefined;
|
|
21
|
+
}
|
|
22
|
+
function isExceptionMessageExpression(expression) {
|
|
23
|
+
if (expression === undefined)
|
|
24
|
+
return false;
|
|
25
|
+
return /\.(message|localizedMessage)$/.test(expression) || /\.(getMessage|stackTraceToString)\s*\(\s*\)$/.test(expression);
|
|
26
|
+
}
|
|
27
|
+
export function analyzeSensitiveLoggingFile(targetRoot, file) {
|
|
28
|
+
const candidates = [];
|
|
29
|
+
const findings = [];
|
|
30
|
+
const matches = collectLoggingEvidence(file.content);
|
|
31
|
+
const limitations = [
|
|
32
|
+
"Bounded lexical source analysis; does not prove a log call executed, that logs are present on a device, or that Crashlytics uploaded data.",
|
|
33
|
+
];
|
|
34
|
+
const location = (m) => buildAndroidSourceLocation(targetRoot, file.absolutePath, { line: m.line });
|
|
35
|
+
const finding = (m, title, evidenceDetails) => findings.push(makeAndroidFinding({
|
|
36
|
+
ruleId: "android-sensitive-logging",
|
|
37
|
+
title,
|
|
38
|
+
severity: m.debugGuarded ? "minor" : "major",
|
|
39
|
+
confidence: "high",
|
|
40
|
+
description: `${title} via ${m.api}. Static source evidence only; does not prove the call executed or that logs were collected.`,
|
|
41
|
+
manifestPath: file.relativePath,
|
|
42
|
+
identity: `${m.api}:${m.line}`,
|
|
43
|
+
location: { line: m.line },
|
|
44
|
+
evidenceDetails: [...evidenceDetails, m.debugGuarded ? "debug-guard=present" : "debug-guard=none"],
|
|
45
|
+
recommendation: "Remove sensitive values from log statements, or guard them out of release builds entirely.",
|
|
46
|
+
}));
|
|
47
|
+
const candidate = (m, summary, rawValue, confidence = "medium", resolutionState = "resolved") => candidates.push(makeCandidateEvidence({
|
|
48
|
+
ruleId: "android-sensitive-logging",
|
|
49
|
+
category: "android-sensitive-logging",
|
|
50
|
+
confidence,
|
|
51
|
+
modulePath: file.modulePath,
|
|
52
|
+
location: location(m),
|
|
53
|
+
summary,
|
|
54
|
+
rawValue,
|
|
55
|
+
resolutionState,
|
|
56
|
+
staticAnalysisLimitations: limitations,
|
|
57
|
+
}));
|
|
58
|
+
for (const m of matches) {
|
|
59
|
+
if (m.kind === "isloggable") {
|
|
60
|
+
candidate(m, "Log.isLoggable is contextual evidence; it is not a security control by itself", undefined, "low", "not-applicable");
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (m.kind === "print-stack-trace") {
|
|
64
|
+
candidate(m, "printStackTrace() called; review evidence only, does not prove secrets are present", m.throwableExpression, "low", "not-applicable");
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (m.kind === "crashlytics") {
|
|
68
|
+
if (m.api === "Crashlytics.recordException") {
|
|
69
|
+
candidate(m, "Crashlytics recordException() called", undefined, "low", "not-applicable");
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (m.api === "Crashlytics.setCustomKey") {
|
|
73
|
+
const keyLiteral = m.tagExpression !== undefined ? classifyDirectExpression(m.tagExpression) : undefined;
|
|
74
|
+
const sensitive = keyLiteral?.kind === "string-literal" && keyLiteral.literalValue !== undefined ? classifySensitiveIdentifier(keyLiteral.literalValue) : sensitiveIdentifierNameFor(m.tagExpression);
|
|
75
|
+
if (sensitive) {
|
|
76
|
+
finding(m, "Sensitive Crashlytics custom key or value set", [`key=${m.tagExpression}`]);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
candidate(m, "Crashlytics setCustomKey() called", m.messageExpression, "low");
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (m.api === "Crashlytics.setUserId") {
|
|
84
|
+
const sensitive = sensitiveIdentifierNameFor(m.messageExpression);
|
|
85
|
+
if (sensitive) {
|
|
86
|
+
finding(m, "Direct personal identifier supplied to Crashlytics setUserId", [`value=${m.messageExpression}`]);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
candidate(m, "Crashlytics setUserId() called; privacy review evidence", m.messageExpression, "low");
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// Crashlytics.log — same sensitive-message policy as Log/Timber below.
|
|
94
|
+
}
|
|
95
|
+
// Log / Timber / stdout / Crashlytics.log share one sensitive-argument policy.
|
|
96
|
+
const tagSensitive = sensitiveIdentifierNameFor(m.tagExpression);
|
|
97
|
+
const messageSensitive = sensitiveIdentifierNameFor(m.messageExpression);
|
|
98
|
+
if (tagSensitive || messageSensitive) {
|
|
99
|
+
finding(m, `Direct sensitive value logged via ${m.api}`, [`argument=${messageSensitive ? m.messageExpression : m.tagExpression}`]);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (isExceptionMessageExpression(m.messageExpression)) {
|
|
103
|
+
candidate(m, "Exception message/stack-trace text logged; content not inspected", m.messageExpression, "low", "not-applicable");
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (m.messageExpression !== undefined) {
|
|
107
|
+
const classification = classifyDirectExpression(m.messageExpression);
|
|
108
|
+
if (classification.kind === "method-call" || classification.kind === "collection-object" || classification.kind === "member-access" || classification.kind === "dynamic-unsupported") {
|
|
109
|
+
candidate(m, `Dynamic ${classification.kind.replace("-", " ")} logged; contents not inspected`, m.messageExpression, "low", "unresolved");
|
|
110
|
+
}
|
|
111
|
+
else if (classification.kind === "identifier") {
|
|
112
|
+
candidate(m, "Dynamic log message logged; identifier not recognized as sensitive by name", m.messageExpression, "low", "unresolved");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { candidates, findings };
|
|
117
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { sortCandidateEvidence } from "../ordering.js";
|
|
2
|
+
import { discoverSecretSourceFiles } from "../secretCandidates/discoverSecretSourceFiles.js";
|
|
3
|
+
import { analyzeSensitiveLoggingFile } from "./analyzeSensitiveLogging.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// v0.4.1 Batch 6 — standalone Android sensitive-logging 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_SENSITIVE_LOGGING_AUDIT_CHECK_ID = "android-sensitive-logging-audit";
|
|
11
|
+
export function auditAndroidSensitiveLogging(targetRoot, detection) {
|
|
12
|
+
if (detection.projectKind === "non-android") {
|
|
13
|
+
return {
|
|
14
|
+
id: ANDROID_SENSITIVE_LOGGING_AUDIT_CHECK_ID,
|
|
15
|
+
category: "android-sensitive-logging",
|
|
16
|
+
title: "Android sensitive-logging audit",
|
|
17
|
+
status: "unsupported",
|
|
18
|
+
requirementLevel: "optional",
|
|
19
|
+
ran: false,
|
|
20
|
+
skipped: true,
|
|
21
|
+
skipInfo: {
|
|
22
|
+
checkId: ANDROID_SENSITIVE_LOGGING_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 { files, skipped } = discoverSecretSourceFiles(targetRoot, modulePaths);
|
|
41
|
+
const sourceFiles = files.filter((file) => /\.(java|kt)$/.test(file.relativePath));
|
|
42
|
+
const findingsById = new Map();
|
|
43
|
+
const candidatesById = new Map();
|
|
44
|
+
for (const file of sourceFiles) {
|
|
45
|
+
const result = analyzeSensitiveLoggingFile(targetRoot, file);
|
|
46
|
+
for (const f of result.findings)
|
|
47
|
+
findingsById.set(f.id, f);
|
|
48
|
+
for (const c of result.candidates)
|
|
49
|
+
candidatesById.set(c.id, c);
|
|
50
|
+
}
|
|
51
|
+
const findings = [...findingsById.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
52
|
+
const candidateEvidence = sortCandidateEvidence([...candidatesById.values()]);
|
|
53
|
+
const hasReportableFinding = findings.some((f) => f.severity !== "informational");
|
|
54
|
+
return {
|
|
55
|
+
id: ANDROID_SENSITIVE_LOGGING_AUDIT_CHECK_ID,
|
|
56
|
+
category: "android-sensitive-logging",
|
|
57
|
+
title: "Android sensitive-logging audit",
|
|
58
|
+
status: hasReportableFinding ? "failed" : "passed",
|
|
59
|
+
requirementLevel: "required",
|
|
60
|
+
ran: true,
|
|
61
|
+
skipped: false,
|
|
62
|
+
evidence: [`${sourceFiles.length} Java/Kotlin file(s) scanned`],
|
|
63
|
+
findings,
|
|
64
|
+
warnings: [
|
|
65
|
+
...skipped.map((s) => `${s.relativePath}: skipped (${s.reason}${s.detail ? `: ${s.detail}` : ""})`),
|
|
66
|
+
"Static analysis only: does not prove log calls executed, that logcat is accessible, or that Crashlytics uploaded data.",
|
|
67
|
+
],
|
|
68
|
+
errors: [],
|
|
69
|
+
sourcePaths: sourceFiles.map((f) => f.relativePath),
|
|
70
|
+
confidence: skipped.length > 0 ? "medium" : "high",
|
|
71
|
+
environmentRequirements: [],
|
|
72
|
+
candidateEvidence,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { executableMask, lineForOffset } from "../sensitiveData/localSourceContext.js";
|
|
2
|
+
import { splitTopLevelArguments } from "../sensitiveData/splitArguments.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// v0.4.1 Batch 6 — bounded direct evidence collection for sensitive-logging
|
|
5
|
+
// sinks (Android Log, Timber, stdout/stderr, printStackTrace, and
|
|
6
|
+
// Crashlytics payload sinks). Purely lexical, over comment/string-masked
|
|
7
|
+
// text with argument text re-sliced from the original content via regex
|
|
8
|
+
// match indices (see sensitiveStorage/collectStorageEvidence.ts for the same
|
|
9
|
+
// pattern and its rationale). Nothing here constructs CandidateEvidence or
|
|
10
|
+
// SecurityFinding.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const LOG_CALL = /\bLog\.(v|d|i|w|e|wtf|println)\s*\(([^;\n]*?)\)/gd;
|
|
13
|
+
const TIMBER_CALL = /\bTimber\.(v|d|i|w|e|wtf|log|tag)\s*\(([^;\n]*?)\)/gd;
|
|
14
|
+
const STDOUT_CALL = /(System\.out|System\.err)\.(print|println)\s*\(([^;\n]*?)\)/gd;
|
|
15
|
+
const KOTLIN_PRINT = /(?<![.\w])(print|println)\s*\(([^;\n]*?)\)/gd;
|
|
16
|
+
const PRINT_STACK_TRACE = /([A-Za-z_][\w.]*)\.printStackTrace\s*\(\s*\)/gd;
|
|
17
|
+
const CRASHLYTICS_CALL = /\b(FirebaseCrashlytics(?:\.getInstance\(\))?|crashlytics)\.(log|setCustomKey|setUserId|recordException)\s*\(([^;\n]*?)\)/gd;
|
|
18
|
+
const IS_LOGGABLE = /\bLog\.isLoggable\s*\(([^;\n]*?)\)/gd;
|
|
19
|
+
const DEBUG_GUARD_WINDOW = 3;
|
|
20
|
+
function realGroup(content, match, group) {
|
|
21
|
+
const span = match.indices?.[group];
|
|
22
|
+
if (!span)
|
|
23
|
+
return match[group];
|
|
24
|
+
return content.slice(span[0], span[1]);
|
|
25
|
+
}
|
|
26
|
+
function isDebugGuarded(content, offset) {
|
|
27
|
+
const startLine = lineForOffset(content, offset) - DEBUG_GUARD_WINDOW;
|
|
28
|
+
const lines = content.split("\n");
|
|
29
|
+
const from = Math.max(0, startLine - 1);
|
|
30
|
+
const window = lines.slice(from, lineForOffset(content, offset)).join("\n");
|
|
31
|
+
return /BuildConfig\.DEBUG|\bDEBUG\b/.test(window);
|
|
32
|
+
}
|
|
33
|
+
function push(list, content, kind, api, offset, extra) {
|
|
34
|
+
list.push({
|
|
35
|
+
kind,
|
|
36
|
+
api,
|
|
37
|
+
debugGuarded: isDebugGuarded(content, offset),
|
|
38
|
+
offset,
|
|
39
|
+
line: lineForOffset(content, offset),
|
|
40
|
+
...extra,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function collectLoggingEvidence(content) {
|
|
44
|
+
const mask = executableMask(content);
|
|
45
|
+
const matches = [];
|
|
46
|
+
for (const match of mask.matchAll(LOG_CALL)) {
|
|
47
|
+
const offset = match.index ?? 0;
|
|
48
|
+
const args = splitTopLevelArguments(realGroup(content, match, 2) ?? "");
|
|
49
|
+
if (match[1] === "println") {
|
|
50
|
+
push(matches, content, "log", `Log.println`, offset, { messageExpression: args[args.length - 1], tagExpression: args[1] });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
push(matches, content, "log", `Log.${match[1]}`, offset, { tagExpression: args[0], messageExpression: args[1], throwableExpression: args[2] });
|
|
54
|
+
}
|
|
55
|
+
for (const match of mask.matchAll(IS_LOGGABLE)) {
|
|
56
|
+
const offset = match.index ?? 0;
|
|
57
|
+
push(matches, content, "isloggable", "Log.isLoggable", offset, {});
|
|
58
|
+
}
|
|
59
|
+
for (const match of mask.matchAll(TIMBER_CALL)) {
|
|
60
|
+
const offset = match.index ?? 0;
|
|
61
|
+
const args = splitTopLevelArguments(realGroup(content, match, 2) ?? "");
|
|
62
|
+
push(matches, content, "timber", `Timber.${match[1]}`, offset, { messageExpression: args[0], throwableExpression: args[1] });
|
|
63
|
+
}
|
|
64
|
+
for (const match of mask.matchAll(STDOUT_CALL)) {
|
|
65
|
+
const offset = match.index ?? 0;
|
|
66
|
+
const args = splitTopLevelArguments(realGroup(content, match, 3) ?? "");
|
|
67
|
+
push(matches, content, "stdout", `${match[1]}.${match[2]}`, offset, { messageExpression: args[0] });
|
|
68
|
+
}
|
|
69
|
+
for (const match of mask.matchAll(KOTLIN_PRINT)) {
|
|
70
|
+
const offset = match.index ?? 0;
|
|
71
|
+
const args = splitTopLevelArguments(realGroup(content, match, 2) ?? "");
|
|
72
|
+
push(matches, content, "stdout", match[1], offset, { messageExpression: args[0] });
|
|
73
|
+
}
|
|
74
|
+
for (const match of mask.matchAll(PRINT_STACK_TRACE)) {
|
|
75
|
+
const offset = match.index ?? 0;
|
|
76
|
+
push(matches, content, "print-stack-trace", "printStackTrace", offset, { throwableExpression: match[1] });
|
|
77
|
+
}
|
|
78
|
+
for (const match of mask.matchAll(CRASHLYTICS_CALL)) {
|
|
79
|
+
const offset = match.index ?? 0;
|
|
80
|
+
const args = splitTopLevelArguments(realGroup(content, match, 3) ?? "");
|
|
81
|
+
push(matches, content, "crashlytics", `Crashlytics.${match[2]}`, offset, {
|
|
82
|
+
tagExpression: match[2] === "setCustomKey" ? args[0] : undefined,
|
|
83
|
+
messageExpression: args[match[2] === "setCustomKey" ? 1 : 0],
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return matches.sort((a, b) => a.offset - b.offset);
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { auditAndroidSensitiveLogging, ANDROID_SENSITIVE_LOGGING_AUDIT_CHECK_ID } from "./checkResult.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { makeAndroidFinding } from "../../audit/androidFinding.js";
|
|
2
|
+
import { makeCandidateEvidence } from "../candidateEvidence.js";
|
|
3
|
+
import { buildAndroidSourceLocation } from "../sourceLocation.js";
|
|
4
|
+
import { classifyDirectExpression } from "../sensitiveData/classifyDirectExpression.js";
|
|
5
|
+
import { classifySensitiveIdentifier } from "../sensitiveData/classifySensitiveIdentifier.js";
|
|
6
|
+
import { splitIdentifierWords } from "../sensitiveData/classifySensitiveIdentifier.js";
|
|
7
|
+
import { collectStorageEvidence } from "./collectStorageEvidence.js";
|
|
8
|
+
const SENSITIVE_FILE_NAME_WORDS = new Set([
|
|
9
|
+
"credentials",
|
|
10
|
+
"password",
|
|
11
|
+
"passwords",
|
|
12
|
+
"token",
|
|
13
|
+
"tokens",
|
|
14
|
+
"secret",
|
|
15
|
+
"secrets",
|
|
16
|
+
"session",
|
|
17
|
+
"auth",
|
|
18
|
+
"cookies",
|
|
19
|
+
"privatekey",
|
|
20
|
+
"account",
|
|
21
|
+
"userprofile",
|
|
22
|
+
]);
|
|
23
|
+
function literalOf(expression) {
|
|
24
|
+
if (expression === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
const classification = classifyDirectExpression(expression);
|
|
27
|
+
return classification.kind === "string-literal" ? classification.literalValue : undefined;
|
|
28
|
+
}
|
|
29
|
+
function isIdentifierExpression(expression) {
|
|
30
|
+
if (expression === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
const classification = classifyDirectExpression(expression);
|
|
33
|
+
return classification.kind === "identifier" || classification.kind === "member-access" ? expression.replace(/^.*\./, "") : undefined;
|
|
34
|
+
}
|
|
35
|
+
function fileNameLooksSensitive(pathHint) {
|
|
36
|
+
const literal = literalOf(pathHint);
|
|
37
|
+
const candidate = literal ?? pathHint;
|
|
38
|
+
if (!candidate)
|
|
39
|
+
return false;
|
|
40
|
+
const words = splitIdentifierWords(candidate.replace(/\.[a-z0-9]+$/i, ""));
|
|
41
|
+
return words.some((word) => SENSITIVE_FILE_NAME_WORDS.has(word));
|
|
42
|
+
}
|
|
43
|
+
function hasWorldMode(modeExpression) {
|
|
44
|
+
return modeExpression !== undefined && /MODE_WORLD_READABLE|MODE_WORLD_WRITEABLE/.test(modeExpression);
|
|
45
|
+
}
|
|
46
|
+
function hasPrivateMode(modeExpression) {
|
|
47
|
+
return modeExpression !== undefined && /MODE_PRIVATE/.test(modeExpression) && !hasWorldMode(modeExpression);
|
|
48
|
+
}
|
|
49
|
+
function sensitiveIdentifierFor(expression) {
|
|
50
|
+
if (expression === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
const literal = literalOf(expression);
|
|
53
|
+
if (literal !== undefined)
|
|
54
|
+
return classifySensitiveIdentifier(literal);
|
|
55
|
+
const identifier = isIdentifierExpression(expression);
|
|
56
|
+
return identifier !== undefined ? classifySensitiveIdentifier(identifier) : undefined;
|
|
57
|
+
}
|
|
58
|
+
export function analyzeSensitiveStorageFile(targetRoot, file) {
|
|
59
|
+
const candidates = [];
|
|
60
|
+
const findings = [];
|
|
61
|
+
const matches = collectStorageEvidence(file.content);
|
|
62
|
+
const location = (m) => buildAndroidSourceLocation(targetRoot, file.absolutePath, { line: m.line });
|
|
63
|
+
const limitations = ["Bounded lexical source analysis; does not evaluate runtime storage contents, filesystem permissions, or scoped-storage behavior for a specific device or API level."];
|
|
64
|
+
const finding = (ruleId, title, m, evidenceDetails, severity = "major") => findings.push(makeAndroidFinding({
|
|
65
|
+
ruleId,
|
|
66
|
+
title,
|
|
67
|
+
severity,
|
|
68
|
+
confidence: "high",
|
|
69
|
+
description: `${title}. Static source evidence only; does not prove data was stored at runtime or that another application can access it.`,
|
|
70
|
+
manifestPath: file.relativePath,
|
|
71
|
+
identity: `${m.api}:${m.line}`,
|
|
72
|
+
location: { line: m.line },
|
|
73
|
+
evidenceDetails,
|
|
74
|
+
recommendation: "Avoid storing sensitive data in plaintext; use EncryptedSharedPreferences/EncryptedFile or an equivalent platform-provided encryption wrapper.",
|
|
75
|
+
}));
|
|
76
|
+
const candidate = (ruleId, m, summary, rawValue, confidence, resolutionState = "resolved") => candidates.push(makeCandidateEvidence({
|
|
77
|
+
ruleId,
|
|
78
|
+
category: "android-sensitive-storage",
|
|
79
|
+
confidence,
|
|
80
|
+
modulePath: file.modulePath,
|
|
81
|
+
location: location(m),
|
|
82
|
+
summary,
|
|
83
|
+
rawValue,
|
|
84
|
+
resolutionState,
|
|
85
|
+
staticAnalysisLimitations: limitations,
|
|
86
|
+
}));
|
|
87
|
+
for (const m of matches) {
|
|
88
|
+
if (m.kind === "shared-preferences-get") {
|
|
89
|
+
if (hasWorldMode(m.modeExpression)) {
|
|
90
|
+
finding("android-sensitive-preferences", "SharedPreferences opened with a world-readable/world-writable mode", m, [`mode=${m.modeExpression}`]);
|
|
91
|
+
}
|
|
92
|
+
else if (hasPrivateMode(m.modeExpression)) {
|
|
93
|
+
candidate("android-sensitive-preferences", m, "SharedPreferences opened with MODE_PRIVATE (restrictive, not encryption)", m.modeExpression, "low", "not-applicable");
|
|
94
|
+
}
|
|
95
|
+
else if (m.modeExpression !== undefined && classifyDirectExpression(m.modeExpression).kind !== "string-literal") {
|
|
96
|
+
candidate("android-sensitive-preferences", m, "SharedPreferences mode is dynamic or unresolved", m.modeExpression, "low", "unresolved");
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (m.kind === "shared-preferences-write") {
|
|
101
|
+
const sensitive = sensitiveIdentifierFor(m.keyExpression);
|
|
102
|
+
if (!sensitive || m.valueExpression === undefined)
|
|
103
|
+
continue;
|
|
104
|
+
const valueClassification = classifyDirectExpression(m.valueExpression);
|
|
105
|
+
if (valueClassification.kind === "string-literal") {
|
|
106
|
+
finding("android-sensitive-preferences", `Direct ${sensitive.sensitiveDataCategory} written to plaintext SharedPreferences`, m, [`key=${m.keyExpression}`]);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
candidate("android-sensitive-preferences", m, `Sensitive key "${m.keyExpression}" written with a dynamic SharedPreferences value`, m.valueExpression, "medium", "unresolved");
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (m.kind === "datastore-write") {
|
|
114
|
+
const sensitive = sensitiveIdentifierFor(m.keyExpression);
|
|
115
|
+
if (!sensitive)
|
|
116
|
+
continue;
|
|
117
|
+
candidate("android-sensitive-preferences", m, `Sensitive DataStore key "${m.keyExpression}" written`, m.valueExpression, "medium", "unresolved");
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (m.kind === "internal-file-write") {
|
|
121
|
+
if (hasWorldMode(m.modeExpression)) {
|
|
122
|
+
finding("android-sensitive-unsafe-file-database-storage", "Internal file opened with a world-readable/world-writable mode", m, [`mode=${m.modeExpression}`]);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const nameSensitive = fileNameLooksSensitive(m.pathHint) || (m.receiver !== "(unresolved)" && Boolean(sensitiveIdentifierFor(m.receiver)));
|
|
126
|
+
const valueSensitive = sensitiveIdentifierFor(m.valueExpression);
|
|
127
|
+
if (!nameSensitive && !valueSensitive)
|
|
128
|
+
continue;
|
|
129
|
+
const valueClassification = m.valueExpression !== undefined ? classifyDirectExpression(m.valueExpression) : undefined;
|
|
130
|
+
if (valueSensitive && valueClassification?.kind === "string-literal") {
|
|
131
|
+
finding("android-sensitive-unsafe-file-database-storage", "Direct sensitive value written to a plaintext internal file", m, [`file=${m.pathHint ?? m.receiver}`]);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
candidate("android-sensitive-unsafe-file-database-storage", m, "Sensitive internal file write candidate", m.valueExpression ?? m.pathHint, "medium", "unresolved");
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (m.kind === "external-file-write") {
|
|
139
|
+
const isPublic = /getExternalStorageDirectory|getExternalStoragePublicDirectory/.test(m.api);
|
|
140
|
+
const isCache = /ExternalCacheDir/.test(m.api);
|
|
141
|
+
const nameSensitive = fileNameLooksSensitive(m.pathHint);
|
|
142
|
+
if (!nameSensitive)
|
|
143
|
+
continue;
|
|
144
|
+
if (isPublic) {
|
|
145
|
+
finding("android-sensitive-external-storage", "Sensitive-looking path written to shared public external storage", m, [`path=${m.pathHint}`]);
|
|
146
|
+
}
|
|
147
|
+
else if (isCache) {
|
|
148
|
+
candidate("android-sensitive-external-storage", m, "Sensitive-looking path written to external cache", m.pathHint, "medium");
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
candidate("android-sensitive-external-storage", m, "Sensitive-looking path written to app-specific external storage", m.pathHint, "medium");
|
|
152
|
+
}
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (m.kind === "database-write") {
|
|
156
|
+
if (m.api === "ContentValues.put") {
|
|
157
|
+
const sensitive = sensitiveIdentifierFor(m.keyExpression);
|
|
158
|
+
if (!sensitive)
|
|
159
|
+
continue;
|
|
160
|
+
const valueClassification = m.valueExpression !== undefined ? classifyDirectExpression(m.valueExpression) : undefined;
|
|
161
|
+
if (valueClassification?.kind === "string-literal") {
|
|
162
|
+
finding("android-sensitive-unsafe-file-database-storage", "Direct sensitive value inserted into a local database column", m, [`column=${m.keyExpression}`]);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
candidate("android-sensitive-unsafe-file-database-storage", m, `Sensitive database column "${m.keyExpression}" candidate`, m.valueExpression, "medium", "unresolved");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (m.api === "execSQL" && m.valueExpression !== undefined) {
|
|
169
|
+
const words = splitIdentifierWords(m.valueExpression);
|
|
170
|
+
if (words.some((word) => classifySensitiveIdentifier(word))) {
|
|
171
|
+
candidate("android-sensitive-unsafe-file-database-storage", m, "Sensitive-looking column name referenced in a raw SQL statement", m.valueExpression, "low");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (m.kind === "encrypted-storage-reference") {
|
|
177
|
+
candidate("android-sensitive-preferences", m, `Protective encrypted-storage API referenced: ${m.api}`, m.api, "low", "not-applicable");
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { candidates, findings };
|
|
182
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { sortCandidateEvidence } from "../ordering.js";
|
|
2
|
+
import { discoverSecretSourceFiles } from "../secretCandidates/discoverSecretSourceFiles.js";
|
|
3
|
+
import { analyzeSensitiveStorageFile } from "./analyzeSensitiveStorage.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// v0.4.1 Batch 6 — standalone Android sensitive-storage 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_SENSITIVE_STORAGE_AUDIT_CHECK_ID = "android-sensitive-storage-audit";
|
|
11
|
+
export function auditAndroidSensitiveStorage(targetRoot, detection) {
|
|
12
|
+
if (detection.projectKind === "non-android") {
|
|
13
|
+
return {
|
|
14
|
+
id: ANDROID_SENSITIVE_STORAGE_AUDIT_CHECK_ID,
|
|
15
|
+
category: "android-sensitive-storage",
|
|
16
|
+
title: "Android sensitive-storage audit",
|
|
17
|
+
status: "unsupported",
|
|
18
|
+
requirementLevel: "optional",
|
|
19
|
+
ran: false,
|
|
20
|
+
skipped: true,
|
|
21
|
+
skipInfo: {
|
|
22
|
+
checkId: ANDROID_SENSITIVE_STORAGE_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 { files, skipped } = discoverSecretSourceFiles(targetRoot, modulePaths);
|
|
41
|
+
const sourceFiles = files.filter((file) => /\.(java|kt)$/.test(file.relativePath));
|
|
42
|
+
const findingsById = new Map();
|
|
43
|
+
const candidatesById = new Map();
|
|
44
|
+
for (const file of sourceFiles) {
|
|
45
|
+
const result = analyzeSensitiveStorageFile(targetRoot, file);
|
|
46
|
+
for (const f of result.findings)
|
|
47
|
+
findingsById.set(f.id, f);
|
|
48
|
+
for (const c of result.candidates)
|
|
49
|
+
candidatesById.set(c.id, c);
|
|
50
|
+
}
|
|
51
|
+
const findings = [...findingsById.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
52
|
+
const candidateEvidence = sortCandidateEvidence([...candidatesById.values()]);
|
|
53
|
+
const hasReportableFinding = findings.some((f) => f.severity !== "informational");
|
|
54
|
+
return {
|
|
55
|
+
id: ANDROID_SENSITIVE_STORAGE_AUDIT_CHECK_ID,
|
|
56
|
+
category: "android-sensitive-storage",
|
|
57
|
+
title: "Android sensitive-storage audit",
|
|
58
|
+
status: hasReportableFinding ? "failed" : "passed",
|
|
59
|
+
requirementLevel: "required",
|
|
60
|
+
ran: true,
|
|
61
|
+
skipped: false,
|
|
62
|
+
evidence: [`${sourceFiles.length} Java/Kotlin file(s) scanned`],
|
|
63
|
+
findings,
|
|
64
|
+
warnings: [
|
|
65
|
+
...skipped.map((s) => `${s.relativePath}: skipped (${s.reason}${s.detail ? `: ${s.detail}` : ""})`),
|
|
66
|
+
"Static analysis only: does not inspect runtime storage contents, filesystem permissions, or database records.",
|
|
67
|
+
],
|
|
68
|
+
errors: [],
|
|
69
|
+
sourcePaths: sourceFiles.map((f) => f.relativePath),
|
|
70
|
+
confidence: skipped.length > 0 ? "medium" : "high",
|
|
71
|
+
environmentRequirements: [],
|
|
72
|
+
candidateEvidence,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { executableMask, lineForOffset } from "../sensitiveData/localSourceContext.js";
|
|
2
|
+
import { splitTopLevelArguments } from "../sensitiveData/splitArguments.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// v0.4.1 Batch 6 — bounded direct evidence collection for sensitive-storage
|
|
5
|
+
// APIs (SharedPreferences, DataStore, internal/external file writes,
|
|
6
|
+
// database/ContentValues writes, and encrypted-storage references).
|
|
7
|
+
//
|
|
8
|
+
// Purely lexical: call shapes are matched over comment/string-masked text
|
|
9
|
+
// (so nothing inside a comment or string literal is matched as executable
|
|
10
|
+
// code), but every captured group offset is then re-sliced out of the
|
|
11
|
+
// ORIGINAL, unmasked content (via regex match "indices", the `d` flag) —
|
|
12
|
+
// masked text has string-literal interiors blanked out, so reading argument
|
|
13
|
+
// text directly from a masked capture group would silently lose every
|
|
14
|
+
// string-literal key/value. Nothing in this file constructs CandidateEvidence
|
|
15
|
+
// or SecurityFinding, and nothing here is exported outside sensitiveStorage/.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const PUT_CALL = /([A-Za-z_][\w.]*)\.(?:edit\(\)\.)?(putString|putStringSet|putInt|putLong|putFloat|putBoolean|remove|clear|apply|commit)\s*\(([^;\n]*?)\)/gd;
|
|
18
|
+
const GET_PREFS = /(?:([A-Za-z_][\w.]*)\.)?(getSharedPreferences|PreferenceManager\.getDefaultSharedPreferences)\s*\(([^;\n]*?)\)/gd;
|
|
19
|
+
const DATASTORE_EDIT = /([A-Za-z_][\w.]*)\[\s*([A-Za-z_][\w.]*)\s*\]\s*=\s*([^;\n]+)/gd;
|
|
20
|
+
const INTERNAL_FILE_WRITE = /(?:([A-Za-z_][\w.]*)\.)?(openFileOutput|writeText|appendText)\s*\(([^;\n]*?)\)/gd;
|
|
21
|
+
const FILE_STREAM_CTOR = /new\s+(FileOutputStream|FileWriter|BufferedWriter)\s*\(([^;\n]*?)\)/gd;
|
|
22
|
+
const EXTERNAL_PATH_API = /(Environment\.getExternalStorageDirectory|Environment\.getExternalStoragePublicDirectory|[A-Za-z_][\w.]*\.getExternalFilesDir|[A-Za-z_][\w.]*\.externalFilesDir|[A-Za-z_][\w.]*\.getExternalCacheDir|[A-Za-z_][\w.]*\.externalCacheDir)\s*\(?([^;\n)]*)\)?/gd;
|
|
23
|
+
const CONTENT_VALUES_PUT = /([A-Za-z_][\w.]*)\.put\s*\(([^;\n]*?)\)/gd;
|
|
24
|
+
const EXEC_SQL = /([A-Za-z_][\w.]*)\.execSQL\s*\(([^;\n]*?)\)/gd;
|
|
25
|
+
const ENCRYPTED_STORAGE_REFERENCE = /\b(EncryptedSharedPreferences|EncryptedFile|MasterKey|AndroidKeyStore|SQLCipher)\b/gd;
|
|
26
|
+
function realGroup(content, match, group) {
|
|
27
|
+
const span = match.indices?.[group];
|
|
28
|
+
if (!span)
|
|
29
|
+
return match[group];
|
|
30
|
+
return content.slice(span[0], span[1]);
|
|
31
|
+
}
|
|
32
|
+
function pushMatch(list, content, kind, api, offset, extra) {
|
|
33
|
+
list.push({ kind, api, receiver: extra.receiver ?? "(unresolved)", offset, line: lineForOffset(content, offset), ...extra });
|
|
34
|
+
}
|
|
35
|
+
export function collectStorageEvidence(content) {
|
|
36
|
+
const mask = executableMask(content);
|
|
37
|
+
const matches = [];
|
|
38
|
+
for (const match of mask.matchAll(PUT_CALL)) {
|
|
39
|
+
const offset = match.index ?? 0;
|
|
40
|
+
const args = splitTopLevelArguments(realGroup(content, match, 3) ?? "");
|
|
41
|
+
pushMatch(matches, content, "shared-preferences-write", `SharedPreferences.Editor.${match[2]}`, offset, {
|
|
42
|
+
receiver: match[1],
|
|
43
|
+
keyExpression: args[0],
|
|
44
|
+
valueExpression: args[1],
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
for (const match of mask.matchAll(GET_PREFS)) {
|
|
48
|
+
const offset = match.index ?? 0;
|
|
49
|
+
const args = splitTopLevelArguments(realGroup(content, match, 3) ?? "");
|
|
50
|
+
pushMatch(matches, content, "shared-preferences-get", match[2], offset, {
|
|
51
|
+
receiver: match[1] ?? match[2],
|
|
52
|
+
pathHint: args[0],
|
|
53
|
+
modeExpression: args[1],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
for (const match of mask.matchAll(DATASTORE_EDIT)) {
|
|
57
|
+
const offset = match.index ?? 0;
|
|
58
|
+
pushMatch(matches, content, "datastore-write", "DataStore.edit[key]=", offset, {
|
|
59
|
+
receiver: match[1],
|
|
60
|
+
keyExpression: match[2],
|
|
61
|
+
valueExpression: realGroup(content, match, 3),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
for (const match of mask.matchAll(INTERNAL_FILE_WRITE)) {
|
|
65
|
+
const offset = match.index ?? 0;
|
|
66
|
+
const args = splitTopLevelArguments(realGroup(content, match, 3) ?? "");
|
|
67
|
+
pushMatch(matches, content, "internal-file-write", match[2], offset, {
|
|
68
|
+
receiver: match[1],
|
|
69
|
+
pathHint: match[2] === "openFileOutput" ? args[0] : undefined,
|
|
70
|
+
modeExpression: match[2] === "openFileOutput" ? args[1] : undefined,
|
|
71
|
+
valueExpression: match[2] !== "openFileOutput" ? args[0] : undefined,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
for (const match of mask.matchAll(FILE_STREAM_CTOR)) {
|
|
75
|
+
const offset = match.index ?? 0;
|
|
76
|
+
const args = splitTopLevelArguments(realGroup(content, match, 2) ?? "");
|
|
77
|
+
pushMatch(matches, content, "internal-file-write", `new ${match[1]}`, offset, {
|
|
78
|
+
pathHint: args[0],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
for (const match of mask.matchAll(EXTERNAL_PATH_API)) {
|
|
82
|
+
const offset = match.index ?? 0;
|
|
83
|
+
pushMatch(matches, content, "external-file-write", match[1], offset, {
|
|
84
|
+
pathHint: realGroup(content, match, 2),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
for (const match of mask.matchAll(CONTENT_VALUES_PUT)) {
|
|
88
|
+
const offset = match.index ?? 0;
|
|
89
|
+
const args = splitTopLevelArguments(realGroup(content, match, 2) ?? "");
|
|
90
|
+
pushMatch(matches, content, "database-write", "ContentValues.put", offset, {
|
|
91
|
+
receiver: match[1],
|
|
92
|
+
keyExpression: args[0],
|
|
93
|
+
valueExpression: args[1],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
for (const match of mask.matchAll(EXEC_SQL)) {
|
|
97
|
+
const offset = match.index ?? 0;
|
|
98
|
+
const args = splitTopLevelArguments(realGroup(content, match, 2) ?? "");
|
|
99
|
+
pushMatch(matches, content, "database-write", "execSQL", offset, {
|
|
100
|
+
receiver: match[1],
|
|
101
|
+
valueExpression: args[0],
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
for (const match of mask.matchAll(ENCRYPTED_STORAGE_REFERENCE)) {
|
|
105
|
+
const offset = match.index ?? 0;
|
|
106
|
+
pushMatch(matches, content, "encrypted-storage-reference", match[1], offset, {});
|
|
107
|
+
}
|
|
108
|
+
return matches.sort((a, b) => a.offset - b.offset);
|
|
109
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { auditAndroidSensitiveStorage, ANDROID_SENSITIVE_STORAGE_AUDIT_CHECK_ID } from "./checkResult.js";
|