flutter_device_protection
Device integrity and screen-capture protection for Flutter, behind a single call.
One scan() applies the protections you ask for and returns a scored report of
everything it found, with the same shape on Android and iOS.
Install
dependencies:
flutter_device_protection: ^0.1.0
Use
import 'package:flutter_device_protection/flutter_device_protection.dart';
final report = await FlutterDeviceProtection.scan(
blockScreenshots: true,
blockScreenRecording: true,
blockBackgroundSnapshot: true,
);
if (report.isAtLeast(RiskLevel.high)) {
// Refuse to show sensitive data, sign the user out, alert your backend.
}
That is the whole API for most applications. Protection is applied before the checks run, so sensitive UI shown in response to the report is already covered.
Every flag defaults to null, meaning "leave that protection as it is", so a
bare scan() is a pure read and safe to call repeatedly.
What it protects against
Three flags, each mapping onto the platform mechanism that actually works:
| Flag | Android | iOS |
|---|---|---|
blockScreenshots |
FLAG_SECURE |
Secure-text-field containment (best effort) |
blockScreenRecording |
FLAG_SECURE |
Blacks the UI out while UIScreen.isCaptured |
blockBackgroundSnapshot |
Cover view on pause | Cover window on resign-active |
On Android the first two share one switch, because FLAG_SECURE cannot
separate them: setting either sets both. On iOS they are genuinely independent.
What it detects
The checks always run. They have no flags because they observe rather than change anything.
| Check | Android | iOS |
|---|---|---|
root |
su binaries, root-management artefacts, test-keys build tags | Jailbreak artefacts, writes outside the sandbox, relocated system directories |
emulator |
Build properties, QEMU pipes | Simulator build target and environment |
debugger |
Debug.isDebuggerConnected |
P_TRACED via sysctl |
frida |
frida-server binaries and processes | Injected Mach-O images, Frida control port |
vpn |
TRANSPORT_VPN capability |
Tunnel interfaces in the scoped proxy settings |
developerMode |
Developer settings flag | Not supported |
usbDebugging |
ADB enabled flag | Not supported |
Checks a platform cannot answer are still present in the report, with
supported set to false, so reporting code never has to branch on the
platform. They never contribute to the risk score.
The report
report.overallRisk.score; // 0-100, weighted across every supported check
report.overallRisk.level; // low < 26, medium < 51, high < 76, critical
report.threats; // the checks that fired, worst first
report.hasThreats; // quick "is there anything to look at"
report.isAtLeast(RiskLevel.high);
report.root.detected;
report.root.confidence; // 0-100
report.root.reasons; // the individual signals that fired
report.root.supported;
report.checks; // every check, keyed by SecurityThreat
report.toMap(); // JSON-encodable, for shipping to a backend
report.toSummary(); // human-readable, for logs
Scores are weighted: root 0.25, frida 0.20, debugger 0.15, then usbDebugging, developerMode, emulator and vpn at 0.10 each. Both platforms use the same weights and thresholds, so the same device state scores the same on each.
Fine-grained control
scan() covers the common case. These exist for toggling protection between
scans, for example while one specific screen is on display:
await FlutterDeviceProtection.enableScreenProtection();
await FlutterDeviceProtection.disableScreenProtection();
await FlutterDeviceProtection.isScreenProtectionEnabled();
await FlutterDeviceProtection.enableBackgroundProtection();
await FlutterDeviceProtection.disableBackgroundProtection();
await FlutterDeviceProtection.isBackgroundProtectionEnabled();
Requirements
- Android: minSdk 24
- iOS: 13.0
What this is not
Client-side detection is evidence, not a security boundary. Anything running on a device the attacker controls can be defeated by an attacker who controls that device: a jailbreak that hides itself will pass the jailbreak checks, and iOS offers no supported way to block a screenshot at all.
Treat a report as a signal to feed into a server-side decision, raise a step-up check, or degrade what the app is willing to display. Do not treat it as proof the device is clean.
Two specific caveats worth knowing:
debuggerfires in ordinary debug builds run from Xcode or Android Studio, because a debugger really is attached. Gate any reaction on build configuration rather than suppressing the check.vpnis the least precise check. iCloud Private Relay and similar features create the same tunnel interfaces, so read a hit as "traffic may be proxied", not as evidence of anything hostile. It is weighted accordingly.
Example
example/ is a working app that scans on launch, renders every check, and
blocks the UI when the risk level reaches high.
cd example
flutter run
Tests
flutter test # models, facade, channel contract
cd example && flutter test # widget tests against a mocked channel
cd example && flutter test integration_test # the real native plugins