Thank you!
Thanks for visiting Appdome! Our mission is to secure every app on the planet by making mobile app security easy. We hope we’re living up to the mission with your project.
This guide walks you through integrating Appdome’s Threat-Events™ into your Flutter applications, offering a step-by-step approach to enhance mobile app security.
Appdome Threat-Events is a robust threat-intelligence framework for Android and iOS apps. It consists of three elements: a Threat Event, the data from each Threat Event, and the Threat-Score™.
Threat-Events streamlines the process for mobile developers by enabling them to register for, listen to, and consume real-time attack and threat data, courtesy of Appdome’s suite of mobile app security, anti-fraud, and mobile anti-bot protection measures. This functionality offers mobile app developers the ability to customize business logic and user experience based on the user’s risk profile and the specific attack or threat presented but also ensures that their mobile application workflows are aware of attacks and threats. Furthermore, it facilitates the passing of threat data to other systems of record, such as app servers, mobile fraud analysis systems, SIEMs, and other data collection points.
The purpose of Threat-Events is to enable Android and iOS applications to adapt and respond to mobile app attacks and threats in real time. This will ensure the safety of user data and transactions.
Appdome Threat-Events can be used as a stand-alone implementation in Flutter Apps or in combination with Threat-Scores. Threat-Events provide in-app notifications of each attack or threat, as well as the metadata associated with the attack. Threat Scores provide the mobile developer with a Threat-Event event score and the combined (aggregated) mobile end-user risk score at the time of the notification.
Before implementing Threat-Events or Threat-Scores in your Flutter App, ensure that the following conditions are met:
Note: You can find the specific identifiers for each Threat-Event and Threat-Score in the knowledge base article associated with each protection.
The figure below shows where you can find Threat-Events and Threat-Scores for each of the runtime mobile app security, anti-fraud, anti-malware, mobile antibot, and other protections available on Appdome.
To enable Threat-Events with any runtime protection, select the check box next to Threat-Events for that feature. Doing so will enable (turn ON) Threat-Events for that feature. To enable Threat-Scores for any runtime protection, click the up/down arrow associated with Threat-Scores to assign a specific score to each protection.
Threat-Scores must have a value greater than zero (0) and less than a thousand (1,000).
Threat-Events and Threat-Scores can be used with or in place of server-based mobile anti-fraud solutions.
Flutter provides various approaches to bridge the gap between its Dart code and platform-specific functionality on Android and iOS. Some common options include:
Choose the approach that best suits your needs when bridging the gap between Flutter and native platforms Threat-Events.
Event channels are the optimal approach for integrating threat-events into your Flutter app due to their inherent security-centric advantages:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class PlatformChannel extends StatefulWidget {
const PlatformChannel({super.key});
@override
State<PlatformChannel> createState() => _PlatformChannelState();
}
class _PlatformChannelState extends State<PlatformChannel> {
static const String _eventChannelName = 'yourEventChannel'; // Replace with your EventChannel name
static const EventChannel _eventChannel = EventChannel(_eventChannelName);
String _currentThreatEvent = 'No ThreatEvent detected';
String? keyboardID;
String? keyboardBlocked;
String? defaultMessage;
String? timeStamp;
String? deviceID;
String? deviceModel;
String? osVersion;
String? kernelInfo;
String? deviceManufacturer;
String? fusedAppToken;
String? carrierPlmn;
String? deviceBrand;
String? deviceBoard;
String? buildHost;
String? buildUser;
String? sdkVersion;
@override
void initState() {
super.initState();
_eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}
void _onEvent(Object? event) {
setState(() {
// Adapt this section based on your specific event data structure
var eventData = event as Map;
// Example: Accessing 'externalID' field from the event
var externalID = eventData['externalID'];
// Customize the rest of the fields based on your event structure
keyboardID = eventData['keyboard'];
keyboardBlocked = eventData['blocked'];
defaultMessage = eventData['defaultMessage'];
timeStamp = eventData['timestamp'];
deviceID = eventData['deviceID'];
deviceModel = eventData['deviceModel'];
osVersion = eventData['osVersion'];
kernelInfo = eventData['kernelInfo'];
deviceManufacturer = eventData['deviceManufacturer'];
fusedAppToken = eventData['fusedAppToken'];
carrierPlmn = eventData['carrierPlmn'];
deviceBrand = eventData['deviceBrand'];
deviceBoard = eventData['deviceBoard'];
buildHost = eventData['buildHost'];
buildUser = eventData['buildUser'];
sdkVersion = eventData['sdkVersion'];
_currentThreatEvent = "Detected ThreatEvent: $externalID.\n" +
"Keyboard ID: $keyboardID\n" +
"Keyboard Blocked: $keyboardBlocked\n" +
"Default Message: $defaultMessage\n" +
"Timestamp: $timeStamp\n" +
"Device ID: $deviceID\n" +
"Device Model: $deviceModel\n" +
"OS Version: $osVersion\n" +
"Kernel Info: $kernelInfo\n" +
"Device Manufacturer: $deviceManufacturer\n" +
"Fused App Token: $fusedAppToken\n" +
"Carrier PLMN: $carrierPlmn\n" +
"Device Brand: $deviceBrand\n" +
"Device Board: $deviceBoard\n" +
"Build Host: $buildHost\n" +
"Build User: $buildUser\n" +
"SDK Version: $sdkVersion\n";
});
}
void _onError(Object error) {
setState(() {
_currentThreatEvent = 'Failed To Get ThreatEvent: ${error}.';
});
}
@override
Widget build(BuildContext context) {
return Material(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(_currentThreatEvent),
],
),
);
}
}
void main() {
runApp(const MaterialApp(home: PlatformChannel()));
}
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate<FlutterStreamHandler>
@end
#import "AppDelegate.h"
#import <Flutter/Flutter.h>
#import "GeneratedPluginRegistrant.h"
#import "ThreatEventReceiver.h"
@implementation AppDelegate {
FlutterEventSink _eventSink;
ThreatEventReceiver *_threatEventReceiver;
}
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
FlutterViewController* controller =
(FlutterViewController*)self.window.rootViewController;
FlutterEventChannel* threatEventBridge = [FlutterEventChannel
eventChannelWithName:@"yourEventChannel"
binaryMessenger:controller];
[threatEventBridge setStreamHandler:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (FlutterError*)onListenWithArguments:(id)arguments
eventSink:(FlutterEventSink)eventSink {
_threatEventReceiver = [[ThreatEventReceiver alloc] init];
_threatEventReceiver.eventSink = eventSink;
return nil;
}
- (FlutterError*)onCancelWithArguments:(id)arguments {
return nil;
}
@end
#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
@interface ThreatEventReceiver : NSObject
@property (nonatomic, copy) FlutterEventSink eventSink;
- (instancetype)init;
@end
#import "ThreatEventReceiver.h"
@implementation ThreatEventReceiver
- (instancetype)init {
self = [super init];
if (self) {
// List of constant names
NSArray<NSString *> *notificationNames = @[ // Replace with your event names
@"BlockedKeyboardEvent",
@"BlockedClipboardEvent",
@"JailbrokenDevice",
@"SslCertificateValidationFailed",
@"SslNonSslConnection",
@"SslServerCertificatePinningFailed",
@"UrlWhitelistFailed",
@"BlockedScreenCaptureEvent",
@"SslIncompatibleCipher",
@"SslIncompatibleVersion",
@"SslInvalidCertificateChain",
@"SslInvalidMinRSASignature",
@"SslInvalidMinECCSignature",
@"SslInvalidMinDigest",
@"AppIntegrityError"
];
// Add observers for each constant name
for (NSString *notificationName in notificationNames) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleNotification:)
name:notificationName
object:nil];
}
}
return self;
}
- (void)dealloc {
// Remove all observers when the object is deallocated
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (self.eventSink) {
self.eventSink = nil;
}
}
- (void)handleNotification:(NSNotification *)notification {
// Handle the notification here
NSLog(@"Received notification: %@", notification.name);
if (!self.eventSink) return;
if (notification.name) {
self.eventSink([notification userInfo]);
} else {
self.eventSink([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"ThreatEvent action is null"
details:nil]);
}
}
@end
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.EventChannel.EventSink;
import io.flutter.plugin.common.EventChannel.StreamHandler;
public class MainActivity extends FlutterActivity {
private static final String YOUR_EVENT_CHANNEL = "yourEventChannel"; // Replace with your EventChannel name
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
new EventChannel(flutterEngine.getDartExecutor(), YOUR_EVENT_CHANNEL).setStreamHandler(
new StreamHandler() {
private ThreatEventReceiver threatEventReceiver;
@Override
public void onListen(Object arguments, EventSink events) {
threatEventReceiver = new ThreatEventReceiver();
threatEventReceiver.init(MainActivity.this, events);
}
@Override
public void onCancel(Object arguments) {
threatEventReceiver.stop();
threatEventReceiver = null;
}
}
);
}
}
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import java.util.Map;
import java.util.HashMap;
import io.flutter.plugin.common.EventChannel.EventSink;
public class ThreatEventReceiver {
private BroadcastReceiver receiver;
private Context context;
private static final String TAG = "ThreatEventReceiver";
private final static String BLOCKED_KEYBOARD = "BlockedKeyboardEvent"; // Replace with your event names
private final static String BLOCKED_CLIPBOARD = "BlockedClipboardEvent";
private final static String ROOTED_DEVICE = "RootedDevice";
private final static String UNKNOWN_SOURCES = "UnknownSourcesEnabled";
private final static String DEVELOPER_OPTIONS = "DeveloperOptionsEnabled";
private final static String SSL_VALIDATION_FAILED = "SslCertificateValidationFailed";
private final static String SSL_NON_SSL_CONNECTION = "SslNonSslConnection";
private final static String SSL_CERT_PINNING_FAILED = "SslCertificatePinningFailed";
private final static String ACCESS_OUTSIDE_WHITELIST = "UrlWhitelistFailed";
private final static String SSL_INCOMPATIBLE_CIPHER = "SslIncompatibleCipher";
private final static String SSL_INCOMPATIBLE_TLS = "SslIncompatibleVersion";
private final static String SSL_INVALID_CA_CHAIN = "SslInvalidCertificateChain";
private final static String SSL_INVALID_RSA_SIGNATURE = "SslInvalidMinRSASignature";
private final static String SSL_INVALID_ECC_SIGNATURE = "SslInvalidMinECCSignature";
private final static String SSL_INVALID_DIGEST = "SslInvalidMinDigest";
private final static String BLOCKED_MANUFACTURER = "BannedManufacturer";
// Only available when ONEShield Threat Events are enabled
private final static String TAMPERED_APP = "AppIntegrityError";
private final static String EMULATOR_FOUND = "EmulatorFound";
// Explicitly state that the ThreatEvents BroadcastReceiver should not be exported
private void registerReceiverWithFlags(IntentFilter intentFilter) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
} else {
context.registerReceiver(receiver, intentFilter);
}
}
public void init(Context context, final EventSink events) {
this.context = context;
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() != null) {
Map<String, Object> map = new HashMap<>();
Bundle extras = intent.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
map.put(key, value);
}
events.success(map);
}
} else {
events.error("UNAVAILABLE", "ThreatEvent action is null", null);
}
}
};
registerReceiverWithFlags(new IntentFilter(BLOCKED_KEYBOARD));
registerReceiverWithFlags(new IntentFilter(BLOCKED_CLIPBOARD));
registerReceiverWithFlags(new IntentFilter(ROOTED_DEVICE));
registerReceiverWithFlags(new IntentFilter(UNKNOWN_SOURCES));
registerReceiverWithFlags(new IntentFilter(DEVELOPER_OPTIONS));
registerReceiverWithFlags(new IntentFilter(SSL_VALIDATION_FAILED));
registerReceiverWithFlags(new IntentFilter(SSL_NON_SSL_CONNECTION));
registerReceiverWithFlags(new IntentFilter(SSL_CERT_PINNING_FAILED));
registerReceiverWithFlags(new IntentFilter(ACCESS_OUTSIDE_WHITELIST));
registerReceiverWithFlags(new IntentFilter(SSL_INCOMPATIBLE_CIPHER));
registerReceiverWithFlags(new IntentFilter(SSL_INCOMPATIBLE_TLS));
registerReceiverWithFlags(new IntentFilter(SSL_INVALID_CA_CHAIN));
registerReceiverWithFlags(new IntentFilter(SSL_INVALID_RSA_SIGNATURE));
registerReceiverWithFlags(new IntentFilter(SSL_INVALID_ECC_SIGNATURE));
registerReceiverWithFlags(new IntentFilter(SSL_INVALID_DIGEST));
registerReceiverWithFlags(new IntentFilter(BLOCKED_MANUFACTURER));
// Only available when ONEShield Threat Events are enabled
registerReceiverWithFlags(new IntentFilter(TAMPERED_APP));
registerReceiverWithFlags(new IntentFilter(EMULATOR_FOUND));
}
// This method should be called during the Activity.onPause() if the context used when calling the init() method is an Activity context
public void stop() {
context.unregisterReceiver(receiver);
}
}
Compatibility with Android 14 is seamless through Flutter EventChannel. Utilizing native platform implementations, the code sample provided above ensures that no modifications are needed to make your app fully compatible with Android 14.
Below is the list of metadata that can be associated with each mobile application Threat-Event and Threat-Score in Flutter Apps.
Threat-Event Context Keys | |
---|---|
message | Message displayed for the user on event |
externalID | The external ID of the event which can be listened via Threat Events |
osVersion | OS version of the current device |
deviceModel | Current device model |
deviceManufacturer | The manufacturer of the current device |
fusedAppToken | The task ID of the Appdome fusion of the currently running app |
kernelInfo | Info about the kernel: system name, node name, release, version and machine. |
carrierPlmn | PLMN of the device |
deviceID | Current device ID |
reasonCode | Reason code of the occurred event |
buildDate | Appdome fusion date of the current application |
devicePlatform | OS name of the current device |
carrierName | Carrier name of the current device |
updatedOSVersion | Is the OS version up to date |
deviceBrand | Brand of the device |
deviceBoard | Board of the device |
buildUser | Build user |
buildHost | Build host |
sdkVersion | Sdk version |
timeZone | Time zone |
deviceFaceDown | Is the device face down |
locationLong | Location long |
locationLat | Location lat |
locationState | Location state |
wifiSsid | Wifi SSID |
wifiSsidPermissionStatus | Wifi SSID permission status |
Some or all of the meta-data for each mobile application Threat-Event and Threat-Score can be consumed in Flutter Apps at the discretion of the mobile developer and used, in combination with other mobile application data, to adapt the business logic or user experience when one or more attacks or threats are present.
Conditional Enforcement is an extension to Appdome’s mobile application Threat-Event framework. By using conditional enforcement, developers can control when Appdome enforcement of each mobile application protection takes place or invoke backup, failsafe, and enforcement to any in-app enforcement used by the mobile developer.
For more information on using conditional enforcement with your Threat-Event implementation, please contact support.appdome.com
After you have implemented the required Threat-Event code in your Flutter Apps, you can confirm that the Appdome protections in the Flutter Apps properly recognize your Threat-Event implementation by reviewing the Certified Secure™ DevSecOps certificate for your build on Appdome.
In the Certified Secure DevSecOps certificate, an incorrect implementation of Threat-Events in your mobile application is displayed as shown below.
For information on how to retrieve the Certified Secure DevSecOps certification for your mobile application on Appdome, please visit the knowledge base article: Using Certified Secure™ Android & iOS Apps Build Certification in DevOps CI/CD
Thanks for visiting Appdome! Our mission is to secure every app on the planet by making mobile app security easy. We hope we’re living up to the mission with your project.