@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,136 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 4 — narrow, deterministic in-memory secret-candidate matching.
|
|
3
|
+
//
|
|
4
|
+
// This is the ONLY layer that ever holds a raw candidate value. Callers
|
|
5
|
+
// (analyzeSecretCandidates.ts) must redact/fingerprint immediately and never
|
|
6
|
+
// store, log, or export the raw value from this module. Nothing in this
|
|
7
|
+
// file is exported through index.ts.
|
|
8
|
+
//
|
|
9
|
+
// No entropy scoring is implemented: the structural markers (PEM headers)
|
|
10
|
+
// and explicit sensitive-identifier context already meet the "high
|
|
11
|
+
// confidence" bar the batch requires without it, and entropy alone must
|
|
12
|
+
// never create a finding per agents.txt Batch 4 section 9.8 — omitting it
|
|
13
|
+
// avoids speculative complexity with no corresponding required rule.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const PRIVATE_KEY_HEADER_PATTERN = /-----BEGIN ([A-Z0-9 ]*?PRIVATE KEY)-----/g;
|
|
16
|
+
function findBlockEnd(content, searchStart, headerLabel) {
|
|
17
|
+
const footer = `-----END ${headerLabel}-----`;
|
|
18
|
+
const footerIndex = content.indexOf(footer, searchStart);
|
|
19
|
+
if (footerIndex === -1) {
|
|
20
|
+
// Unterminated block — bound the captured region rather than reading to
|
|
21
|
+
// end of file unbounded.
|
|
22
|
+
const MAX_UNTERMINATED_BLOCK_LENGTH = 4000;
|
|
23
|
+
return { end: Math.min(content.length, searchStart + MAX_UNTERMINATED_BLOCK_LENGTH), terminated: false };
|
|
24
|
+
}
|
|
25
|
+
return { end: footerIndex + footer.length, terminated: true };
|
|
26
|
+
}
|
|
27
|
+
function lineNumberAt(content, index) {
|
|
28
|
+
let line = 1;
|
|
29
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
30
|
+
if (content[i] === "\n")
|
|
31
|
+
line++;
|
|
32
|
+
}
|
|
33
|
+
return line;
|
|
34
|
+
}
|
|
35
|
+
// Public keys and certificates ("-----BEGIN PUBLIC KEY-----",
|
|
36
|
+
// "-----BEGIN CERTIFICATE-----") never match PRIVATE_KEY_HEADER_PATTERN
|
|
37
|
+
// (which requires the literal substring "PRIVATE KEY") — the boundary is
|
|
38
|
+
// enforced by construction, not a separate exclusion check.
|
|
39
|
+
export function matchPrivateKeyBlocks(content) {
|
|
40
|
+
const matches = [];
|
|
41
|
+
for (const headerMatch of content.matchAll(PRIVATE_KEY_HEADER_PATTERN)) {
|
|
42
|
+
const headerLabel = headerMatch[1];
|
|
43
|
+
const startIndex = headerMatch.index ?? 0;
|
|
44
|
+
const { end, terminated } = findBlockEnd(content, startIndex, headerLabel);
|
|
45
|
+
matches.push({
|
|
46
|
+
line: lineNumberAt(content, startIndex),
|
|
47
|
+
headerLabel,
|
|
48
|
+
rawBlock: content.slice(startIndex, end),
|
|
49
|
+
terminated,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return matches;
|
|
53
|
+
}
|
|
54
|
+
// Sensitive identifier names that, when paired with a literal (never a
|
|
55
|
+
// reference/placeholder), are treated as high-confidence secret-shaped
|
|
56
|
+
// evidence. Deliberately narrow — matches agents.txt Batch 4 section 9.5.
|
|
57
|
+
//
|
|
58
|
+
// Splits the identifier into words (snake_case and camelCase boundaries)
|
|
59
|
+
// rather than using a single \b-anchored regex, so real-world constant
|
|
60
|
+
// naming like `SECRET_TOKEN`, `DB_PASSWORD`, or `placeholderSecret` is
|
|
61
|
+
// recognized — a plain `\bsecret\b` regex fails on these because
|
|
62
|
+
// underscore and camelCase transitions are not `\b` word boundaries.
|
|
63
|
+
const SENSITIVE_SINGLE_WORDS = new Set(["password", "passwd", "pwd", "secret"]);
|
|
64
|
+
const SENSITIVE_WORD_PAIRS = [
|
|
65
|
+
["client", "secret"],
|
|
66
|
+
["access", "token"],
|
|
67
|
+
["bearer", "token"],
|
|
68
|
+
["auth", "token"],
|
|
69
|
+
["api", "secret"],
|
|
70
|
+
["signing", "password"],
|
|
71
|
+
["store", "password"],
|
|
72
|
+
["key", "password"],
|
|
73
|
+
["database", "password"],
|
|
74
|
+
["db", "password"],
|
|
75
|
+
];
|
|
76
|
+
function splitIdentifierWords(identifier) {
|
|
77
|
+
return identifier
|
|
78
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
79
|
+
.split(/[_\-.\s]+/)
|
|
80
|
+
.map((word) => word.toLowerCase())
|
|
81
|
+
.filter((word) => word.length > 0);
|
|
82
|
+
}
|
|
83
|
+
function isSensitiveIdentifier(identifier) {
|
|
84
|
+
const words = splitIdentifierWords(identifier);
|
|
85
|
+
if (words.some((word) => SENSITIVE_SINGLE_WORDS.has(word)))
|
|
86
|
+
return true;
|
|
87
|
+
for (const [first, second] of SENSITIVE_WORD_PAIRS) {
|
|
88
|
+
for (let i = 0; i < words.length - 1; i++) {
|
|
89
|
+
if (words[i] === first && words[i + 1] === second)
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
// Finds `identifier = "value"` / `identifier: "value"` / `identifier="value"`
|
|
96
|
+
// / `"identifier": "value"` (JSON-style quoted key) pairs anywhere on a line
|
|
97
|
+
// (the value side quoting is required — code files never match an unquoted
|
|
98
|
+
// right-hand side, so a variable/function reference such as
|
|
99
|
+
// `val password = getPassword()` can never be mistaken for a literal).
|
|
100
|
+
const KEY_VALUE_QUOTED_PATTERN = /["']?([A-Za-z_][A-Za-z0-9_]*)["']?\s*[:=]\s*["']([^"']*)["']/g;
|
|
101
|
+
// Properties/YAML/TOML permit unquoted scalar values on a `key = value` /
|
|
102
|
+
// `key: value` line; still requires the key itself to match the sensitive
|
|
103
|
+
// identifier pattern.
|
|
104
|
+
const KEY_VALUE_UNQUOTED_PATTERN = /^\s*([A-Za-z_][A-Za-z0-9_.-]*)\s*[:=]\s*(\S.*?)\s*$/;
|
|
105
|
+
// Scans line-by-line (bounds work naturally; also gives exact line numbers
|
|
106
|
+
// without a second pass). Returns only pairs whose *captured identifier*
|
|
107
|
+
// (not just the line generally) matches a sensitive name — reference/
|
|
108
|
+
// placeholder/empty-value filtering happens in analyzeSecretCandidates.ts,
|
|
109
|
+
// which also owns redaction, so this function's return values are the last
|
|
110
|
+
// point at which a raw value exists before that immediate conversion.
|
|
111
|
+
export function matchSensitiveAssignments(content, fileKind) {
|
|
112
|
+
const matches = [];
|
|
113
|
+
const lines = content.split(/\r\n|\r|\n/);
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const line = lines[i];
|
|
116
|
+
if (fileKind === "config-unquoted") {
|
|
117
|
+
const unquoted = line.match(KEY_VALUE_UNQUOTED_PATTERN);
|
|
118
|
+
if (unquoted && isSensitiveIdentifier(unquoted[1])) {
|
|
119
|
+
matches.push({ line: i + 1, identifier: unquoted[1], rawValue: stripSurroundingQuotes(unquoted[2]) });
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
for (const pairMatch of line.matchAll(KEY_VALUE_QUOTED_PATTERN)) {
|
|
124
|
+
if (isSensitiveIdentifier(pairMatch[1])) {
|
|
125
|
+
matches.push({ line: i + 1, identifier: pairMatch[1], rawValue: pairMatch[2] });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return matches;
|
|
130
|
+
}
|
|
131
|
+
function stripSurroundingQuotes(value) {
|
|
132
|
+
if (value.length >= 2 && ((value[0] === '"' && value.endsWith('"')) || (value[0] === "'" && value.endsWith("'")))) {
|
|
133
|
+
return value.slice(1, -1);
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 4 — conservative placeholder/example/reference suppression.
|
|
3
|
+
//
|
|
4
|
+
// Deliberately a small, exact/narrow-normalized check list rather than a
|
|
5
|
+
// large heuristic catalog (agents.txt Batch 4 section 9.9). Operates only on
|
|
6
|
+
// the already-captured raw value (never re-reads the file), and this module
|
|
7
|
+
// is not exported through index.ts.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
const EXACT_PLACEHOLDER_VALUES = new Set([
|
|
10
|
+
"changeme",
|
|
11
|
+
"change_me",
|
|
12
|
+
"change-me",
|
|
13
|
+
"password",
|
|
14
|
+
"secret",
|
|
15
|
+
"example",
|
|
16
|
+
"sample",
|
|
17
|
+
"dummy",
|
|
18
|
+
"fake",
|
|
19
|
+
"test",
|
|
20
|
+
"your-token-here",
|
|
21
|
+
"your_token_here",
|
|
22
|
+
"replace-me",
|
|
23
|
+
"replace_me",
|
|
24
|
+
"redacted",
|
|
25
|
+
"masked",
|
|
26
|
+
"todo",
|
|
27
|
+
"xxx",
|
|
28
|
+
"placeholder",
|
|
29
|
+
"n/a",
|
|
30
|
+
"none",
|
|
31
|
+
].map((v) => v.toLowerCase()));
|
|
32
|
+
export function isEmptyValue(value) {
|
|
33
|
+
return value.trim().length === 0;
|
|
34
|
+
}
|
|
35
|
+
export function isObviousPlaceholderValue(value) {
|
|
36
|
+
const normalized = value.trim().toLowerCase();
|
|
37
|
+
if (normalized.length === 0)
|
|
38
|
+
return true;
|
|
39
|
+
if (EXACT_PLACEHOLDER_VALUES.has(normalized))
|
|
40
|
+
return true;
|
|
41
|
+
// Fully repeated single character (e.g. "****", "xxxxxxxx", "00000000").
|
|
42
|
+
if (/^(.)\1*$/.test(normalized))
|
|
43
|
+
return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
// Environment/property/manifest-placeholder reference syntax embedded in the
|
|
47
|
+
// literal itself (e.g. a Kotlin string template `"${DB_PASSWORD}"`, a
|
|
48
|
+
// properties-file `${some.property}`, a shell-style `$VAR`, a Windows
|
|
49
|
+
// `%VAR%`, or an Android manifest `@string/...`/`@xml/...` reference). Any
|
|
50
|
+
// of these means the "literal" is not actually a hardcoded value.
|
|
51
|
+
const REFERENCE_SYNTAX_PATTERN = /\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|%[A-Za-z_][A-Za-z0-9_]*%|^@(string|xml|raw|array|integer|bool)\//;
|
|
52
|
+
export function isReferenceSyntax(value) {
|
|
53
|
+
return REFERENCE_SYNTAX_PATTERN.test(value.trim());
|
|
54
|
+
}
|
|
55
|
+
// True when the value should never become a finding/candidate at all —
|
|
56
|
+
// empty or an obvious placeholder/example. Reference syntax is handled
|
|
57
|
+
// separately by the caller because it should surface as non-literal
|
|
58
|
+
// metadata (Batch 4 section 9.12), not be silently dropped.
|
|
59
|
+
export function shouldSuppressEntirely(value) {
|
|
60
|
+
return isEmptyValue(value) || isObviousPlaceholderValue(value);
|
|
61
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 4 — bounded secret-candidate scan contracts.
|
|
3
|
+
//
|
|
4
|
+
// Local to this feature; does not duplicate Batch 1's CandidateEvidence,
|
|
5
|
+
// SecurityFinding, or the shared bounded-source-scan utility
|
|
6
|
+
// (src/securityValidation/attackScenarios/boundedSourceScan.ts) — it wraps
|
|
7
|
+
// the existing target-containment/glob-collection primitives
|
|
8
|
+
// (src/core/fileGlobs.ts) with Android-appropriate exclusions and adds
|
|
9
|
+
// structured skip evidence, which neither existing utility provides.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 1 — sensitive-data category vocabulary for later secret/
|
|
3
|
+
// signing/configuration candidate scanners.
|
|
4
|
+
//
|
|
5
|
+
// Deliberately small and additive. This is not a security-severity scale
|
|
6
|
+
// (see SecuritySeverity in src/securityValidation/types.ts, reused as-is by
|
|
7
|
+
// candidateEvidence.ts) — it only classifies *what kind* of value a static
|
|
8
|
+
// candidate looks like.
|
|
9
|
+
//
|
|
10
|
+
// A bare Firebase API key (the public `apiKey` field in google-services.json
|
|
11
|
+
// / Firebase web config) must NOT be classified as "cloud-secret-key" or any
|
|
12
|
+
// other private-secret category by default: Firebase API keys identify a
|
|
13
|
+
// Firebase project but are not treated as confidential by Google's own
|
|
14
|
+
// documentation, and misclassifying them would produce a permanent, noisy
|
|
15
|
+
// false positive for a huge fraction of real Android apps. A later batch's
|
|
16
|
+
// Firebase scanner should classify that value as "unknown-sensitive-value"
|
|
17
|
+
// (or omit a candidate entirely) rather than a private-secret category.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
export const SENSITIVE_DATA_CATEGORIES = [
|
|
20
|
+
"password",
|
|
21
|
+
"access-token",
|
|
22
|
+
"bearer-token",
|
|
23
|
+
"oauth-client-secret",
|
|
24
|
+
"cloud-secret-key",
|
|
25
|
+
"private-key",
|
|
26
|
+
"database-credential",
|
|
27
|
+
"signing-password",
|
|
28
|
+
"recovery-code",
|
|
29
|
+
"personal-data",
|
|
30
|
+
"unknown-sensitive-value",
|
|
31
|
+
];
|
|
32
|
+
export function isSensitiveDataCategory(value) {
|
|
33
|
+
return SENSITIVE_DATA_CATEGORIES.includes(value);
|
|
34
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 6 — shared bounded direct-expression classification.
|
|
3
|
+
//
|
|
4
|
+
// Classifies a single, already-extracted argument/value expression string
|
|
5
|
+
// (captured by an analyzer's own bounded regex, e.g. the second argument to
|
|
6
|
+
// a Log.d(...) call, or the value passed to putString(...)) into a coarse
|
|
7
|
+
// syntactic kind. This performs no evaluation, no alias resolution beyond
|
|
8
|
+
// what the caller already resolved, and no interprocedural analysis — it is
|
|
9
|
+
// purely a classifier over the literal text of the expression.
|
|
10
|
+
//
|
|
11
|
+
// Safety invariant: `rawExpression` and the derived `literalValue` are
|
|
12
|
+
// returned for the caller's own redaction step (via
|
|
13
|
+
// makeCandidateEvidence/redactedPreviewForCandidate) — `boundedSummary` never
|
|
14
|
+
// contains literal content, only the classified kind and shape, so a caller
|
|
15
|
+
// that forgets to redact still cannot leak a raw value through the summary.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export const DIRECT_EXPRESSION_KINDS = [
|
|
18
|
+
"string-literal",
|
|
19
|
+
"numeric-literal",
|
|
20
|
+
"boolean-literal",
|
|
21
|
+
"null-literal",
|
|
22
|
+
"identifier",
|
|
23
|
+
"member-access",
|
|
24
|
+
"method-parameter",
|
|
25
|
+
"method-call",
|
|
26
|
+
"environment-lookup",
|
|
27
|
+
"property-lookup",
|
|
28
|
+
"placeholder-interpolation",
|
|
29
|
+
"collection-object",
|
|
30
|
+
"dynamic-unsupported",
|
|
31
|
+
];
|
|
32
|
+
const MAX_EXPRESSION_LENGTH = 400;
|
|
33
|
+
function bound(expression) {
|
|
34
|
+
return expression.length > MAX_EXPRESSION_LENGTH ? `${expression.slice(0, MAX_EXPRESSION_LENGTH)}...` : expression;
|
|
35
|
+
}
|
|
36
|
+
function unquoteJavaKotlinString(literal) {
|
|
37
|
+
const tripleMatch = /^"""([\s\S]*)"""$/.exec(literal);
|
|
38
|
+
if (tripleMatch)
|
|
39
|
+
return tripleMatch[1];
|
|
40
|
+
const doubleMatch = /^"((?:[^"\\]|\\.)*)"$/.exec(literal);
|
|
41
|
+
if (doubleMatch) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(literal);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return doubleMatch[1];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
export function classifyDirectExpression(rawExpression) {
|
|
52
|
+
const trimmed = bound(rawExpression.trim());
|
|
53
|
+
const summaryFor = (kind) => `${kind} (length=${trimmed.length})`;
|
|
54
|
+
if (trimmed.length === 0) {
|
|
55
|
+
return { kind: "dynamic-unsupported", isDirectLiteral: false, boundedSummary: summaryFor("dynamic-unsupported") };
|
|
56
|
+
}
|
|
57
|
+
// Kotlin string templates / Java concatenation with a dynamic operand:
|
|
58
|
+
// treated as placeholder-interpolation, not a pure literal, even though it
|
|
59
|
+
// may still contain a sensitive literal fragment — callers that need the
|
|
60
|
+
// literal fragment extract it separately before classification.
|
|
61
|
+
if (/\$\{[^}]*\}|\$[A-Za-z_][\w]*/.test(trimmed) && /^"/.test(trimmed)) {
|
|
62
|
+
return { kind: "placeholder-interpolation", isDirectLiteral: false, boundedSummary: summaryFor("placeholder-interpolation") };
|
|
63
|
+
}
|
|
64
|
+
if (/^"[^"]*"\s*\+/.test(trimmed) || /\+\s*"[^"]*"$/.test(trimmed) || (/^".*"$/.test(trimmed) && /\+/.test(trimmed))) {
|
|
65
|
+
return { kind: "placeholder-interpolation", isDirectLiteral: false, boundedSummary: summaryFor("placeholder-interpolation") };
|
|
66
|
+
}
|
|
67
|
+
const unquoted = unquoteJavaKotlinString(trimmed);
|
|
68
|
+
if (unquoted !== undefined) {
|
|
69
|
+
return { kind: "string-literal", isDirectLiteral: true, literalValue: unquoted, boundedSummary: summaryFor("string-literal") };
|
|
70
|
+
}
|
|
71
|
+
if (/^-?\d+(\.\d+)?[fFdDlL]?$/.test(trimmed)) {
|
|
72
|
+
return { kind: "numeric-literal", isDirectLiteral: true, literalValue: trimmed, boundedSummary: summaryFor("numeric-literal") };
|
|
73
|
+
}
|
|
74
|
+
if (trimmed === "true" || trimmed === "false") {
|
|
75
|
+
return { kind: "boolean-literal", isDirectLiteral: true, literalValue: trimmed, boundedSummary: summaryFor("boolean-literal") };
|
|
76
|
+
}
|
|
77
|
+
if (trimmed === "null") {
|
|
78
|
+
return { kind: "null-literal", isDirectLiteral: false, boundedSummary: summaryFor("null-literal") };
|
|
79
|
+
}
|
|
80
|
+
if (/^(System\.getenv|getenv)\s*\(/.test(trimmed)) {
|
|
81
|
+
return { kind: "environment-lookup", isDirectLiteral: false, boundedSummary: summaryFor("environment-lookup") };
|
|
82
|
+
}
|
|
83
|
+
if (/^BuildConfig\./.test(trimmed)) {
|
|
84
|
+
return { kind: "property-lookup", isDirectLiteral: false, boundedSummary: summaryFor("property-lookup") };
|
|
85
|
+
}
|
|
86
|
+
if (/^[{\[]/.test(trimmed)) {
|
|
87
|
+
return { kind: "collection-object", isDirectLiteral: false, boundedSummary: summaryFor("collection-object") };
|
|
88
|
+
}
|
|
89
|
+
if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*\s*\(/.test(trimmed)) {
|
|
90
|
+
return { kind: "method-call", isDirectLiteral: false, boundedSummary: summaryFor("method-call") };
|
|
91
|
+
}
|
|
92
|
+
if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)+$/.test(trimmed)) {
|
|
93
|
+
return { kind: "member-access", isDirectLiteral: false, boundedSummary: summaryFor("member-access") };
|
|
94
|
+
}
|
|
95
|
+
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
96
|
+
return { kind: "identifier", isDirectLiteral: false, boundedSummary: summaryFor("identifier") };
|
|
97
|
+
}
|
|
98
|
+
return { kind: "dynamic-unsupported", isDirectLiteral: false, boundedSummary: summaryFor("dynamic-unsupported") };
|
|
99
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const EXACT_WORD_CATEGORY = new Map([
|
|
2
|
+
// Authentication and credentials
|
|
3
|
+
["password", "password"],
|
|
4
|
+
["passwd", "password"],
|
|
5
|
+
["pwd", "password"],
|
|
6
|
+
["passcode", "password"],
|
|
7
|
+
["pin", "password"],
|
|
8
|
+
["secret", "unknown-sensitive-value"],
|
|
9
|
+
["credential", "unknown-sensitive-value"],
|
|
10
|
+
["credentials", "unknown-sensitive-value"],
|
|
11
|
+
["auth", "access-token"],
|
|
12
|
+
["authorization", "access-token"],
|
|
13
|
+
["bearer", "bearer-token"],
|
|
14
|
+
["bearertoken", "bearer-token"],
|
|
15
|
+
["token", "access-token"],
|
|
16
|
+
["accesstoken", "access-token"],
|
|
17
|
+
["refreshtoken", "access-token"],
|
|
18
|
+
["authtoken", "access-token"],
|
|
19
|
+
["sessiontoken", "access-token"],
|
|
20
|
+
["apisecret", "cloud-secret-key"],
|
|
21
|
+
["clientsecret", "oauth-client-secret"],
|
|
22
|
+
// Signing and recovery
|
|
23
|
+
["signingpassword", "signing-password"],
|
|
24
|
+
["storepassword", "signing-password"],
|
|
25
|
+
["keypassword", "signing-password"],
|
|
26
|
+
["recoverycode", "recovery-code"],
|
|
27
|
+
["backupcode", "recovery-code"],
|
|
28
|
+
["otp", "recovery-code"],
|
|
29
|
+
["onetimepassword", "recovery-code"],
|
|
30
|
+
// Personal and account data
|
|
31
|
+
["email", "personal-data"],
|
|
32
|
+
["phone", "personal-data"],
|
|
33
|
+
["phonenumber", "personal-data"],
|
|
34
|
+
["address", "personal-data"],
|
|
35
|
+
["location", "personal-data"],
|
|
36
|
+
["latitude", "personal-data"],
|
|
37
|
+
["longitude", "personal-data"],
|
|
38
|
+
["userid", "personal-data"],
|
|
39
|
+
["accountid", "personal-data"],
|
|
40
|
+
["deviceid", "personal-data"],
|
|
41
|
+
["advertisingid", "personal-data"],
|
|
42
|
+
// Financial or regulated data (no dedicated category — reuses
|
|
43
|
+
// personal-data, the closest Batch 1 regulated-data category)
|
|
44
|
+
["cardnumber", "personal-data"],
|
|
45
|
+
["creditcard", "personal-data"],
|
|
46
|
+
["bankaccount", "personal-data"],
|
|
47
|
+
["ssn", "personal-data"],
|
|
48
|
+
["taxid", "personal-data"],
|
|
49
|
+
// Database/session data
|
|
50
|
+
["session", "access-token"],
|
|
51
|
+
["cookie", "access-token"],
|
|
52
|
+
["databasepassword", "database-credential"],
|
|
53
|
+
["dbpassword", "database-credential"],
|
|
54
|
+
["connectionstring", "database-credential"],
|
|
55
|
+
]);
|
|
56
|
+
// Public identifiers that must never be classified as sensitive by name
|
|
57
|
+
// alone, even though a naive substring match might otherwise catch them.
|
|
58
|
+
const PUBLIC_IDENTIFIER_WORDS = new Set([
|
|
59
|
+
"apikey",
|
|
60
|
+
"projectid",
|
|
61
|
+
"appid",
|
|
62
|
+
"packagename",
|
|
63
|
+
"storagebucket",
|
|
64
|
+
"databaseurl",
|
|
65
|
+
"senderid",
|
|
66
|
+
"clientid",
|
|
67
|
+
"mobilesdkappid",
|
|
68
|
+
"applicationid",
|
|
69
|
+
"namespace",
|
|
70
|
+
]);
|
|
71
|
+
// Splits camelCase/PascalCase/snake_case/kebab-case identifiers into
|
|
72
|
+
// lowercase words, e.g. "userAuthToken" / "user_auth_token" -> ["user",
|
|
73
|
+
// "auth", "token"].
|
|
74
|
+
export function splitIdentifierWords(identifier) {
|
|
75
|
+
return identifier
|
|
76
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
77
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
78
|
+
.replace(/[-_.]+/g, " ")
|
|
79
|
+
.trim()
|
|
80
|
+
.split(/\s+/)
|
|
81
|
+
.filter(Boolean)
|
|
82
|
+
.map((word) => word.toLowerCase());
|
|
83
|
+
}
|
|
84
|
+
function normalizeCompound(words) {
|
|
85
|
+
return words.join("");
|
|
86
|
+
}
|
|
87
|
+
// Returns a classification only for a defensible direct match: either the
|
|
88
|
+
// whole normalized identifier (with separators removed) matches a known
|
|
89
|
+
// compound entry, or any individual word in the identifier exactly matches a
|
|
90
|
+
// known single-word entry. Never matches on an arbitrary substring.
|
|
91
|
+
export function classifySensitiveIdentifier(identifier) {
|
|
92
|
+
const words = splitIdentifierWords(identifier);
|
|
93
|
+
if (words.length === 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
const compound = normalizeCompound(words);
|
|
96
|
+
if (PUBLIC_IDENTIFIER_WORDS.has(compound))
|
|
97
|
+
return undefined;
|
|
98
|
+
const compoundCategory = EXACT_WORD_CATEGORY.get(compound);
|
|
99
|
+
if (compoundCategory !== undefined) {
|
|
100
|
+
return { sensitiveDataCategory: compoundCategory, normalized: compound };
|
|
101
|
+
}
|
|
102
|
+
for (const word of words) {
|
|
103
|
+
if (PUBLIC_IDENTIFIER_WORDS.has(word))
|
|
104
|
+
continue;
|
|
105
|
+
const category = EXACT_WORD_CATEGORY.get(word);
|
|
106
|
+
if (category !== undefined) {
|
|
107
|
+
return { sensitiveDataCategory: category, normalized: word };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { classifySensitiveIdentifier, splitIdentifierWords } from "./classifySensitiveIdentifier.js";
|
|
2
|
+
export { classifyDirectExpression, DIRECT_EXPRESSION_KINDS } from "./classifyDirectExpression.js";
|
|
3
|
+
export { executableMask, lineForOffset, methodScopes, scopeAt, sameScope } from "./localSourceContext.js";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 6 — narrow shared local Java/Kotlin source-context helpers.
|
|
3
|
+
//
|
|
4
|
+
// Extracted (not copied verbatim) from the bounded masking/scope technique
|
|
5
|
+
// Batch 5's WebView analyzer implemented privately in
|
|
6
|
+
// advancedSecurity/webview/checkResult.ts. That file is left completely
|
|
7
|
+
// unchanged (its own mask/scope logic stays inline) — this module is new,
|
|
8
|
+
// additive code used only by the four Batch 6 analyzers, generalized just
|
|
9
|
+
// enough to be language-general rather than SSL-callback-specific. It is
|
|
10
|
+
// NOT a compiler front end: no AST, no symbol resolution, no cross-method
|
|
11
|
+
// data flow. It only offers:
|
|
12
|
+
// - executableMask(): comment/string-masked text with byte-for-byte
|
|
13
|
+
// preserved offsets and line breaks, so downstream regexes never match
|
|
14
|
+
// inside a comment or a string literal, and reported line numbers stay
|
|
15
|
+
// correct against the original source.
|
|
16
|
+
// - methodScopes(): brace-depth-bounded method-like ranges (used for
|
|
17
|
+
// same-method correlation only — never cross-method).
|
|
18
|
+
// - lineForOffset(): 1-based line number for a byte offset.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const STRING_OR_COMMENT = /"""[\s\S]*?"""|"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|\/\/[^\n]*|\/\*[\s\S]*?\*\//g;
|
|
21
|
+
// Masks comment and string/char contents with spaces (preserving embedded
|
|
22
|
+
// newlines) so line numbers and offsets never shift and downstream regexes
|
|
23
|
+
// never match text that only appears inside a comment or a literal.
|
|
24
|
+
export function executableMask(source) {
|
|
25
|
+
return source.replace(STRING_OR_COMMENT, (match) => match.replace(/[^\n]/g, " "));
|
|
26
|
+
}
|
|
27
|
+
export function lineForOffset(source, offset) {
|
|
28
|
+
let line = 1;
|
|
29
|
+
for (let i = 0; i < offset && i < source.length; i += 1) {
|
|
30
|
+
if (source[i] === "\n")
|
|
31
|
+
line += 1;
|
|
32
|
+
}
|
|
33
|
+
return line;
|
|
34
|
+
}
|
|
35
|
+
const METHOD_HEADER = /(?:fun|void|[A-Za-z_$][\w$.<>,?\[\] ]*?)\s+[A-Za-z_$][\w$]*\s*\([^)]*\)[^{;]*\{/g;
|
|
36
|
+
function braceEnd(mask, openBraceIndex) {
|
|
37
|
+
let depth = 0;
|
|
38
|
+
for (let i = openBraceIndex; i < mask.length; i += 1) {
|
|
39
|
+
if (mask[i] === "{")
|
|
40
|
+
depth += 1;
|
|
41
|
+
else if (mask[i] === "}") {
|
|
42
|
+
depth -= 1;
|
|
43
|
+
if (depth === 0)
|
|
44
|
+
return i;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return mask.length - 1;
|
|
48
|
+
}
|
|
49
|
+
// Bounded, best-effort method-like scope extraction over already-masked
|
|
50
|
+
// text. Overlapping/nested matches are tolerated (each open-brace found by
|
|
51
|
+
// the header regex gets its own scope) — callers only use scope identity for
|
|
52
|
+
// "same enclosing scope" correlation, never for accurate call-graph shape.
|
|
53
|
+
export function methodScopes(mask) {
|
|
54
|
+
const scopes = [];
|
|
55
|
+
let match;
|
|
56
|
+
let id = 0;
|
|
57
|
+
METHOD_HEADER.lastIndex = 0;
|
|
58
|
+
while ((match = METHOD_HEADER.exec(mask)) !== null) {
|
|
59
|
+
const openBrace = match.index + match[0].length - 1;
|
|
60
|
+
if (mask[openBrace] !== "{")
|
|
61
|
+
continue;
|
|
62
|
+
const close = braceEnd(mask, openBrace);
|
|
63
|
+
scopes.push({ from: match.index, to: close, id });
|
|
64
|
+
id += 1;
|
|
65
|
+
}
|
|
66
|
+
return scopes;
|
|
67
|
+
}
|
|
68
|
+
// Innermost (smallest-span) scope containing offset, or undefined if offset
|
|
69
|
+
// is not inside any recognized method-like range (e.g. field initializer).
|
|
70
|
+
export function scopeAt(scopes, offset) {
|
|
71
|
+
let best;
|
|
72
|
+
for (const scope of scopes) {
|
|
73
|
+
if (offset >= scope.from && offset <= scope.to) {
|
|
74
|
+
if (best === undefined || scope.to - scope.from < best.to - best.from)
|
|
75
|
+
best = scope;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return best;
|
|
79
|
+
}
|
|
80
|
+
// Same-method correlation: true only when both offsets resolve to the same
|
|
81
|
+
// enclosing scope id (or both resolve to no scope, treated as file-level and
|
|
82
|
+
// therefore not correlated across arbitrary distance — callers should apply
|
|
83
|
+
// an additional bounded-distance check for the no-scope case).
|
|
84
|
+
export function sameScope(scopes, offsetA, offsetB) {
|
|
85
|
+
const a = scopeAt(scopes, offsetA);
|
|
86
|
+
const b = scopeAt(scopes, offsetB);
|
|
87
|
+
if (a === undefined || b === undefined)
|
|
88
|
+
return false;
|
|
89
|
+
return a.id === b.id;
|
|
90
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.1 Batch 6 — shared bounded top-level argument splitter.
|
|
3
|
+
//
|
|
4
|
+
// Splits a call's argument text on top-level commas only (commas nested
|
|
5
|
+
// inside (), [], {}, or string/char literals are not split points). Used by
|
|
6
|
+
// all four Batch 6 analyzers to pull individual arguments out of an already
|
|
7
|
+
// bounded, already-masked-for-comments call match. Not a parser: does not
|
|
8
|
+
// understand trailing lambdas, named/default arguments beyond a literal
|
|
9
|
+
// `name = value` prefix strip, or varargs semantics.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const MAX_ARGUMENTS = 12;
|
|
12
|
+
export function splitTopLevelArguments(argsText) {
|
|
13
|
+
const args = [];
|
|
14
|
+
let depth = 0;
|
|
15
|
+
let current = "";
|
|
16
|
+
let quote;
|
|
17
|
+
for (let i = 0; i < argsText.length; i += 1) {
|
|
18
|
+
const ch = argsText[i];
|
|
19
|
+
if (quote) {
|
|
20
|
+
current += ch;
|
|
21
|
+
if (ch === "\\") {
|
|
22
|
+
i += 1;
|
|
23
|
+
if (i < argsText.length)
|
|
24
|
+
current += argsText[i];
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (ch === quote)
|
|
28
|
+
quote = undefined;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ch === '"' || ch === "'") {
|
|
32
|
+
quote = ch;
|
|
33
|
+
current += ch;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
37
|
+
depth += 1;
|
|
38
|
+
current += ch;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
42
|
+
depth -= 1;
|
|
43
|
+
current += ch;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (ch === "," && depth === 0) {
|
|
47
|
+
args.push(current.trim());
|
|
48
|
+
current = "";
|
|
49
|
+
if (args.length >= MAX_ARGUMENTS)
|
|
50
|
+
break;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
current += ch;
|
|
54
|
+
}
|
|
55
|
+
if (current.trim().length > 0 || args.length === 0)
|
|
56
|
+
args.push(current.trim());
|
|
57
|
+
// Strip a leading `name = ` Kotlin named-argument prefix, if present.
|
|
58
|
+
return args.map((arg) => arg.replace(/^[A-Za-z_$][\w$]*\s*=\s*(?!=)/, ""));
|
|
59
|
+
}
|