flutter_rasp 5.1.0
flutter_rasp: ^5.1.0 copied to clipboard
RASP plugin for Flutter. Detect root, jailbreak, emulators, debuggers, hooks, repackaging, VPN, SSL pinning, and more.
Flutter RASP #
A comprehensive RASP (Runtime Application Self-Protection) plugin for Flutter. Protect your app against reverse engineering, tampering, runtime attacks, and man-in-the-middle attacks.
Features #
Threat Detection #
| Threat | Android | iOS | Description |
|---|---|---|---|
| Root / Jailbreak | ✅ | ✅ | Compromised OS with full system access. Attackers can bypass app sandboxing, read private data, and inject code |
| Emulator / Simulator | ✅ | ✅ | Virtual environments used to automate attacks, bypass device-bound protections, and analyze app behavior at scale |
| Debugger | ✅ | ✅ | Attached debuggers (JDWP, ptrace) allow stepping through code, modifying variables at runtime, and extracting secrets |
| Hooks (Frida/Xposed) | ✅ | ✅ | Instrumentation frameworks that intercept and modify function calls at runtime, bypassing security checks |
| Repackaging | ✅ | ✅ | Tampered app re-signed with a different certificate. Used to inject malware, remove license checks, or steal data |
| Trusted Install | ✅ | ✅ | Sideloaded apps bypass store review and integrity checks, increasing risk of running modified or malicious builds |
| VPN | ✅ | ✅ | Active VPN or proxy that can intercept, inspect, and modify network traffic between the app and its servers |
| Developer Mode | ✅ | ❌ | Enabled developer options expose debugging interfaces that allow unauthorized access to app internals |
| ADB Enabled | ✅ | ❌ | Android Debug Bridge enabled, allowing unauthorized USB debugging access to app internals and data extraction |
| Device Passcode | ✅ | ✅ | Device without screen lock. Physical access gives unrestricted access to app data and keychain entries |
| Secure Hardware | ✅ | ✅ | Missing hardware-backed keystore (TEE/StrongBox, Secure Enclave). Cryptographic keys can be extracted by software attacks |
| Obfuscation | ✅ | ❌ | Unobfuscated binary with readable class and symbol names, making reverse engineering and vulnerability discovery trivial |
| Time Spoofing | ✅ | ❌ | Manipulated system clock used to bypass time-based logic like token expiration, trial periods, or certificate validity |
| Location Spoofing | ✅ | ❌ | Fake GPS coordinates used to bypass geo-restrictions, cheat in location-based services, or commit region-locked fraud |
| Multi-Instance | ✅ | ❌ | Cloned or dual-app environments that run multiple copies of the app to abuse promotions, bypass rate limits, or impersonate users |
| Screen Capture | ✅ | ✅ | Blocks screenshots and screen recording to prevent leaking sensitive UI content like PINs, tokens, or personal data |
SSL Certificate Pinning #
| Feature | Description |
|---|---|
| PEM Certificate Pinning | Only trusts the .pem certificates you provide — any other connection fails automatically |
| Encrypted Certificate Pinning | Encrypt .pem files so they can't be extracted from your app bundle |
| Remote Certificate Updates | Fetch certificates from your API at runtime — no app update needed on rotation |
| Secure Storage | Certificates stored via Keychain (iOS) / EncryptedSharedPreferences (Android) |
Compatible with dart:io, Dio, package:http |
Works with any HTTP client that accepts a dart:io HttpClient |
Getting Started #
dependencies:
flutter_rasp: ^5.1.0
| Platform | Minimum Version |
|---|---|
| Android | API 24 (Android 7.0) |
| iOS | 13.0 |
No additional permissions required.
Usage #
Initialization #
import 'package:flutter_rasp/flutter_rasp.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterRasp.instance.initialize(
config: const RaspConfig(
policy: ThreatPolicy.high,
monitoringInterval: Duration(seconds: 10),
androidConfig: AndroidRaspConfig(
signingCertHashes: ['A1:2B:3C:4D:5E:6F:70:81:92:A3:B4:C5:D6:E7:F8:09:1A:2B:3C:4D:5E:6F:70:81:92:A3:B4:C5:D6:E7:F8:09'],
),
iosConfig: IosRaspConfig(
teamId: 'A1B2C3D4E5',
bundleIds: ['com.yourcompany.yourapp'],
),
),
onThreatDetected: (threats) => debugPrint('$threats'),
threatCallback: ThreatCallback(
onRoot: () => navigateToBlockedScreen(),
onVpn: () => showVpnWarning(),
),
);
runApp(const MyApp());
}
Note: At least one of
onThreatDetectedorthreatCallbackmust be provided.
Platform Configuration #
Android — signingCertHashes accepts the SHA-256 fingerprint directly from Google Play Console.
Important: Google Play re-signs your app. You must use the Play signing key, not your local keystore.
Go to Google Play Console → your app → Release → App integrity → App signing and copy the SHA-256 fingerprint from the App signing key certificate section. Pass it directly:
AndroidRaspConfig(
signingCertHashes: ['A1:2B:3C:4D:5E:6F:70:81:92:A3:B4:C5:D6:E7:F8:09:1A:2B:3C:4D:5E:6F:70:81:92:A3:B4:C5:D6:E7:F8:09'],
)
iOS — Find your Team ID at Apple Developer Account → Membership Details.
SSL Certificate Pinning #
Tip: Initialize SSL pinning at app startup (e.g., in
main()) before making any HTTP request. The first call builds and caches theSecurityContext— subsequent calls return instantly with zero latency.
Download your server's certificate:
openssl s_client -connect your-api.com:443 -servername your-api.com \
2>/dev/null | openssl x509 > assets/certs/your_server.pem
Register it in pubspec.yaml:
flutter:
assets:
- assets/certs/
Plain PEM
const config = SslPinningConfig(
certificateAssetPaths: ['assets/certs/your_server.pem'],
);
final client = await SslPinningClient.createHttpClient(config);
Encrypted PEM
Encrypt your .pem with the Certificate Encryptor web tool, then place the .enc file in your assets:
const config = SslPinningConfig(
certificateAssetPaths: ['assets/certs/your_server.enc'],
passphrase: 'your_passphrase',
);
final client = await SslPinningClient.createHttpClient(config);
Remote Certificate Updates
Pass an onFetchRemote callback to update certificates at runtime — no app update needed on server certificate rotation. Each config represents one endpoint with one certificate:
// API endpoint
const apiConfig = SslPinningConfig(
certificateAssetPaths: ['assets/certs/api.enc'],
passphrase: 'your_passphrase',
);
final apiClient = await SslPinningClient.createHttpClient(
apiConfig,
onFetchRemote: () async {
final response = await yourApi.get(
'/certs/current.enc',
headers: {'Authorization': 'Bearer $token'},
);
return response.bodyBytes;
},
);
// CDN endpoint (independent config, independent storage)
const cdnConfig = SslPinningConfig(
certificateAssetPaths: ['assets/certs/cdn.enc'],
passphrase: 'your_passphrase',
);
final cdnClient = await SslPinningClient.createHttpClient(
cdnConfig,
onFetchRemote: () async {
final response = await yourCdn.get('/certs/cdn.enc');
return response.bodyBytes;
},
);
The plugin resolves each certificate independently: stored cert → remote fetch → asset fallback. Stored certificates use Keychain (iOS) and EncryptedSharedPreferences (Android), and are only written when the content changes.
await SslPinningClient.clearStoredCertificate(apiConfig);
SslPinningClient.invalidateCache();
HTTP Client Compatibility
Works with any HTTP client that accepts a dart:io HttpClient:
// dart:io
final request = await client.getUrl(Uri.parse('https://your-api.com/endpoint'));
// Dio
final dio = Dio()
..httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () => client,
);
// package:http
final httpClient = IOClient(client);
You can also use SslPinningClient.createContext(config) to get a SecurityContext directly if you need more control.
See the example app for a complete working implementation.
Supported Stores / Distribution Methods #
The supportedStores parameter in AndroidRaspConfig controls which install sources are considered trusted.
| Store / Distribution method | Package name | Notes |
|---|---|---|
| App Store (iOS) | Included by default, no action needed | |
| TestFlight (iOS) | Included by default, no action needed | |
| Google Play | com.android.vending |
Included by default, no action needed |
| Huawei AppGallery | com.huawei.appmarket |
Included by default, no action needed |
| Amazon Appstore | com.amazon.venezia |
Included by default, no action needed |
| Samsung Galaxy Store | com.sec.android.app.samsungapps |
Included by default, no action needed |
| Firebase App Distribution | dev.firebase.appdistribution |
|
| Vivo App Store | com.vivo.appstore |
Common on Vivo devices |
| HeyTap | com.heytap.market |
Common on Realme and Oppo devices |
| Oppo App Market | com.oppo.market |
Common on Oppo devices |
| GetApps | com.xiaomi.mipicks |
Common on Xiaomi, Redmi and POCO devices |
To support additional stores, add their package names:
AndroidRaspConfig(
signingCertHashes: ['...'],
supportedStores: [
...AndroidRaspConfig().supportedStores,
'dev.firebase.appdistribution', // Firebase App Distribution
'com.vivo.appstore', // Vivo App Store
'com.heytap.market', // HeyTap (Realme, Oppo)
'com.oppo.market', // Oppo App Market
'com.xiaomi.mipicks', // GetApps (Xiaomi, Redmi, POCO)
],
);
Threat Policies #
Policies control which threats terminate the app at the native level before Dart code can react.
| Policy | Exit Threats |
|---|---|
ThreatPolicy.none |
None (report only) |
ThreatPolicy.low |
repackaging, trustedInstall |
ThreatPolicy.medium |
root, hook, repackaging, trustedInstall, obfuscationIssues, multiInstance |
ThreatPolicy.high |
root, hook, repackaging, trustedInstall, debug, devicePasscode, obfuscationIssues, multiInstance, secureHardwareNotAvailable, locationSpoofing, adbEnabled |
const policy = ThreatPolicy(
exitThreats: {Threat.root, Threat.repackaging, Threat.vpn},
);
Tip: Use
ThreatPolicy.noneduring development.
Obfuscation Detection (Android) #
flutter run → detected (debug has no R8)
flutter run --release → not detected (R8 obfuscates classes)
Enable R8 in android/app/build.gradle.kts:
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
Note: Flutter's
--obfuscateflag only covers Dart code.isMinifyEnabled = trueis required to obfuscate the native Android layer.
Scans & Individual Checks #
final result = await FlutterRasp.instance.scanAll();
if (result.isCompromised) {
debugPrint('Detected: ${result.detectedThreats}');
}
Available: isRooted(), isEmulator(), isDebugged(), isHooked(), isRepackaged(), isUntrustedInstall(), isVpnConnected(), isDeveloperMode(), isAdbEnabled(), isDevicePasscodeDisabled(), isSecureHardwareUnavailable(), hasObfuscationIssues(), isTimeSpoofed(), isLocationSpoofed(), isMultiInstance().
Screen Capture Protection #
await FlutterRasp.instance.blockScreenCapture(true);
Architecture #
Flutter App
|
FlutterRasp (Singleton) SslPinningClient
| |
FlutterRaspPlatform (Interface) CertificateDecryptor (encrypted .enc)
| CertificateStore (Keychain / EncryptedSharedPrefs)
PigeonFlutterRasp SecurityContext (withTrustedRoots: false)
|--- FlutterRaspHostApi (type-safe commands/checks)
|--- FlutterRaspFlutterApi (type-safe threat stream)
|
Android (Kotlin) iOS (Swift)
------------------- -------------------
FlutterRaspPlugin FlutterRaspPlugin
| |
flutter_rasp_core (AAR) flutter_rasp_core (XCFramework)
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
Adding a New Detector #
- Create a detector class implementing
ThreatDetector(Android) orThreatDetectable(iOS) - Add it to the
DetectorRegistrylist - Add the corresponding
Threatenum value in Dart
License #
This project is licensed under the MIT License - see the LICENSE file for details.