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.).
| 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() + SafetyNetAndroid.checkProxy(), 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"
}Published on pub.dev. Add it to your app's
pubspec.yaml:
dependencies:
safety_net_flutter: ^1.2.0Then:
flutter pub getFor local development against an unreleased version of this plugin instead, use a git dependency pinned to a branch/commit (this repo's git tags lag behind pub.dev releases, so don't pin to a tag expecting it to match the latest published version):
dependencies:
safety_net_flutter:
git:
url: https://github.com/DipakPanchasara/SafetyNetFlutter.gitSafetyNet and SafetyNetObjC are both published on the CocoaPods trunk, so no extra
Podfile setup is needed regardless of which iOS plugin resolution your app uses:
-
Swift Package Manager (Flutter's modern default,
ios/Flutter/Generated.xcconfigreferencingPackage.swift): this plugin's ownPackage.swiftalready declares the SafetyNet git dependency (https://github.com/DipakPanchasara/SafetyNet.git, from2.1.0), and SPM resolves it transitively. -
CocoaPods (
ios/Podfile): this plugin's own podspec already declaress.dependency 'SafetyNet', which CocoaPods now resolves straight from the trunk — just runpod installfromios/, no extrapodlines needed in your ownPodfile.
No other iOS setup is required — see "Do I need to touch AppDelegate?" below.
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.
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());
}import 'package:safety_net_flutter/safety_net_flutter.dart';
// Call once (e.g. at app launch), well before you'll need a token.
// Per Google's guidance this has non-trivial latency — typically under
// 10s, budget up to a minute.
await SafetyNetFlutter.prepareIntegrityCheck(yourCloudProjectNumber);
// Later, when you need a token for a specific action:
final token = await SafetyNetFlutter.requestIntegrityToken(requestHash);
// Send `token` to your server — it is opaque and is NOT itself a verdict.
// Your server must decode it against Google
// (POST https://playintegrity.googleapis.com/v1/{PACKAGE_NAME}:decodeIntegrityToken)
// and enforce the verdict there. Never trust a client-side read of this token.Bridges SafetyNetAndroid's IntegrityTokenProvider — see the
SafetyNetAndroid README
for cloudProjectNumber setup and the full server-side verification contract.
Both functions reject on iOS — Play Integrity is a Google Play Services API
with no iOS equivalent. This is a normal PlatformException (code
UNSUPPORTED_PLATFORM), not a crash — handle it like any other Dart error, or
gate the call yourself since you already know it's Android-only:
import 'dart:io' show Platform;
if (Platform.isAndroid) {
await SafetyNetFlutter.prepareIntegrityCheck(yourCloudProjectNumber);
}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.
See example/ for a minimal app that runs the check on launch and displays
isCompromised and reasons.
cd example
flutter run-
lib/— Dart-facing API (SafetyNetFlutter.check(),prepareIntegrityCheck()/requestIntegrityToken(),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.