Heuristic Device Integrity Signals for Flutter
Lightweight detection of compromised devices: root/jailbreak, emulator/simulator, hook/Frida, and debugger attachment — for both Android and iOS. No third-party SDKs.
| iOS | Android |
|---|---|
![]() |
![]() |
- ✅ Android: Kotlin + C++ (JNI) for native signal collection
- ✅ iOS: Swift + Objective-C++ for native security checks
- ✅ Heuristic approach: Multi-signal detection with fail-soft behavior
- ✅ Fast: Targets 1–20 ms total execution time (native scans typically 1–5 ms)
- ✅ Typed API:
DeviceTrustReportmodel via MethodChannel - ✅ No third-party dependencies: Pure platform code, no external SDKs
| Platform | Minimum Version | Notes |
|---|---|---|
| Android | API 24+ (Android 7.0) | Kotlin + C++ JNI |
| iOS | iOS 13.0+ | Swift 5.0 |
Add to your pubspec.yaml:
dependencies:
device_trust: ^1.0.0Run:
flutter pub getimport 'package:device_trust/device_trust.dart';
Future<void> checkDeviceTrust() async {
// Get the device trust report
final report = await DeviceTrust.getReport();
// Simple policy: compromised if any major flag is true
final compromised = report.rootedOrJailbroken ||
report.fridaSuspected ||
report.emulator ||
report.debuggerAttached;
if (compromised) {
print('⚠️ Device integrity compromised');
} else {
print('✅ Device appears secure');
}
// Log detailed signals
print('Details: ${report.details}');
}final report = await DeviceTrust.getReport();
// Android-specific
if (report.devModeEnabled) {
print('Developer mode is enabled');
}
if (report.adbEnabled) {
print('ADB debugging is enabled');
}
// iOS-specific: check jailbreak paths
final jbPaths = report.details['jbPathHits'] as List<dynamic>? ?? [];
if (jbPaths.isNotEmpty) {
print('Jailbreak paths detected: $jbPaths');
}Returns a Future<DeviceTrustReport> with the following fields:
| Field | Type | Description |
|---|---|---|
rootedOrJailbroken |
bool |
Device is rooted (Android) or jailbroken (iOS) |
emulator |
bool |
Running on emulator/simulator |
fridaSuspected |
bool |
Frida or hooking framework detected |
debuggerAttached |
bool |
Debugger is attached to the process |
devModeEnabled |
bool |
Developer mode enabled (Android only) |
adbEnabled |
bool |
ADB debugging enabled (Android only) |
details |
Map<String, dynamic> |
Platform-specific signals and metadata |
Returns Future<bool> indicating whether the current platform is supported.
- Manifest Queries: The plugin's
AndroidManifest.xmldeclares<queries>for common root management apps (Magisk, SuperSU, etc.) and Frida server. These merge automatically via Gradle—no manual configuration needed. - Native Library: C++ code is compiled into an AAR with the following ABIs:
arm64-v8a(64-bit ARM)armeabi-v7a(32-bit ARM)x86_64(64-bit x86)
- Auto-linking: CMake/ndk-build handles linking; no additional setup required.
- Compatibility: This plugin relies on your app's Android Gradle Plugin (AGP) and Kotlin versions. If you encounter version conflicts during build, align the AGP/Kotlin versions in your app's root
build.gradleto match the plugin's requirements (typically AGP 8.0+ and Kotlin 1.9+). - 16KB Page Size Support: Android devices with 16KB page size are supported (Android 15+ on some devices). The native library is built with
-Wl,-z,max-page-size=16384for arm64-v8a. We recommend using a modern NDK (r26+) for optimal compatibility.
-
URL Schemes: For jailbreak detection, the plugin checks if certain URL schemes can be opened (
cydia://,sileo://, etc.). Add these to your app'sInfo.plistunderLSApplicationQueriesSchemes:<key>LSApplicationQueriesSchemes</key> <array> <string>cydia</string> <string>sileo</string> <string>zbra</string> <string>filza</string> <string>undecimus</string> <string>activator</string> </array>
Note: This is for
canOpenURLchecks only—no URLs are actually opened. -
Anti-Debug: The native function
DTNDenyDebuggerAttach()callsptrace(PT_DENY_ATTACH), but only in:- Release builds (not Debug)
- Physical devices (not Simulator)
In Debug/Simulator, it's a no-op to avoid interfering with development.
-
Bridging Header: Not required. CocoaPods framework mode exposes C functions via the umbrella header, so Swift code sees them automatically.
- Native Scan Duration: Typically 1–5 ms for file checks, process inspection, and memory analysis.
- Total Time: Targets 1–20 ms end-to-end (native + Dart overhead).
- Fail-Soft: If the native library fails to load, times out, or throws an error, the plugin returns safe defaults (all flags
false, empty details). The app will not crash.
This plugin uses heuristic detection, which can be bypassed by:
- Magisk Hide, Shamiko (root cloaking on Android)
- Frida stealth mode, Objection (hooking framework concealment)
- Custom OS modifications or kernel patches
-
The plugin does not make blocking decisions for you.
-
Use multiple signals together to build a robust policy:
final highRisk = report.rootedOrJailbroken && report.fridaSuspected; final mediumRisk = report.emulator || report.debuggerAttached;
- Emulators/Simulators are flagged as compromised by default. In production, you may want to allow them for internal testing.
- Debug mode always attaches a debugger—this is expected during development.
The example/ directory contains a production-level diagnostic UI that displays:
- Summary of all flags (color-coded)
- Policy evaluation (example)
- JSON details (with copy-to-clipboard)
- Detected signals (paths, URL schemes, libraries)
Run it:
cd example
flutter run -d <device-or-simulator>See example/README.md for platform-specific expectations (e.g., simulator shows emulator: true).
CocoaPods uses framework mode (use_frameworks!) and generates an umbrella header that includes all public headers. Swift code in the pod target sees C functions (like DTNCollectNativeSignalsJSON) via this umbrella, so no manual bridging header is needed.
- iOS: Apple enforces W^X (Write XOR Execute) on modern devices. RWX segments are rare and typically indicate a jailbreak or Frida injection.
- Simulator: Memory layout differs; RWX signals may not appear as expected.
No—it works on both emulators/simulators and physical devices. However:
- Emulators are flagged as
emulator: true. - Some signals (like anti-debug
ptrace) are only active on physical devices in Release mode.
Currently, thresholds are hardcoded in the native layer. A future version may expose configuration options (e.g., RWX segment count threshold).
- Additional signals (USB debugging status, system integrity checks)
- Configurable thresholds (e.g., adjust RWX segment sensitivity)
- Optional policy/example UI as a separate package
- Linux/Windows/macOS support (community contribution welcome)
Contributions are welcome! Please:
- Fork the repo
- Create a feature branch
- Add tests for new functionality
- Submit a pull request
See CONTRIBUTING.md for guidelines.
MIT License. See LICENSE for details.
- Issues: GitHub Issues
- Repository: github.com/MuhammedErdemKazanci/device_trust
Built with ❤️ for Flutter security

