Sparround

App hardening: root detection, Play Integrity, R8, FLAG_SECURE

Hardening is layered defense for running in a hostile environment (root, emulators, hooking frameworks):

  • Play Integrity API — Google's attestation service: a server-verifiable verdict on device integrity (MEETS_DEVICE_INTEGRITY), app authenticity (official APK, installed from Play) and licensing. Unlike local root checks, the verdict arrives signed from Google's servers and is hard to fake in-app.
  • Root/tamper detection — local checks (su binaries, Magisk traces, the debuggable flag, emulator markers): quickly bypassed, but they raise attack cost. The response policy is bank-specific: full block / restricted mode / a server signal.
  • R8/ProGuard — minification + obfuscation: smaller code, meaningless names — reverse engineering gets harder (never impossible). Keep mapping.txt for crash deobfuscation.
  • FLAG_SECURE — bans screenshots and screen recording; hides content in recents. Standard on banking screens.
  • Debug protectionsandroid:debuggable false in release (the default), debugger-attach checks.
kotlin
// Həssas ekranlarda screenshot/recording qadağası
class TransactionActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window.setFlags(
            WindowManager.LayoutParams.FLAG_SECURE,
            WindowManager.LayoutParams.FLAG_SECURE
        )
    }
}

// build.gradle.kts — release hardening
android {
    buildTypes {
        release {
            isMinifyEnabled = true          // R8: kod kiçilir + obfuscate
            isShrinkResources = true        // istifadə olunmayan resurslar silinir
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

FLAG_SECURE and R8 configuration

The balance question comes: "Is fully blocking rooted devices right?" A good answer shows the trade-off: full block → simple for security audits, but legitimate power users are lost and detection bypasses become an arms race; risk-based → viewing works on detected root while money movement is restricted/step-upped, with a signal to the server risk score. The decision belongs to the risk committee, not engineering alone — saying so shows you understand the fintech context.

🛠 Practice task

Harden a release build and verify the result:

  • Build a release APK with isMinifyEnabled = true, confirm mapping.txt appears; open the APK in a decompiler and see the renamed symbols.
  • Add FLAG_SECURE to a sensitive screen and try taking a screenshot.
  • Check how the content looks in the recents screen.
  • Test a reflection-based DTO in release without keep rules — reproduce the "works in debug, breaks in release" case.

Done when: you have filled in the layered-defense table for your own app.

📚 Sources and documentation