Block rooted and jailbroken devices in Flutter
If your app handles payments, health data, or anything else worth stealing, a rooted phone is a problem. Root access lets other apps read your storage, hook your code, and change what your app sees. Most teams agree the
If your app handles payments, health data, or anything else worth stealing, a rooted phone is a problem. Root access lets other apps read your storage, hook your code, and change what your app sees.
Most teams agree they should check for this. Then the check ends up scattered across main(), a splash screen, and a few platform channels, with no clear rule for when to block and when to just log.
I wanted one place for it. So I wrote device_integrity_guard.
What it does
It runs a set of checks before runApp, and then either starts your app or shows a block screen.
- Root on Android: known
subinaries, root apps, and a writable system folder - Jailbreak on iOS: known jailbreak files and write access outside the app sandbox
- Emulators, from the device info Android and iOS report
- An attached debugger
A policy decides what each signal means. You can block it, only log it, or allow it, and you can switch all of it off in debug builds.
Try it
flutter pub add device_integrity_guard
Then wrap your app in main():
Future<void> main() async {
final guard = DeviceIntegrityGuard(
policy: DeviceIntegrityPolicy(
allowDebugMode: !kReleaseMode,
blockOnCompromise: kReleaseMode,
),
onViolation: (result) => debugPrint('${result.blockedSignals}'),
);
await runGuardedApp(app: const MyApp(), guard: guard);
}
That's the whole setup. In a release build, a rooted or jailbroken device gets the block screen instead of your app. In debug, the checks are skipped, so you can still work on an emulator.
Block, log, or allow
The part I use most is the policy. Not every signal deserves the same answer.
A rooted phone in production is usually a block. An emulator in your QA build is expected. A debugger attached to a release build is worth knowing about, even if you don't block it yet.
So each detector can be switched on or off, each kind of signal can be allowed or not, and onViolation hands you the full result. That includes every signal, the reason for each one, and how long the scan took. Send it to your analytics first, see what real users trigger, and only then turn on blocking.
Your own screen and your own checks
The default block screen shows a short English message and the list of what was found. You can pass your own widget instead, or a builder that receives the summary of what was found:
await runGuardedApp(
app: const MyApp(),
guard: guard,
blockedBuilder: (summary) => MyBlockedScreen(summary: summary),
);
Checks are pluggable too. Implement IntegritySignalDetector for anything specific to your app, like an APK signature check, and add it next to the built-in ones.
What it can't do
These checks run on the device, so a determined attacker can hide from them. Treat this as your first line, not your only one. For anything that really matters, pair it with server-side attestation: Play Integrity on Android and App Attest on iOS. The guard keeps casual tampering and test devices out of production. Attestation lets your backend refuse requests from a device it can't trust.
One iOS detail: the jailbreak check also asks whether the cydia:// URL scheme can be opened. iOS only answers that if cydia is listed under LSApplicationQueriesSchemes in your Info.plist, so add it there.
Try it yourself
Add the package, ship it with blocking off and logging on for one release, and look at what your users actually trigger. The example app and the full API are here:
NoaTubic
/
device_integrity_guard
Reusable toolkit that lets you stop a Flutter application before it boots when the runtime environment looks unsafe. It bundles common integrity detectors (emulator, root, jailbreak, debugger) and gives you a cohesive policy + UI surface to decide whether suspicious signals should block the UX, simply be logged, or be ignored in specific builds.
Device Integrity Guard
device_integrity_guard is a reusable toolkit that lets you stop a Flutter application before it boots when the runtime environment looks unsafe. It bundles common integrity detectors (emulator, root, jailbreak, debugger) and gives you a cohesive policy + UI surface to decide whether suspicious signals should block the UX, simply be logged, or be ignored in specific builds.
Highlights
- π Configurable policy β Toggle each detector, decide if emulators/root/jailbreak/debuggers are acceptable, and bypass checks entirely in debug builds.
- π§ͺ Swappable detectors β Implement the
IntegritySignalDetectorinterface to plug in custom probes (e.g., corporate MDM, binary signatures). Default detectors live undersrc/detection/. - π§ Actionable results β
DeviceIntegrityGuard.checkAndMaybeBlock()returns aDeviceIntegrityResultthat lists allIntegritySignals plus how long scanning took. - π₯οΈ Builtβin blocking UI β
CompromisedEnvironmentScreenships with sensible English copy and can be replaced with any widget at runtime. - π Drop-in bootstrap helper β
runGuardedAppruns theβ¦
https://pub.dev/packages/device_integrity_guard
Feedback and issues are welcome on GitHub.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.