safety_net_flutter 1.2.0
safety_net_flutter: ^1.2.0 copied to clipboard
Flutter plugin wrapping SafetyNet (iOS) and SafetyNetAndroid for report-only root/jailbreak/tamper/debugger detection on both platforms.
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() + 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"
}
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());
}
Play Integrity API (Android-only) #
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);
}
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(),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.