@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,210 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveWithinRoot } from "../../../core/pathSafety.js";
|
|
4
|
+
import { extractVersionCatalogEvidence } from "../detect/versionCatalogEvidence.js";
|
|
5
|
+
import { parseWrapperProperties } from "./wrapperMetadata.js";
|
|
6
|
+
import { parseVersionCatalog } from "./versionCatalogMetadata.js";
|
|
7
|
+
import { extractModuleGradleMetadata } from "./moduleMetadataExtractor.js";
|
|
8
|
+
import { resolvePluginVersion } from "./pluginVersionExtractor.js";
|
|
9
|
+
import { parseSettingsMetadata } from "./settingsMetadata.js";
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// v0.4.0 Batch 4 — static Android Gradle metadata extraction entry point.
|
|
12
|
+
//
|
|
13
|
+
// Static, deterministic, read-only, network-free, and independent of Gradle
|
|
14
|
+
// execution/Java/Android SDK availability — consumes only the Batch 2
|
|
15
|
+
// AndroidDetectionResult (settings/build/wrapper/catalog file paths and
|
|
16
|
+
// module records) plus direct filesystem reads of those same files.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
function readTextSafe(absolutePath) {
|
|
19
|
+
try {
|
|
20
|
+
return readFileSync(absolutePath, "utf8");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const ANDROID_APPLICATION_PLUGIN_ID = "com.android.application";
|
|
27
|
+
const ANDROID_LIBRARY_PLUGIN_ID = "com.android.library";
|
|
28
|
+
const KOTLIN_ANDROID_PLUGIN_ID = "org.jetbrains.kotlin.android";
|
|
29
|
+
const AGP_CLASSPATH_GROUP_ARTIFACT = "com.android.tools.build:gradle";
|
|
30
|
+
export function readAndroidGradleMetadata(targetRoot, detection) {
|
|
31
|
+
const resolvedRoot = path.resolve(targetRoot);
|
|
32
|
+
const parseWarnings = [];
|
|
33
|
+
const unsupportedExpressions = [];
|
|
34
|
+
// ---- Wrapper ---------------------------------------------------------------
|
|
35
|
+
const wrapperPropertiesPath = "gradle/wrapper/gradle-wrapper.properties";
|
|
36
|
+
let wrapperPropertiesExists = false;
|
|
37
|
+
try {
|
|
38
|
+
resolveWithinRoot(resolvedRoot, wrapperPropertiesPath);
|
|
39
|
+
wrapperPropertiesExists = true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
wrapperPropertiesExists = false;
|
|
43
|
+
}
|
|
44
|
+
const wrapperText = wrapperPropertiesExists ? readTextSafe(path.join(resolvedRoot, wrapperPropertiesPath)) : undefined;
|
|
45
|
+
const gradlewPresent = readTextSafe(path.join(resolvedRoot, "gradlew")) !== undefined;
|
|
46
|
+
const gradlewBatPresent = readTextSafe(path.join(resolvedRoot, "gradlew.bat")) !== undefined;
|
|
47
|
+
const wrapperWarnings = [];
|
|
48
|
+
let wrapper;
|
|
49
|
+
if (wrapperText !== undefined) {
|
|
50
|
+
const parsed = parseWrapperProperties(wrapperText);
|
|
51
|
+
wrapperWarnings.push(...parsed.warnings);
|
|
52
|
+
if (!gradlewPresent && !gradlewBatPresent) {
|
|
53
|
+
wrapperWarnings.push("gradle-wrapper.properties exists but neither gradlew nor gradlew.bat was found.");
|
|
54
|
+
}
|
|
55
|
+
wrapper = {
|
|
56
|
+
present: detection.hasGradleWrapper,
|
|
57
|
+
wrapperPropertiesPath,
|
|
58
|
+
versionEvidence: parsed.gradleVersion,
|
|
59
|
+
distributionUrl: parsed.distributionUrl,
|
|
60
|
+
distributionType: parsed.distributionType,
|
|
61
|
+
checksumPropertyPresent: parsed.checksumPropertyPresent,
|
|
62
|
+
gradlewPresent,
|
|
63
|
+
gradlewBatPresent,
|
|
64
|
+
warnings: wrapperWarnings,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (gradlewPresent || gradlewBatPresent) {
|
|
69
|
+
wrapperWarnings.push("gradlew/gradlew.bat found but gradle-wrapper.properties is missing or unreadable.");
|
|
70
|
+
}
|
|
71
|
+
wrapper = {
|
|
72
|
+
present: detection.hasGradleWrapper,
|
|
73
|
+
gradlewPresent,
|
|
74
|
+
gradlewBatPresent,
|
|
75
|
+
warnings: wrapperWarnings,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
parseWarnings.push(...wrapperWarnings);
|
|
79
|
+
// ---- Settings ---------------------------------------------------------------
|
|
80
|
+
let rootProjectName;
|
|
81
|
+
for (const settingsPath of detection.gradleSettingsFiles) {
|
|
82
|
+
const text = readTextSafe(path.join(resolvedRoot, settingsPath));
|
|
83
|
+
if (!text)
|
|
84
|
+
continue;
|
|
85
|
+
const parsed = parseSettingsMetadata(text);
|
|
86
|
+
if (parsed.rootProjectName && !rootProjectName)
|
|
87
|
+
rootProjectName = parsed.rootProjectName;
|
|
88
|
+
if (parsed.includedBuilds.length > 0) {
|
|
89
|
+
parseWarnings.push(`${settingsPath}: included build(s) present as unsupported/external evidence: ${parsed.includedBuilds.join(", ")}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// ---- Version catalog ---------------------------------------------------------
|
|
93
|
+
let versionCatalogEvidence;
|
|
94
|
+
let parsedCatalog;
|
|
95
|
+
if (detection.versionCatalogFiles.length > 0) {
|
|
96
|
+
const catalogText = readTextSafe(path.join(resolvedRoot, detection.versionCatalogFiles[0]));
|
|
97
|
+
if (catalogText) {
|
|
98
|
+
versionCatalogEvidence = extractVersionCatalogEvidence(catalogText);
|
|
99
|
+
parsedCatalog = parseVersionCatalog(catalogText);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// ---- Root build files: plugin/AGP/Kotlin version evidence --------------------
|
|
103
|
+
const pluginEvidence = {};
|
|
104
|
+
for (const rootBuildFilePath of detection.rootBuildFiles) {
|
|
105
|
+
const text = readTextSafe(path.join(resolvedRoot, rootBuildFilePath));
|
|
106
|
+
if (!text)
|
|
107
|
+
continue;
|
|
108
|
+
const hasDirectAgpTextEvidence = text.includes(ANDROID_APPLICATION_PLUGIN_ID) || text.includes(ANDROID_LIBRARY_PLUGIN_ID);
|
|
109
|
+
if (!pluginEvidence.androidGradlePluginVersion) {
|
|
110
|
+
const agpVersion = resolvePluginVersion(text, ANDROID_APPLICATION_PLUGIN_ID, parsedCatalog, AGP_CLASSPATH_GROUP_ARTIFACT);
|
|
111
|
+
if (agpVersion.version)
|
|
112
|
+
pluginEvidence.androidGradlePluginVersion = agpVersion.version;
|
|
113
|
+
}
|
|
114
|
+
if (!pluginEvidence.androidGradlePluginEvidence && (hasDirectAgpTextEvidence || pluginEvidence.androidGradlePluginVersion)) {
|
|
115
|
+
pluginEvidence.androidGradlePluginEvidence = hasDirectAgpTextEvidence
|
|
116
|
+
? `Android Gradle Plugin referenced in ${rootBuildFilePath}`
|
|
117
|
+
: `Android Gradle Plugin referenced via version-catalog alias in ${rootBuildFilePath}`;
|
|
118
|
+
}
|
|
119
|
+
const hasDirectKotlinTextEvidence = text.includes(KOTLIN_ANDROID_PLUGIN_ID);
|
|
120
|
+
if (!pluginEvidence.kotlinAndroidPluginVersion) {
|
|
121
|
+
const kotlinVersion = resolvePluginVersion(text, KOTLIN_ANDROID_PLUGIN_ID, parsedCatalog);
|
|
122
|
+
if (kotlinVersion.version)
|
|
123
|
+
pluginEvidence.kotlinAndroidPluginVersion = kotlinVersion.version;
|
|
124
|
+
}
|
|
125
|
+
if (!pluginEvidence.kotlinAndroidPluginEvidence && (hasDirectKotlinTextEvidence || pluginEvidence.kotlinAndroidPluginVersion)) {
|
|
126
|
+
pluginEvidence.kotlinAndroidPluginEvidence = hasDirectKotlinTextEvidence
|
|
127
|
+
? `Kotlin Android plugin referenced in ${rootBuildFilePath}`
|
|
128
|
+
: `Kotlin Android plugin referenced via version-catalog alias in ${rootBuildFilePath}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (versionCatalogEvidence && versionCatalogEvidence.composeAliasEvidence.length > 0) {
|
|
132
|
+
pluginEvidence.composePluginEvidence = `Version-catalog Compose alias(es): ${versionCatalogEvidence.composeAliasEvidence.join(", ")}`;
|
|
133
|
+
}
|
|
134
|
+
if (!pluginEvidence.androidGradlePluginVersion && pluginEvidence.androidGradlePluginEvidence) {
|
|
135
|
+
unsupportedExpressions.push("Android Gradle Plugin version could not be statically resolved.");
|
|
136
|
+
}
|
|
137
|
+
// ---- Modules ------------------------------------------------------------------
|
|
138
|
+
const moduleBuildFiles = [];
|
|
139
|
+
const modules = [];
|
|
140
|
+
const applicationIdsByModule = [];
|
|
141
|
+
for (const module of detection.modules) {
|
|
142
|
+
const buildFileCandidates = [`${module.path}/build.gradle.kts`, `${module.path}/build.gradle`];
|
|
143
|
+
const buildFileRelPath = buildFileCandidates.find((candidate) => readTextSafe(path.join(resolvedRoot, candidate)) !== undefined);
|
|
144
|
+
if (!buildFileRelPath) {
|
|
145
|
+
modules.push({
|
|
146
|
+
path: module.path,
|
|
147
|
+
buildTypes: [],
|
|
148
|
+
sourceSetEvidence: [],
|
|
149
|
+
testSourceSetEvidence: [],
|
|
150
|
+
unsupportedExpressions: [],
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
moduleBuildFiles.push(buildFileRelPath);
|
|
155
|
+
const text = readTextSafe(path.join(resolvedRoot, buildFileRelPath)) ?? "";
|
|
156
|
+
const extracted = extractModuleGradleMetadata(text);
|
|
157
|
+
const javaSourceRoots = detection.javaSourceRoots.filter((p) => p.startsWith(`${module.path}/`));
|
|
158
|
+
const kotlinSourceRoots = detection.kotlinSourceRoots.filter((p) => p.startsWith(`${module.path}/`));
|
|
159
|
+
const unitTestRoots = detection.unitTestSourceRoots.filter((p) => p.startsWith(`${module.path}/`));
|
|
160
|
+
const instrumentedTestRoots = detection.instrumentedTestSourceRoots.filter((p) => p.startsWith(`${module.path}/`));
|
|
161
|
+
modules.push({
|
|
162
|
+
path: module.path,
|
|
163
|
+
buildFilePath: buildFileRelPath,
|
|
164
|
+
...extracted.info,
|
|
165
|
+
sourceSetEvidence: [...extracted.info.sourceSetEvidence, ...javaSourceRoots, ...kotlinSourceRoots].sort((a, b) => a.localeCompare(b)),
|
|
166
|
+
testSourceSetEvidence: [...unitTestRoots, ...instrumentedTestRoots].sort((a, b) => a.localeCompare(b)),
|
|
167
|
+
});
|
|
168
|
+
if (extracted.info.applicationId) {
|
|
169
|
+
applicationIdsByModule.push({ modulePath: module.path, value: extracted.info.applicationId, sourceFile: buildFileRelPath });
|
|
170
|
+
}
|
|
171
|
+
unsupportedExpressions.push(...extracted.info.unsupportedExpressions.map((e) => `${buildFileRelPath}: ${e}`));
|
|
172
|
+
}
|
|
173
|
+
// ---- Conflicts ------------------------------------------------------------------
|
|
174
|
+
const conflicts = [];
|
|
175
|
+
const applicationModulePaths = new Set(detection.applicationModules);
|
|
176
|
+
const applicationIdConflictEntries = applicationIdsByModule.filter((e) => applicationModulePaths.has(e.modulePath));
|
|
177
|
+
const distinctApplicationModuleIds = new Set(applicationIdConflictEntries.map((e) => e.value));
|
|
178
|
+
if (distinctApplicationModuleIds.size > 1) {
|
|
179
|
+
conflicts.push({
|
|
180
|
+
field: "applicationId",
|
|
181
|
+
modulePath: [...applicationModulePaths].sort((a, b) => a.localeCompare(b)).join(", "),
|
|
182
|
+
values: applicationIdConflictEntries.map((e) => ({ value: e.value, sourceFile: e.sourceFile })),
|
|
183
|
+
note: `${distinctApplicationModuleIds.size} distinct applicationId values found across application modules; no primary value was selected.`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// ---- Confidence ------------------------------------------------------------------
|
|
187
|
+
const hasAnyResolvedModuleMetadata = modules.some((m) => m.namespace || m.applicationId || m.minSdk || m.targetSdk || m.compileSdk);
|
|
188
|
+
const hasUnresolvedEvidence = unsupportedExpressions.length > 0 || conflicts.length > 0;
|
|
189
|
+
let metadataConfidence = "unknown";
|
|
190
|
+
if (hasAnyResolvedModuleMetadata) {
|
|
191
|
+
metadataConfidence = hasUnresolvedEvidence ? "medium" : "high";
|
|
192
|
+
}
|
|
193
|
+
else if (moduleBuildFiles.length > 0) {
|
|
194
|
+
metadataConfidence = "low";
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
wrapper,
|
|
198
|
+
settingsFiles: detection.gradleSettingsFiles,
|
|
199
|
+
rootBuildFiles: detection.rootBuildFiles,
|
|
200
|
+
moduleBuildFiles: moduleBuildFiles.sort((a, b) => a.localeCompare(b)),
|
|
201
|
+
rootProjectName,
|
|
202
|
+
pluginEvidence,
|
|
203
|
+
modules: modules.sort((a, b) => a.path.localeCompare(b.path)),
|
|
204
|
+
versionCatalogEvidence,
|
|
205
|
+
conflicts: conflicts.length > 0 ? conflicts : undefined,
|
|
206
|
+
metadataConfidence,
|
|
207
|
+
parseWarnings: [...new Set(parseWarnings)],
|
|
208
|
+
unsupportedExpressions: [...new Set(unsupportedExpressions)].sort((a, b) => a.localeCompare(b)),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Selects the application module deterministically only when exactly one
|
|
2
|
+
// application module exists (agents.txt Batch 4 section 7.14 — never invent
|
|
3
|
+
// a primary application module when several exist without a deterministic
|
|
4
|
+
// rule).
|
|
5
|
+
export function buildAndroidReleaseMetadataSummary(detection, gradle) {
|
|
6
|
+
const applicationModulePaths = detection.applicationModules;
|
|
7
|
+
const unresolvedFields = [];
|
|
8
|
+
if (applicationModulePaths.length === 0) {
|
|
9
|
+
return {
|
|
10
|
+
applicationModuleSelectionNote: "No application module was detected; release metadata is not available.",
|
|
11
|
+
buildTypes: [],
|
|
12
|
+
wrapperGradleVersion: gradle.wrapper.versionEvidence,
|
|
13
|
+
androidGradlePluginVersion: gradle.pluginEvidence.androidGradlePluginVersion,
|
|
14
|
+
kotlinAndroidPluginVersion: gradle.pluginEvidence.kotlinAndroidPluginVersion,
|
|
15
|
+
metadataConfidence: gradle.metadataConfidence,
|
|
16
|
+
unresolvedFields: [],
|
|
17
|
+
conflicts: gradle.conflicts ?? [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (applicationModulePaths.length > 1) {
|
|
21
|
+
return {
|
|
22
|
+
applicationModuleSelectionNote: `${applicationModulePaths.length} application modules were detected (${applicationModulePaths.join(", ")}); no single application module was selected deterministically.`,
|
|
23
|
+
buildTypes: [],
|
|
24
|
+
wrapperGradleVersion: gradle.wrapper.versionEvidence,
|
|
25
|
+
androidGradlePluginVersion: gradle.pluginEvidence.androidGradlePluginVersion,
|
|
26
|
+
kotlinAndroidPluginVersion: gradle.pluginEvidence.kotlinAndroidPluginVersion,
|
|
27
|
+
metadataConfidence: gradle.metadataConfidence,
|
|
28
|
+
unresolvedFields: [],
|
|
29
|
+
conflicts: gradle.conflicts ?? [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const applicationModulePath = applicationModulePaths[0];
|
|
33
|
+
const moduleInfo = gradle.modules.find((m) => m.path === applicationModulePath);
|
|
34
|
+
for (const [field, value] of [
|
|
35
|
+
["applicationId", moduleInfo?.applicationIdRaw],
|
|
36
|
+
["versionCode", moduleInfo?.versionCodeRaw],
|
|
37
|
+
["versionName", moduleInfo?.versionNameRaw],
|
|
38
|
+
["minSdk", moduleInfo?.minSdkRaw],
|
|
39
|
+
["targetSdk", moduleInfo?.targetSdkRaw],
|
|
40
|
+
["compileSdk", moduleInfo?.compileSdkRaw],
|
|
41
|
+
]) {
|
|
42
|
+
if (value)
|
|
43
|
+
unresolvedFields.push(`${field}: ${value}`);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
applicationModulePath,
|
|
47
|
+
applicationModuleSelectionNote: `Exactly one application module was detected (${applicationModulePath}); its metadata is used as the release metadata summary.`,
|
|
48
|
+
applicationId: moduleInfo?.applicationId,
|
|
49
|
+
namespace: moduleInfo?.namespace,
|
|
50
|
+
versionCode: moduleInfo?.versionCode,
|
|
51
|
+
versionName: moduleInfo?.versionName,
|
|
52
|
+
minSdk: moduleInfo?.minSdk,
|
|
53
|
+
targetSdk: moduleInfo?.targetSdk,
|
|
54
|
+
compileSdk: moduleInfo?.compileSdk,
|
|
55
|
+
buildTypes: moduleInfo?.buildTypes ?? [],
|
|
56
|
+
wrapperGradleVersion: gradle.wrapper.versionEvidence,
|
|
57
|
+
androidGradlePluginVersion: gradle.pluginEvidence.androidGradlePluginVersion,
|
|
58
|
+
kotlinAndroidPluginVersion: gradle.pluginEvidence.kotlinAndroidPluginVersion,
|
|
59
|
+
composeEnabled: moduleInfo?.composeEnabled,
|
|
60
|
+
metadataConfidence: gradle.metadataConfidence,
|
|
61
|
+
unresolvedFields,
|
|
62
|
+
conflicts: gradle.conflicts ?? [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.0 Batch 4 — bounded settings.gradle(.kts) metadata beyond Batch 2's
|
|
3
|
+
// module-include parsing.
|
|
4
|
+
//
|
|
5
|
+
// Only extracts a direct `rootProject.name = "..."` / `rootProject.name "..."`
|
|
6
|
+
// literal and detects included-build/plugin-management/dependency-resolution
|
|
7
|
+
// presence as evidence — never evaluates the referenced build's contents.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
const ROOT_PROJECT_NAME_PATTERN = /rootProject\.name\s*=?\s*['"]([^'"]+)['"]/;
|
|
10
|
+
export function parseSettingsMetadata(text) {
|
|
11
|
+
const rootProjectName = text.match(ROOT_PROJECT_NAME_PATTERN)?.[1];
|
|
12
|
+
const includedBuilds = [...text.matchAll(/includeBuild\(\s*['"]([^'"]+)['"]\s*\)/g)].map((m) => m[1]);
|
|
13
|
+
return {
|
|
14
|
+
rootProjectName,
|
|
15
|
+
hasPluginManagement: /\bpluginManagement\s*\{/.test(text),
|
|
16
|
+
hasDependencyResolutionManagement: /\bdependencyResolutionManagement\s*\{/.test(text),
|
|
17
|
+
hasVersionCatalogDeclaration: /\bversionCatalogs\s*\{/.test(text) || /\bcreate\(\s*['"]libs['"]/.test(text),
|
|
18
|
+
includedBuilds: includedBuilds.sort((a, b) => a.localeCompare(b)),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { runSecurityCommand } from "../../../../securityValidation/commandRunner.js";
|
|
2
|
+
export function createRealGradleCommandExecutor() {
|
|
3
|
+
return (plan) => runSecurityCommand({
|
|
4
|
+
command: plan.wrapperExecutablePath,
|
|
5
|
+
args: plan.args,
|
|
6
|
+
cwd: plan.cwd,
|
|
7
|
+
timeoutMs: plan.timeoutMs,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.0 Batch 4 — per-operation Gradle validation check-result construction
|
|
3
|
+
// (agents.txt Batch 4 section 7.26).
|
|
4
|
+
//
|
|
5
|
+
// Status semantics differ slightly by operation:
|
|
6
|
+
// - wrapper-version: nonzero/spawn-failure -> "error" (this is the
|
|
7
|
+
// simplest possible invocation; a failure here is an orchestration
|
|
8
|
+
// problem, not a build/environment nuance).
|
|
9
|
+
// - tasks: nonzero/spawn-failure -> "inconclusive" (configuration or
|
|
10
|
+
// environment prevented task discovery, not a security-relevant
|
|
11
|
+
// failure).
|
|
12
|
+
// - assemble-debug / unit-test-debug / lint-debug: spawn-failure ->
|
|
13
|
+
// "inconclusive" (environment incomplete); nonzero exit with a real
|
|
14
|
+
// process result -> "failed" (a genuine build/test/lint failure is
|
|
15
|
+
// validation evidence, never a fabricated SecurityFinding — section
|
|
16
|
+
// 7.27).
|
|
17
|
+
// A timed-out operation is always "inconclusive", never "passed" or
|
|
18
|
+
// silently "error" (section 7.22).
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const MAX_OUTPUT_SUMMARY_LENGTH = 4000;
|
|
21
|
+
function boundedSummary(text) {
|
|
22
|
+
if (text.length <= MAX_OUTPUT_SUMMARY_LENGTH)
|
|
23
|
+
return { summary: text, truncated: false };
|
|
24
|
+
return { summary: text.slice(0, MAX_OUTPUT_SUMMARY_LENGTH), truncated: true };
|
|
25
|
+
}
|
|
26
|
+
function statusForResult(operationId, result) {
|
|
27
|
+
if (result.timedOut)
|
|
28
|
+
return "inconclusive";
|
|
29
|
+
if (result.exitCode === null) {
|
|
30
|
+
return operationId === "wrapper-version" ? "error" : "inconclusive";
|
|
31
|
+
}
|
|
32
|
+
if (result.exitCode === 0)
|
|
33
|
+
return "passed";
|
|
34
|
+
if (operationId === "wrapper-version")
|
|
35
|
+
return "error";
|
|
36
|
+
if (operationId === "tasks")
|
|
37
|
+
return "inconclusive";
|
|
38
|
+
return "failed";
|
|
39
|
+
}
|
|
40
|
+
function confidenceForStatus(status) {
|
|
41
|
+
switch (status) {
|
|
42
|
+
case "passed":
|
|
43
|
+
case "failed":
|
|
44
|
+
return "high";
|
|
45
|
+
case "inconclusive":
|
|
46
|
+
return "medium";
|
|
47
|
+
default:
|
|
48
|
+
return "unknown";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function buildGradleOperationCheckResult(plan, result, mutation) {
|
|
52
|
+
const status = statusForResult(plan.operationId, result);
|
|
53
|
+
const confidence = confidenceForStatus(status);
|
|
54
|
+
const stdoutBound = boundedSummary(result.stdout);
|
|
55
|
+
const stderrBound = boundedSummary(result.stderr);
|
|
56
|
+
const warnings = [];
|
|
57
|
+
if (result.timedOut)
|
|
58
|
+
warnings.push(`Operation timed out after ${plan.timeoutMs}ms.`);
|
|
59
|
+
if (result.exitCode === null && !result.timedOut)
|
|
60
|
+
warnings.push("Command could not be spawned or resolved (see stderr evidence).");
|
|
61
|
+
if (stdoutBound.truncated)
|
|
62
|
+
warnings.push("stdout was truncated for this result.");
|
|
63
|
+
if (stderrBound.truncated)
|
|
64
|
+
warnings.push("stderr was truncated for this result.");
|
|
65
|
+
if (!mutation.comparable)
|
|
66
|
+
warnings.push(`Target mutation evidence unavailable: ${mutation.reason ?? "unknown reason"}`);
|
|
67
|
+
if (mutation.unexpectedChanges.length > 0) {
|
|
68
|
+
warnings.push(`Unexpected target change(s) observed after this operation: ${mutation.unexpectedChanges.join(", ")}`);
|
|
69
|
+
}
|
|
70
|
+
const boundedCommand = {
|
|
71
|
+
...result,
|
|
72
|
+
stdout: stdoutBound.summary,
|
|
73
|
+
stderr: stderrBound.summary,
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
id: `android-gradle-${plan.operationId}`,
|
|
77
|
+
category: "android-gradle",
|
|
78
|
+
title: `Gradle operation: ${plan.operationId}`,
|
|
79
|
+
status,
|
|
80
|
+
requirementLevel: "optional",
|
|
81
|
+
ran: true,
|
|
82
|
+
skipped: false,
|
|
83
|
+
evidence: [
|
|
84
|
+
`exitCode=${result.exitCode ?? "null"}`,
|
|
85
|
+
`durationMs=${result.durationMs}`,
|
|
86
|
+
`expectedGeneratedPaths=${plan.expectedGeneratedPaths.join(",") || "(none)"}`,
|
|
87
|
+
...mutation.expectedGeneratedChanges.map((p) => `generatedOutput=${p}`),
|
|
88
|
+
],
|
|
89
|
+
findings: [],
|
|
90
|
+
warnings,
|
|
91
|
+
errors: result.exitCode === null && !result.timedOut ? [stderrBound.summary || "Command could not be spawned or resolved."] : [],
|
|
92
|
+
durationMs: result.durationMs,
|
|
93
|
+
command: boundedCommand,
|
|
94
|
+
sourcePaths: [plan.wrapperExecutablePath],
|
|
95
|
+
confidence,
|
|
96
|
+
environmentRequirements: plan.environmentLimitations,
|
|
97
|
+
targetModificationObserved: mutation.comparable ? mutation.unexpectedChanges.length > 0 : undefined,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.4.0 Batch 4 — allowlisted optional Gradle validation operations
|
|
3
|
+
// (agents.txt Batch 4 section 7.17).
|
|
4
|
+
//
|
|
5
|
+
// This is the ONLY place operation-to-argument mappings are defined. The
|
|
6
|
+
// planner (planner.ts) rejects any operation id not in ALLOWLISTED_OPERATION_IDS
|
|
7
|
+
// before a command is ever constructed — arbitrary task strings are never
|
|
8
|
+
// accepted from a caller.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export const ALLOWLISTED_OPERATION_IDS = ["wrapper-version", "tasks", "assemble-debug", "unit-test-debug", "lint-debug"];
|
|
11
|
+
export function isAllowlistedOperationId(value) {
|
|
12
|
+
return ALLOWLISTED_OPERATION_IDS.includes(value);
|
|
13
|
+
}
|
|
14
|
+
// Conservative, bounded timeouts (agents.txt section 7.22). No retries, no
|
|
15
|
+
// unbounded execution — a timed-out operation is terminated and reported
|
|
16
|
+
// inconclusive/error, never passed.
|
|
17
|
+
const SHORT_TIMEOUT_MS = 30_000;
|
|
18
|
+
const MODERATE_TIMEOUT_MS = 120_000;
|
|
19
|
+
const LONG_TIMEOUT_MS = 600_000;
|
|
20
|
+
// Safety flags applied to every operation: --no-daemon avoids leaving a
|
|
21
|
+
// background Gradle daemon running after this process exits, --console=plain
|
|
22
|
+
// keeps output parseable, and --offline prevents this batch from
|
|
23
|
+
// intentionally triggering network access (agents.txt section 7.18) — the
|
|
24
|
+
// tradeoff is that operations needing uncached dependencies will fail or
|
|
25
|
+
// report inconclusive rather than silently downloading them.
|
|
26
|
+
const SAFETY_FLAGS = ["--no-daemon", "--console=plain"];
|
|
27
|
+
const OFFLINE_SAFETY_FLAGS = [...SAFETY_FLAGS, "--offline"];
|
|
28
|
+
export const GRADLE_OPERATIONS = {
|
|
29
|
+
"wrapper-version": {
|
|
30
|
+
id: "wrapper-version",
|
|
31
|
+
args: ["--version"],
|
|
32
|
+
timeoutMs: SHORT_TIMEOUT_MS,
|
|
33
|
+
mayCreateBuildOutputs: true, // Gradle creates/updates .gradle/ on first invocation.
|
|
34
|
+
description: "Reports the Gradle version the wrapper would use, without building anything.",
|
|
35
|
+
},
|
|
36
|
+
tasks: {
|
|
37
|
+
id: "tasks",
|
|
38
|
+
gradleTaskName: "tasks",
|
|
39
|
+
args: ["tasks", "--all", ...OFFLINE_SAFETY_FLAGS],
|
|
40
|
+
timeoutMs: MODERATE_TIMEOUT_MS,
|
|
41
|
+
mayCreateBuildOutputs: true,
|
|
42
|
+
description: "Lists available Gradle tasks so later operations can be skipped safely when unavailable.",
|
|
43
|
+
},
|
|
44
|
+
"assemble-debug": {
|
|
45
|
+
id: "assemble-debug",
|
|
46
|
+
gradleTaskName: "assembleDebug",
|
|
47
|
+
args: ["assembleDebug", ...OFFLINE_SAFETY_FLAGS],
|
|
48
|
+
timeoutMs: LONG_TIMEOUT_MS,
|
|
49
|
+
mayCreateBuildOutputs: true,
|
|
50
|
+
description: "Assembles the debug variant (no signing, no release build, no publishing).",
|
|
51
|
+
},
|
|
52
|
+
"unit-test-debug": {
|
|
53
|
+
id: "unit-test-debug",
|
|
54
|
+
gradleTaskName: "testDebugUnitTest",
|
|
55
|
+
args: ["testDebugUnitTest", ...OFFLINE_SAFETY_FLAGS],
|
|
56
|
+
timeoutMs: LONG_TIMEOUT_MS,
|
|
57
|
+
mayCreateBuildOutputs: true,
|
|
58
|
+
description: "Runs local (non-instrumented) debug unit tests.",
|
|
59
|
+
},
|
|
60
|
+
"lint-debug": {
|
|
61
|
+
id: "lint-debug",
|
|
62
|
+
gradleTaskName: "lintDebug",
|
|
63
|
+
args: ["lintDebug", ...OFFLINE_SAFETY_FLAGS],
|
|
64
|
+
timeoutMs: LONG_TIMEOUT_MS,
|
|
65
|
+
mayCreateBuildOutputs: true,
|
|
66
|
+
description: "Runs Android Lint for the debug variant. Lint output is not parsed into security findings in this batch.",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveWithinRoot } from "../../../../core/pathSafety.js";
|
|
4
|
+
import { GRADLE_OPERATIONS, isAllowlistedOperationId } from "./operations.js";
|
|
5
|
+
function wrapperScriptNameForPlatform(platform) {
|
|
6
|
+
return platform === "win32" ? "gradlew.bat" : "gradlew";
|
|
7
|
+
}
|
|
8
|
+
export function buildGradleCommandPlan(operationId, targetRoot, detection, platform = process.platform) {
|
|
9
|
+
if (!isAllowlistedOperationId(operationId)) {
|
|
10
|
+
return { rejected: true, reason: `Unknown or unsupported Gradle operation id: "${operationId}". Only allowlisted operations may be planned.` };
|
|
11
|
+
}
|
|
12
|
+
const resolvedRoot = path.resolve(targetRoot);
|
|
13
|
+
const wrapperScriptName = wrapperScriptNameForPlatform(platform);
|
|
14
|
+
let wrapperExecutablePath;
|
|
15
|
+
try {
|
|
16
|
+
wrapperExecutablePath = resolveWithinRoot(resolvedRoot, wrapperScriptName);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { rejected: true, reason: `Wrapper path escapes the target root: ${wrapperScriptName}` };
|
|
20
|
+
}
|
|
21
|
+
if (!fs.existsSync(wrapperExecutablePath)) {
|
|
22
|
+
return {
|
|
23
|
+
rejected: true,
|
|
24
|
+
reason: `No ${wrapperScriptName} was found at the target root. Optional Gradle validation requires a Gradle wrapper checked into the target for this platform.`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const definition = GRADLE_OPERATIONS[operationId];
|
|
28
|
+
const expectedGeneratedPaths = definition.mayCreateBuildOutputs
|
|
29
|
+
? [".gradle/", "build/", ...detection.modules.map((m) => `${m.path}/build/`)]
|
|
30
|
+
: [];
|
|
31
|
+
const environmentLimitations = [
|
|
32
|
+
"Requires a local or downloadable Gradle distribution matching the wrapper version — this planner does not verify one is available.",
|
|
33
|
+
"Requires a Java runtime on PATH — this planner does not verify Java availability.",
|
|
34
|
+
];
|
|
35
|
+
if (definition.args.includes("--offline")) {
|
|
36
|
+
environmentLimitations.push("Runs with --offline; tasks needing uncached dependencies will fail or report inconclusive rather than downloading them.");
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
rejected: false,
|
|
40
|
+
operationId,
|
|
41
|
+
wrapperExecutablePath,
|
|
42
|
+
args: definition.args,
|
|
43
|
+
cwd: resolvedRoot,
|
|
44
|
+
timeoutMs: definition.timeoutMs,
|
|
45
|
+
mayCreateBuildOutputs: definition.mayCreateBuildOutputs,
|
|
46
|
+
expectedGeneratedPaths,
|
|
47
|
+
environmentLimitations,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { GRADLE_OPERATIONS } from "./operations.js";
|
|
4
|
+
import { buildGradleCommandPlan } from "./planner.js";
|
|
5
|
+
import { createRealGradleCommandExecutor } from "./executor.js";
|
|
6
|
+
import { buildGradleOperationCheckResult } from "./operationCheckResult.js";
|
|
7
|
+
import { buildTargetMutationReport, captureTargetSnapshot } from "./targetMutation.js";
|
|
8
|
+
import { isGradleTaskAvailable, parseGradleTaskNames } from "./taskListParser.js";
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// v0.4.0 Batch 4 — optional Gradle validation orchestrator (agents.txt Batch
|
|
11
|
+
// 4 sections 7.17, 7.20, 7.24, 7.26).
|
|
12
|
+
//
|
|
13
|
+
// Static metadata collection (readAndroidGradleMetadata.ts) never calls this
|
|
14
|
+
// module. This orchestrator only ever runs a Gradle command when
|
|
15
|
+
// `enabled: true` is passed explicitly — every other code path returns a
|
|
16
|
+
// "not-run" result without touching the filesystem beyond a target
|
|
17
|
+
// git-status snapshot. Task-gated operations (assemble/test/lint) are
|
|
18
|
+
// skipped, not attempted, unless task availability was already discovered
|
|
19
|
+
// via the "tasks" operation earlier in the same call (or the caller opts in
|
|
20
|
+
// via allowWithoutTaskDiscovery).
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
const TASK_GATED_OPERATIONS = new Set(["assemble-debug", "unit-test-debug", "lint-debug"]);
|
|
23
|
+
function notRunResult(operationId) {
|
|
24
|
+
return {
|
|
25
|
+
id: `android-gradle-${operationId}`,
|
|
26
|
+
category: "android-gradle",
|
|
27
|
+
title: `Gradle operation: ${operationId}`,
|
|
28
|
+
status: "not-run",
|
|
29
|
+
requirementLevel: "optional",
|
|
30
|
+
ran: false,
|
|
31
|
+
skipped: false,
|
|
32
|
+
evidence: [],
|
|
33
|
+
findings: [],
|
|
34
|
+
warnings: [],
|
|
35
|
+
errors: [],
|
|
36
|
+
sourcePaths: [],
|
|
37
|
+
confidence: "unknown",
|
|
38
|
+
environmentRequirements: [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function skippedResult(operationId, reason, missingCapability) {
|
|
42
|
+
return {
|
|
43
|
+
id: `android-gradle-${operationId}`,
|
|
44
|
+
category: "android-gradle",
|
|
45
|
+
title: `Gradle operation: ${operationId}`,
|
|
46
|
+
status: "skipped",
|
|
47
|
+
requirementLevel: "optional",
|
|
48
|
+
ran: false,
|
|
49
|
+
skipped: true,
|
|
50
|
+
skipInfo: {
|
|
51
|
+
checkId: `android-gradle-${operationId}`,
|
|
52
|
+
reason,
|
|
53
|
+
requirementLevel: "optional",
|
|
54
|
+
missingCapability,
|
|
55
|
+
verdictImpact: "optional Gradle validation evidence is unavailable for this operation",
|
|
56
|
+
recommendedNextAction: "Re-run with the required precondition satisfied, or accept reduced evidence.",
|
|
57
|
+
},
|
|
58
|
+
evidence: [],
|
|
59
|
+
findings: [],
|
|
60
|
+
warnings: [],
|
|
61
|
+
errors: [],
|
|
62
|
+
sourcePaths: [],
|
|
63
|
+
confidence: "unknown",
|
|
64
|
+
environmentRequirements: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function hasGitDirectory(targetRoot) {
|
|
68
|
+
try {
|
|
69
|
+
return fs.existsSync(path.join(targetRoot, ".git"));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export async function runOptionalGradleValidation(options) {
|
|
76
|
+
if (!options.enabled) {
|
|
77
|
+
return options.operationIds.map((id) => notRunResult(id));
|
|
78
|
+
}
|
|
79
|
+
const executor = options.executor ?? createRealGradleCommandExecutor();
|
|
80
|
+
const platform = options.platform ?? process.platform;
|
|
81
|
+
const resolvedRoot = path.resolve(options.targetRoot);
|
|
82
|
+
const hasGit = hasGitDirectory(resolvedRoot);
|
|
83
|
+
let discoveredTaskNames;
|
|
84
|
+
const results = [];
|
|
85
|
+
for (const operationId of options.operationIds) {
|
|
86
|
+
if (TASK_GATED_OPERATIONS.has(operationId)) {
|
|
87
|
+
if (!discoveredTaskNames && !options.allowWithoutTaskDiscovery) {
|
|
88
|
+
results.push(skippedResult(operationId, "Task availability was not discovered (the 'tasks' operation did not run successfully first) and allowWithoutTaskDiscovery was not set.", "gradle-task-discovery"));
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const gradleTaskName = GRADLE_OPERATIONS[operationId].gradleTaskName;
|
|
92
|
+
if (discoveredTaskNames && !isGradleTaskAvailable(discoveredTaskNames, gradleTaskName)) {
|
|
93
|
+
results.push(skippedResult(operationId, `Task "${gradleTaskName}" is not available in this project.`, "gradle-task"));
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const plan = buildGradleCommandPlan(operationId, resolvedRoot, options.detection, platform);
|
|
98
|
+
if (plan.rejected) {
|
|
99
|
+
results.push(skippedResult(operationId, plan.reason, "gradle-wrapper"));
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const before = captureTargetSnapshot(resolvedRoot, hasGit);
|
|
103
|
+
const commandResult = await executor(plan);
|
|
104
|
+
const after = captureTargetSnapshot(resolvedRoot, hasGit);
|
|
105
|
+
const mutation = buildTargetMutationReport(before, after);
|
|
106
|
+
const checkResult = buildGradleOperationCheckResult(plan, commandResult, mutation);
|
|
107
|
+
results.push(checkResult);
|
|
108
|
+
if (operationId === "tasks" && checkResult.status === "passed") {
|
|
109
|
+
discoveredTaskNames = parseGradleTaskNames(commandResult.stdout);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return results;
|
|
113
|
+
}
|