safety_net_flutter
A Flutter plugin wrapping SafetyNet (iOS) and SafetyNetAndroid (Android) behind one Dart API: root/jailbreak/tamper/debugger detection for Flutter apps.
Like both native libraries it wraps, this plugin never auto-reacts. It only reports what it finds — your app decides what to do with the result (block a screen, log it, step up authentication, etc.).
What each platform actually runs
| Platform | Underlying check |
|---|---|
| iOS | SafetyNet.shared.check() — the full scored pipeline: jailbreak signals, debugger/tracing, code-signature integrity, proxy/VPN. See the SafetyNet README for the full signal list and scoring thresholds. |
| Android | SafetyNetAndroid.checkRoot() + SafetyNetAndroid.checkDebugger(), combined into one result. See the SafetyNetAndroid README for the full check list. |
The Dart-facing result is intentionally a minimal, unified shape rather than exposing each
platform's full native richness (e.g. iOS's numeric ThreatLevel isn't surfaced) — apps that
need finer-grained platform-specific data should call the native SDKs directly on that platform.
class SafetyNetCheckResult {
final bool isCompromised;
final List<String> reasons; // raw check names that fired, e.g. "jailbreakDetected"
}
Installation
Add the dependency to your app's pubspec.yaml:
dependencies:
safety_net_flutter:
git:
url: https://github.com/DipakPanchasara/SafetyNetFlutter.git
tag: 1.0.0 # once tagged; otherwise omit to track the default branch
Then:
flutter pub get
iOS dependency resolution
SafetyNet is not yet published to CocoaPods trunk, so the two supported iOS integration
paths differ slightly:
-
If your app uses Swift Package Manager for iOS plugins (Flutter's modern default,
ios/Flutter/Generated.xcconfigreferencingPackage.swift): nothing further to do. This plugin's ownPackage.swiftalready declares the SafetyNet git dependency (https://github.com/DipakPanchasara/SafetyNet.git, from2.1.0), and SPM resolves it transitively. -
If your app uses CocoaPods for iOS plugins (
ios/Podfile): you must add these two lines to your ownPodfileso CocoaPods can resolveSafetyNetand its ownSafetyNetObjCdependency (a podspec'ss.dependencycan only point at a name registered in a spec repo, not an arbitrary git URL — only aPodfile'spodline can do that):pod 'SafetyNet', :git => 'https://github.com/DipakPanchasara/SafetyNet.git', :tag => '2.1.0' pod 'SafetyNetObjC', :git => 'https://github.com/DipakPanchasara/SafetyNet.git', :tag => '2.1.0'Then run
pod installfromios/. (Seeexample/ios/Podfilein this repo for a working reference.)
No other iOS setup is required — see "Do I need to touch AppDelegate?" below.
Android dependency resolution
The Android side depends on SafetyNetAndroid via Maven Central
(io.github.dipakpanchasara:safetynet-android). As long as your app's repository list
includes mavenCentral() — true by default in virtually every Flutter/Gradle project — no
further setup is required.
Usage
import 'package:safety_net_flutter/safety_net_flutter.dart';
Future<void> checkDeviceSecurity() async {
final result = await SafetyNetFlutter.check();
if (result.isCompromised) {
// Your call: log it, show a warning, require step-up auth, etc.
// SafetyNetFlutter itself never blocks UI or logs the user out.
print('Device flagged: ${result.reasons}');
}
}
A common pattern is running the check once at app launch, before runApp():
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final result = await SafetyNetFlutter.check();
if (result.isCompromised) {
// record/report as appropriate for your app
}
runApp(const MyApp());
}
Do I need to touch AppDelegate?
No manual AppDelegate edit is needed to register this plugin. Flutter's
GeneratedPluginRegistrant (generated automatically at build time) registers every plugin
listed in your pubspec.yaml, including this one — that's true whether your AppDelegate is
Swift or Objective-C, and regardless of whether you use SPM or CocoaPods for the iOS side.
Two things worth knowing that are independent of Flutter's plugin registration:
- SafetyNet's anti-ptrace protection (
AntiDebugBridge.m's constructor) runs automatically the moment the SafetyNet dynamic library is loaded into the process — this happens at process launch, before Dart or Flutter even start, and needs no wiring on your part. - If you specifically want the check result available as early as possible in the app
lifecycle (e.g. before the first frame renders), call
SafetyNetFlutter.check()at the top of Dart'smain()as shown above — there's no AppDelegate-level equivalent needed for this, since the check itself is a Dart-initiated platform channel call.
Example app
See example/ for a minimal app that runs the check on launch and displays
isCompromised and reasons.
cd example
flutter run
Repository layout
lib/— Dart-facing API (SafetyNetFlutter.check(),SafetyNetCheckResult, platform interface, method channel implementation).ios/— iOS plugin implementation (Swift), wrappingSafetyNet.android/— Android plugin implementation (Kotlin), wrappingSafetyNetAndroid.example/— demo app exercising the plugin on both platforms.