Android¶
Android checks: Gradle/SDK config, manifest, ProGuard/R8, dependency hygiene, packaging, and store requirements.
37 checks.
android-allow-backup¶
Severity: Medium
Unscoped backups can copy sensitive app data (auth tokens, databases, shared preferences) to adb or cloud backups, where it is exposed outside the app sandbox.
How to fix: Set android:allowBackup="false" if the app holds sensitive data, or define android:dataExtractionRules (Android 12+) / android:fullBackupContent to exclude secrets from backups.
android-bundle-missing¶
Severity: Medium
Android App Bundles (AAB) reduce download size by 15-30% on average through dynamic delivery of resources and native libraries.
How to fix: Replace assembleRelease with bundleRelease in CI, or add a bundleRelease step alongside. Google Play requires AAB for new apps.
android-cleartext-traffic¶
Severity: Medium
Allowing cleartext traffic exposes the app to man-in-the-middle attacks and data interception.
How to fix: Set android:usesCleartextTraffic="false" or use a network security config to allow cleartext only for specific domains during development.
android-debuggable-manifest¶
Severity: High
A debuggable app exposes internal state, allows attaching debuggers, and bypasses some security restrictions — a serious security risk in production.
How to fix: Remove the android:debuggable attribute from the manifest. Debug mode is controlled by the build type in Gradle.
android-duplicate-async-stack¶
Severity: Low
Overlapping async stacks (RxJava + Coroutines + AsyncTask) add weight and cognitive load.
How to fix: Consolidate on one concurrency approach (Coroutines/Flow is the modern default).
android-duplicate-image-stack¶
Severity: Low
Multiple image-loading libraries (Glide, Coil, Picasso, Fresco) duplicate caches and bloat the app.
How to fix: Pick one image-loading library and remove the others.
android-duplicate-serialization-stack¶
Severity: Low
Multiple JSON/serialization libraries (Gson, Moshi, kotlinx.serialization) add method count, size, and inconsistency.
How to fix: Standardize on a single serialization library across modules.
android-dynamic-dependency-version¶
Severity: Medium
Dynamic versions can introduce unexpected breaking changes and make builds non-deterministic, complicating debugging and rollbacks.
How to fix: Replace dynamic versions with pinned versions (e.g. "2.10.1" instead of "2.+").
android-excessive-permissions¶
Severity: Low
Excessive permissions erode user trust, may trigger additional Play Store review, and increase the attack surface.
How to fix: Use tools:node="remove" in the app manifest to strip SDK-contributed permissions you do not need, and verify the final merged set with aapt dump permissions app.apk.
android-flutter-uncompressed-native-libs¶
Severity: Medium
Since Android 6.0+ the system can load .so files directly from the APK. Extracting them wastes storage and increases install size.
How to fix: Remove android:extractNativeLibs="true" from AndroidManifest.xml or set it to false. Ensure minSdk >= 23 and .so files are page-aligned (default with AGP 3.6+).
android-gradle-overview¶
Severity: Info
Understanding the Gradle configuration helps identify SDK targeting issues and build setup problems.
How to fix: Review SDK versions to ensure they align with your target audience and platform requirements.
android-hardcoded-signing¶
Severity: High
Committing signing credentials leaks release-signing material into version control. Anyone with repo access (or git history) can sign builds that impersonate your app.
How to fix: Move signing credentials to a git-ignored keystore.properties file or CI environment variables, and read them via System.getenv("...") or project.findProperty("...") in signingConfigs.
android-high-risk-restricted-permissions¶
Severity: Medium
Play-restricted permissions (SMS/Call-Log, QUERY_ALL_PACKAGES, MANAGE_EXTERNAL_STORAGE, AccessibilityService, …) require a Permissions Declaration and a core-feature justification. Shipping one the app does not genuinely need risks app removal — and this is stricter than the count-based excessive-permissions heuristic.
How to fix: Remove any of these your app does not require as a core feature. For the rest, file the Play Console Permissions Declaration and keep the justification current.
android-incomplete-density-buckets¶
Severity: Low
A bitmap supplied for a single density is up/down-scaled by the OS on all other devices, causing blurry or jagged rendering. Providing the right buckets (or a vector) keeps assets crisp.
How to fix: Provide the asset across the standard density buckets (mdpi/hdpi/xhdpi/xxhdpi/xxxhdpi), or convert it to a vector drawable (VectorDrawable) which scales without per-density variants.
android-insecure-exported-component¶
Severity: Medium
Exported components without a guarding permission can be invoked by any other app on the device, enabling component-injection, unauthorized access to internal functionality, and data leakage.
How to fix: Set android:exported="false" for components not intended for external apps. If a component must be exposed, gate it with a custom
android-jetifier-unneeded¶
Severity: Low
When the whole graph is already AndroidX, Jetifier runs on every build and produces no useful change — a silent, recurring waste of build time. (Verify no transitive dependency still ships Support Library bytecode before removing it; transitive deps are not visible to static analysis.)
How to fix: Remove android.enableJetifier=true from gradle.properties (run a build first to confirm no dependency still requires it).
android-legacy-external-storage¶
Severity: Medium
requestLegacyExternalStorage is a temporary opt-out that no longer works on Android 11 (API 30)+. Its presence indicates the app may not handle scoped storage correctly.
How to fix: Migrate file access to the scoped storage APIs (MediaStore, SAF) and remove the requestLegacyExternalStorage attribute.
android-legacy-multidex-unneeded¶
Severity: Low
Carrying the legacy multidex path at minSdk 21+ adds an unused dependency and build step with no benefit. Removing it trims the dependency graph and clarifies the build config.
How to fix: Remove multiDexEnabled true and the androidx.multidex / com.android.support:multidex dependency — multidex is automatic at minSdk 21+.
android-legacy-support-library¶
Severity: Medium
The Android Support Library is no longer maintained. AndroidX provides bug fixes, new features, and is required by most modern libraries.
How to fix: Use the Android Studio "Migrate to AndroidX" refactoring tool to replace com.android.support dependencies with their AndroidX equivalents.
android-lint-checks-disabled¶
Severity: Low
Lint checks for release builds catch issues like missing translations, unused resources, and accessibility problems before they reach production.
How to fix: Remove "checkReleaseBuilds false" from the lintOptions/lint block to re-enable lint checks for release builds.
android-low-min-sdk¶
Severity: Low
Supporting very old Android versions increases maintenance burden and limits modern API usage for a negligible share of active devices. This is advisory — Play does not enforce a minimum.
How to fix: Consider raising minSdk to at least … to drop legacy support.
android-low-target-sdk¶
Severity: High–Medium
Google Play blocks uploads (new apps and updates) that target below the required API level — this is a hard publication blocker, not a style issue.
How to fix: Update targetSdk to … or higher and address behavioral changes for the new API levels.
android-missing-minify¶
Severity: Medium
Without code minification, the release APK/AAB contains unused code, increasing bundle size and making reverse engineering easier.
How to fix: Add "minifyEnabled true" (or "isMinifyEnabled = true" in Kotlin DSL) to the release build type and configure ProGuard/R8 rules.
android-missing-proguard¶
Severity: Medium
Without custom ProGuard rules, R8 may strip classes needed at runtime (e.g., via reflection), causing crashes.
How to fix: Add a proguard-rules.pro file next to the build.gradle with appropriate keep rules for your project.
android-missing-shrink-resources¶
Severity: Low
Without resource shrinking, unused resources remain in the APK, unnecessarily increasing bundle size.
How to fix: Add "shrinkResources true" (or "isShrinkResources = true" in Kotlin DSL) to the release build type. Note: this requires minifyEnabled to be true.
android-mixed-kapt-ksp¶
Severity: Medium
Running both kapt and ksp in the same module doubles annotation processing work, significantly slowing incremental builds.
How to fix: Migrate all annotation processors in these modules from kapt to ksp where ksp plugins are available.
android-network-security-config-cleartext¶
Severity: Medium–Low
A base-config allowing cleartext exposes all app traffic to man-in-the-middle attacks and interception, exactly the risk the manifest flag is meant to close.
How to fix: Set cleartextTrafficPermitted="false" in base-config (or drop the attribute — false is the default on API 28+). Allow cleartext only per-domain via
android-non-transitive-r-class¶
Severity: Low
Transitive R classes copy every dependency's resource IDs into every module's R class — larger DEX and more R-class recompilation on every incremental build. Non-transitive R (the AGP 8.0+ default) gives each module only its own IDs, trimming size and speeding builds.
How to fix: Add android.nonTransitiveRClass=true to the root gradle.properties and run the AGP "Refactor → Migrate to Non-Transitive R Classes" tool to fix any newly-qualified R references.
android-nonfinal-resids-disabled¶
Severity: Low
Non-final R IDs let AGP skip re-generating and re-compiling R classes for unchanged library modules, speeding incremental builds. Explicitly disabling it silently gives that up.
How to fix: Remove android.nonFinalResIds=false (or set it to true) to use non-final R-class IDs.
android-oversized-resource¶
Severity:
Large resource files bloat the APK/AAB, increase download time, and consume more device storage.
How to fix: Compress the resource, convert images to WebP, or use vector drawables where possible.
android-proguard-excessive-dontwarn¶
Severity: Low
Excessive -dontwarn rules suppress warnings that could indicate missing classes or broken dependencies at runtime.
How to fix: Cross-reference suppressed warnings with actual dependencies via ./gradlew :app:dependencies and remove stale -dontwarn entries. For library-scoped rules, use consumer-rules.pro (AGP 4.0+) so each library ships its own ProGuard config.
android-proguard-keep-all¶
Severity: High
A blanket keep rule negates the benefits of R8/ProGuard — the app remains fully decompilable and the APK size is not reduced.
How to fix: Replace the blanket keep rule with specific keep rules for classes that need to be preserved (e.g., models used with reflection, JNI interfaces).
android-repository-confusion-risk¶
Severity: Medium
JCenter is read-only (since 2021) and Bintray is gone. Keeping them in the repository list is a dependency-confusion vector: the abandoned coordinate namespace can be re-registered and served by an attacker, and builds silently fall back to whichever repo answers first. Nothing fails loudly, so it lingers.
How to fix: Remove jcenter()/Bintray from the repositories block. Resolve every dependency from mavenCentral() / google() (or your own trusted repo), and pin sources so coordinates cannot silently move between repositories.
android-snapshot-dependency¶
Severity: High
SNAPSHOT versions can change at any time without notice, causing unpredictable build failures and runtime issues in production.
How to fix: Replace SNAPSHOT versions with stable release versions.
android-split-abi-missing¶
Severity: Medium
Without ABI splits, the APK includes native libraries for all architectures, increasing download size by 2-4x for each user.
How to fix: Add ABI splits in build.gradle or migrate to Android App Bundle (AAB) which handles this automatically.
android-unsafe-tls-validation¶
Severity: High
An empty checkServerTrusted or a HostnameVerifier that returns true turns off certificate/hostname validation — every proxy or attacker on the path can transparently MITM the connection and read/modify traffic. The build succeeds and the app works, so the hole is invisible in normal testing.
How to fix: Remove the custom TrustManager / HostnameVerifier and use the platform defaults. If you must pin a self-signed or private-CA cert, use a proper certificate-pinning setup (e.g. OkHttp CertificatePinner or a network-security-config trust anchor) instead of trusting everything.
android-webview-debug-flags¶
Severity: Medium
setWebContentsDebuggingEnabled(true) leaves production WebViews open to remote DOM inspection; setAllowUniversalAccessFromFileURLs/setAllowFileAccessFromFileURLs(true) let file:// pages read any origin or local file — a data-exfiltration path. These stay green in the build and are easy to miss.
How to fix: Gate setWebContentsDebuggingEnabled behind BuildConfig.DEBUG, and remove setAllowUniversalAccessFromFileURLs / setAllowFileAccessFromFileURLs (default false) unless a specific, trusted flow requires them.