From 6b1d5c8e6a9da6a560a28376958fc15c2792d3f5 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Thu, 7 May 2026 16:25:43 +0200 Subject: [PATCH 01/20] feat: bump Android SDK to 18.3.0 --- android/build.gradle | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 3802ef1..1193161 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -3,7 +3,7 @@ version '1.0-SNAPSHOT' buildscript { ext.kotlin_version = '2.1.0' - ext.talsec_version = '18.0.4' + ext.talsec_version = '18.3.0' repositories { google() mavenCentral() diff --git a/pubspec.yaml b/pubspec.yaml index 7c5716a..fd5562d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: freerasp description: Flutter library for improving app security and threat monitoring on Android and iOS mobile devices. Learn more about provided features on the freeRASP's homepage first. -version: 7.5.1 +version: 7.6.0 homepage: https://www.talsec.app/freerasp-in-app-protection-security-talsec repository: https://github.com/talsec/Free-RASP-Flutter From 1058b9e92fbe84972942eae80418a6ba8ff643e8 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Thu, 7 May 2026 16:25:52 +0200 Subject: [PATCH 02/20] feat!: rename reason to reasons in SuspiciousAppInfo --- .../com/aheaditec/freerasp/Extensions.kt | 52 ++++++++++++++++++- .../freerasp/generated/TalsecPigeonApi.kt | 8 +-- example/lib/widgets/malware_bottom_sheet.dart | 2 +- lib/src/generated/talsec_pigeon_api.g.dart | 8 +-- pigeons/talsec_pigeon_api.dart | 4 +- 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt b/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt index a5e9a1b..6b667a5 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt @@ -4,8 +4,13 @@ import android.content.Context import android.content.pm.PackageInfo import android.os.Build import com.aheaditec.talsec_security.security.api.ExternalIdResult +import com.aheaditec.talsec_security.security.api.MalwareScanScope +import com.aheaditec.talsec_security.security.api.ReasonMode +import com.aheaditec.talsec_security.security.api.ScopeType +import com.aheaditec.talsec_security.security.api.SuspiciousAppDetectionConfig import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo import io.flutter.plugin.common.MethodChannel +import org.json.JSONObject import com.aheaditec.freerasp.generated.PackageInfo as FlutterPackageInfo import com.aheaditec.freerasp.generated.SuspiciousAppInfo as FlutterSuspiciousAppInfo @@ -33,7 +38,7 @@ internal inline fun runResultCatching(result: MethodChannel.Result, block: () -> * this [SuspiciousAppInfo]. */ internal fun SuspiciousAppInfo.toPigeon(context: Context): FlutterSuspiciousAppInfo { - return FlutterSuspiciousAppInfo(this.packageInfo.toPigeon(context), this.reason) + return FlutterSuspiciousAppInfo(this.packageInfo.toPigeon(context), this.reasons.toList()) } /** @@ -83,3 +88,48 @@ internal fun ExternalIdResult.resolve(result: MethodChannel.Result) { is ExternalIdResult.Error -> result.error("external-id-failure", this.errorMsg, null) } } + +internal fun JSONObject.toMalwareScanScope(): MalwareScanScope { + val scopeTypeStr = optString("scanScope", "SIDELOADED_ONLY") + val scanScope = runCatching { ScopeType.valueOf(scopeTypeStr) }.getOrDefault(ScopeType.SIDELOADED_ONLY) + val trustedInstallSources = optJSONArray("trustedInstallSources")?.let { arr -> + (0 until arr.length()).map { arr.getString(it) }.toSet() + } + return MalwareScanScope(scanScope = scanScope, trustedInstallSources = trustedInstallSources) +} + +internal fun JSONObject.toSuspiciousAppDetectionConfig(): SuspiciousAppDetectionConfig { + val packageNames = optJSONArray("packageNames")?.let { arr -> + (0 until arr.length()).map { arr.getString(it) }.toSet() + } + val hashes = optJSONArray("hashes")?.let { arr -> + (0 until arr.length()).map { arr.getString(it) }.toSet() + } + val requestedPermissions = optJSONArray("requestedPermissions")?.let { outer -> + (0 until outer.length()).map { i -> + val inner = outer.getJSONArray(i) + (0 until inner.length()).map { j -> inner.getString(j) }.toSet() + }.toSet() + } + val grantedPermissions = optJSONArray("grantedPermissions")?.let { outer -> + (0 until outer.length()).map { i -> + val inner = outer.getJSONArray(i) + (0 until inner.length()).map { j -> inner.getString(j) }.toSet() + }.toSet() + } + val malwareScanScope = optJSONObject("malwareScanScope")?.toMalwareScanScope() + val reasonModeStr = optString("reasonMode") + val reasonMode = if (reasonModeStr.isNullOrEmpty()) { + ReasonMode.HIGHEST_CONFIDENCE + } else { + runCatching { ReasonMode.valueOf(reasonModeStr) }.getOrDefault(ReasonMode.HIGHEST_CONFIDENCE) + } + return SuspiciousAppDetectionConfig( + packageNames = packageNames, + hashes = hashes, + requestedPermissions = requestedPermissions, + grantedPermissions = grantedPermissions, + malwareScanScope = malwareScanScope, + reasonMode = reasonMode, + ) +} diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/generated/TalsecPigeonApi.kt b/android/src/main/kotlin/com/aheaditec/freerasp/generated/TalsecPigeonApi.kt index 05bae9b..3ae9472 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/generated/TalsecPigeonApi.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/generated/TalsecPigeonApi.kt @@ -62,20 +62,20 @@ data class PackageInfo ( /** Generated class from Pigeon that represents data sent in messages. */ data class SuspiciousAppInfo ( val packageInfo: PackageInfo, - val reason: String + val reasons: List ) { companion object { fun fromList(pigeonVar_list: List): SuspiciousAppInfo { val packageInfo = pigeonVar_list[0] as PackageInfo - val reason = pigeonVar_list[1] as String - return SuspiciousAppInfo(packageInfo, reason) + val reasons = pigeonVar_list[1] as List + return SuspiciousAppInfo(packageInfo, reasons) } } fun toList(): List { return listOf( packageInfo, - reason, + reasons, ) } } diff --git a/example/lib/widgets/malware_bottom_sheet.dart b/example/lib/widgets/malware_bottom_sheet.dart index 04abf12..efcb27b 100644 --- a/example/lib/widgets/malware_bottom_sheet.dart +++ b/example/lib/widgets/malware_bottom_sheet.dart @@ -75,7 +75,7 @@ class MalwareListTile extends StatelessWidget { return ListTile( title: Text(malware.packageInfo.packageName), - subtitle: Text('Reason: ${malware.reason}'), + subtitle: Text('Reasons: ${malware.reasons.join(', ')}'), leading: appIcon, ); }, diff --git a/lib/src/generated/talsec_pigeon_api.g.dart b/lib/src/generated/talsec_pigeon_api.g.dart index fec170b..a6e3a50 100644 --- a/lib/src/generated/talsec_pigeon_api.g.dart +++ b/lib/src/generated/talsec_pigeon_api.g.dart @@ -63,17 +63,17 @@ class PackageInfo { class SuspiciousAppInfo { SuspiciousAppInfo({ required this.packageInfo, - required this.reason, + required this.reasons, }); PackageInfo packageInfo; - String reason; + List reasons; Object encode() { return [ packageInfo, - reason, + reasons, ]; } @@ -81,7 +81,7 @@ class SuspiciousAppInfo { result as List; return SuspiciousAppInfo( packageInfo: result[0]! as PackageInfo, - reason: result[1]! as String, + reasons: (result[1] as List?)!.cast(), ); } } diff --git a/pigeons/talsec_pigeon_api.dart b/pigeons/talsec_pigeon_api.dart index 186abea..1c07b00 100644 --- a/pigeons/talsec_pigeon_api.dart +++ b/pigeons/talsec_pigeon_api.dart @@ -28,11 +28,11 @@ class PackageInfo { class SuspiciousAppInfo { const SuspiciousAppInfo({ required this.packageInfo, - required this.reason, + required this.reasons, }); final PackageInfo packageInfo; - final String reason; + final List reasons; } @FlutterApi() From bd7894bd829a427fc9f20e1209e54949a7bc0d9e Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Thu, 7 May 2026 16:25:57 +0200 Subject: [PATCH 03/20] feat!: deprecate old malware config fields --- lib/src/models/android_config.dart | 5 +++++ lib/src/models/malware_config.dart | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/src/models/android_config.dart b/lib/src/models/android_config.dart index a613445..1cf6715 100644 --- a/lib/src/models/android_config.dart +++ b/lib/src/models/android_config.dart @@ -13,6 +13,7 @@ class AndroidConfig { required this.signingCertHashes, this.supportedStores = const [], this.malwareConfig, + this.suspiciousAppDetectionConfig, }) { ConfigVerifier.verifyAndroid(this); } @@ -34,5 +35,9 @@ class AndroidConfig { final List supportedStores; /// Malware configuration for Android. + @Deprecated('Use suspiciousAppDetectionConfig instead') final MalwareConfig? malwareConfig; + + /// Suspicious app detection configuration for Android. + final SuspiciousAppDetectionConfig? suspiciousAppDetectionConfig; } diff --git a/lib/src/models/malware_config.dart b/lib/src/models/malware_config.dart index 417d797..c520b5b 100644 --- a/lib/src/models/malware_config.dart +++ b/lib/src/models/malware_config.dart @@ -7,9 +7,13 @@ part 'malware_config.g.dart'; class MalwareConfig { /// Creates a new instance of [MalwareConfig]. MalwareConfig({ + @Deprecated('Use SuspiciousAppDetectionConfig instead') this.blacklistedPackageNames = const [], + @Deprecated('Use SuspiciousAppDetectionConfig instead') this.blacklistedHashes = const [], + @Deprecated('Use SuspiciousAppDetectionConfig instead') this.suspiciousPermissions = const [], + @Deprecated('Use SuspiciousAppDetectionConfig instead') this.whitelistedInstallationSources = const [], }); @@ -21,14 +25,18 @@ class MalwareConfig { Map toJson() => _$MalwareConfigToJson(this); /// List of blocklisted applications with given package name. + @Deprecated('Use SuspiciousAppDetectionConfig instead') final List blacklistedPackageNames; /// List of blocklisted applications with given hash. + @Deprecated('Use SuspiciousAppDetectionConfig instead') final List blacklistedHashes; /// List of blocklisted applications with given permissions. + @Deprecated('Use SuspiciousAppDetectionConfig instead') final List> suspiciousPermissions; /// List of whitelisted installation sources. + @Deprecated('Use SuspiciousAppDetectionConfig instead') final List whitelistedInstallationSources; } From 56487c37465adffab5a4abfb73f7d4c792a8223b Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Thu, 7 May 2026 16:26:02 +0200 Subject: [PATCH 04/20] feat: add SuspiciousAppDetectionConfig to API --- example/lib/main.dart | 10 +- lib/src/models/android_config.g.dart | 10 +- lib/src/models/models.dart | 1 + .../suspicious_app_detection_config.dart | 92 +++++++++++++++++++ .../suspicious_app_detection_config.g.dart | 92 +++++++++++++++++++ 5 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 lib/src/models/suspicious_app_detection_config.dart create mode 100644 lib/src/models/suspicious_app_detection_config.g.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 7a9311f..378a21e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -38,12 +38,16 @@ Future _initializeTalsec() async { packageName: 'com.aheaditec.freeraspExample', signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], supportedStores: ['com.sec.android.app.samsungapps'], - malwareConfig: MalwareConfig( - blacklistedPackageNames: ['com.aheaditec.freeraspExample'], - suspiciousPermissions: [ + suspiciousAppDetectionConfig: SuspiciousAppDetectionConfig( + packageNames: ['com.aheaditec.freeraspExample'], + requestedPermissions: [ ['android.permission.CAMERA'], ['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'], ], + malwareScanScope: MalwareScanScope( + scanScope: ScopeType.sideloadedOnly, + ), + reasonMode: ReasonMode.highestConfidence, ), ), iosConfig: IOSConfig( diff --git a/lib/src/models/android_config.g.dart b/lib/src/models/android_config.g.dart index ed4dc50..a0c415e 100644 --- a/lib/src/models/android_config.g.dart +++ b/lib/src/models/android_config.g.dart @@ -6,6 +6,7 @@ part of 'android_config.dart'; // JsonSerializableGenerator // ************************************************************************** +// ignore: deprecated_member_use_from_same_package AndroidConfig _$AndroidConfigFromJson(Map json) => AndroidConfig( packageName: json['packageName'] as String, @@ -20,6 +21,10 @@ AndroidConfig _$AndroidConfigFromJson(Map json) => ? null : MalwareConfig.fromJson( json['malwareConfig'] as Map), + suspiciousAppDetectionConfig: json['suspiciousAppDetectionConfig'] == null + ? null + : SuspiciousAppDetectionConfig.fromJson( + json['suspiciousAppDetectionConfig'] as Map), ); Map _$AndroidConfigToJson(AndroidConfig instance) { @@ -35,6 +40,9 @@ Map _$AndroidConfigToJson(AndroidConfig instance) { } } - writeNotNull('malwareConfig', instance.malwareConfig); + // ignore: deprecated_member_use_from_same_package + writeNotNull('malwareConfig', instance.malwareConfig?.toJson()); + writeNotNull('suspiciousAppDetectionConfig', + instance.suspiciousAppDetectionConfig?.toJson()); return val; } diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 8b19213..7f5199a 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -1,4 +1,5 @@ export 'android_config.dart'; export 'ios_config.dart'; export 'malware_config.dart'; +export 'suspicious_app_detection_config.dart'; export 'talsec_config.dart'; diff --git a/lib/src/models/suspicious_app_detection_config.dart b/lib/src/models/suspicious_app_detection_config.dart new file mode 100644 index 0000000..351e881 --- /dev/null +++ b/lib/src/models/suspicious_app_detection_config.dart @@ -0,0 +1,92 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'suspicious_app_detection_config.g.dart'; + +/// The scope of apps to be scanned for malware. +enum ScopeType { + /// Only sideloaded apps. + sideloadedOnly, + + /// Sideloaded and system apps, excluding OEM apps. + sideloadedAndSystemExcludeOem, + + /// Sideloaded and OEM apps. + sideloadedAndOem, + + /// Sideloaded, system, and OEM apps. + sideloadedAndSystemAndOem, + + /// All apps. + all, +} + +/// The mode for reporting malware detection reasons. +enum ReasonMode { + /// Report all reasons. + all, + + /// Report only the highest confidence reason. + highestConfidence, +} + +/// Configuration for malware scan scope and trusted install sources. +@JsonSerializable(includeIfNull: false) +class MalwareScanScope { + /// Creates a new instance of [MalwareScanScope]. + const MalwareScanScope({ + required this.scanScope, + this.trustedInstallSources, + }); + + /// Converts from json + factory MalwareScanScope.fromJson(Map json) => + _$MalwareScanScopeFromJson(json); + + /// Converts to json + Map toJson() => _$MalwareScanScopeToJson(this); + + /// The scope of apps to be scanned. + final ScopeType scanScope; + + /// List of trusted install sources. + final List? trustedInstallSources; +} + +/// Configuration for suspicious app detection. +@JsonSerializable(includeIfNull: false) +class SuspiciousAppDetectionConfig { + /// Creates a new instance of [SuspiciousAppDetectionConfig]. + const SuspiciousAppDetectionConfig({ + this.packageNames, + this.hashes, + this.requestedPermissions, + this.grantedPermissions, + this.malwareScanScope, + this.reasonMode, + }); + + /// Converts from json + factory SuspiciousAppDetectionConfig.fromJson(Map json) => + _$SuspiciousAppDetectionConfigFromJson(json); + + /// Converts to json + Map toJson() => _$SuspiciousAppDetectionConfigToJson(this); + + /// List of suspicious package names to detect. + final List? packageNames; + + /// List of suspicious app hashes to detect. + final List? hashes; + + /// Sets of requested permissions that indicate a suspicious app. + final List>? requestedPermissions; + + /// Sets of granted permissions that indicate a suspicious app. + final List>? grantedPermissions; + + /// Configuration for the malware scan scope. + final MalwareScanScope? malwareScanScope; + + /// The mode for reporting detection reasons. + final ReasonMode? reasonMode; +} diff --git a/lib/src/models/suspicious_app_detection_config.g.dart b/lib/src/models/suspicious_app_detection_config.g.dart new file mode 100644 index 0000000..8a5009b --- /dev/null +++ b/lib/src/models/suspicious_app_detection_config.g.dart @@ -0,0 +1,92 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'suspicious_app_detection_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +const _$ScopeTypeEnumMap = { + ScopeType.sideloadedOnly: 'SIDELOADED_ONLY', + ScopeType.sideloadedAndSystemExcludeOem: 'SIDELOADED_AND_SYSTEM_EXCLUDE_OEM', + ScopeType.sideloadedAndOem: 'SIDELOADED_AND_OEM', + ScopeType.sideloadedAndSystemAndOem: 'SIDELOADED_AND_SYSTEM_AND_OEM', + ScopeType.all: 'ALL', +}; + +const _$ReasonModeEnumMap = { + ReasonMode.all: 'ALL', + ReasonMode.highestConfidence: 'HIGHEST_CONFIDENCE', +}; + +MalwareScanScope _$MalwareScanScopeFromJson(Map json) => + MalwareScanScope( + scanScope: $enumDecode(_$ScopeTypeEnumMap, json['scanScope']), + trustedInstallSources: (json['trustedInstallSources'] as List?) + ?.map((e) => e as String) + .toList(), + ); + +Map _$MalwareScanScopeToJson(MalwareScanScope instance) { + final val = { + 'scanScope': _$ScopeTypeEnumMap[instance.scanScope]!, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('trustedInstallSources', instance.trustedInstallSources); + return val; +} + +SuspiciousAppDetectionConfig _$SuspiciousAppDetectionConfigFromJson( + Map json) => + SuspiciousAppDetectionConfig( + packageNames: (json['packageNames'] as List?) + ?.map((e) => e as String) + .toList(), + hashes: (json['hashes'] as List?) + ?.map((e) => e as String) + .toList(), + requestedPermissions: (json['requestedPermissions'] as List?) + ?.map( + (e) => (e as List).map((e) => e as String).toList(), + ) + .toList(), + grantedPermissions: (json['grantedPermissions'] as List?) + ?.map( + (e) => (e as List).map((e) => e as String).toList(), + ) + .toList(), + malwareScanScope: json['malwareScanScope'] == null + ? null + : MalwareScanScope.fromJson( + json['malwareScanScope'] as Map), + reasonMode: $enumDecodeNullable(_$ReasonModeEnumMap, json['reasonMode']), + ); + +Map _$SuspiciousAppDetectionConfigToJson( + SuspiciousAppDetectionConfig instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('packageNames', instance.packageNames); + writeNotNull('hashes', instance.hashes); + writeNotNull('requestedPermissions', instance.requestedPermissions); + writeNotNull('grantedPermissions', instance.grantedPermissions); + writeNotNull('malwareScanScope', instance.malwareScanScope?.toJson()); + writeNotNull( + 'reasonMode', + instance.reasonMode == null + ? null + : _$ReasonModeEnumMap[instance.reasonMode!]); + return val; +} From ba56ae02d8602bea9c4c9967441a714dafe6e43a Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Thu, 7 May 2026 16:26:12 +0200 Subject: [PATCH 05/20] refactor: extract SuspiciousAppDetectionConfig parsing to utils --- .../src/main/kotlin/com/aheaditec/freerasp/Utils.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt b/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt index 6b2c110..897e40f 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt @@ -38,7 +38,7 @@ internal object Utils { val alternativeStores = androidConfig.extractArray("supportedStores") val malwareConfig = parseMalwareConfig(androidConfig) - return TalsecConfig.Builder(packageName, certificateHashes) + val builder = TalsecConfig.Builder(packageName, certificateHashes) .watcherMail(watcherMail) .supportedAlternativeStores(alternativeStores) .prod(isProd) @@ -47,7 +47,12 @@ internal object Utils { .blacklistedHashes(malwareConfig.blacklistedHashes) .suspiciousPermissions(malwareConfig.suspiciousPermissions) .whitelistedInstallationSources(malwareConfig.whitelistedInstallationSources) - .build() + + androidConfig.optJSONObject("suspiciousAppDetectionConfig")?.let { + builder.suspiciousAppDetection(it.toSuspiciousAppDetectionConfig()) + } + + return builder.build() } private fun parseMalwareConfig(androidConfig: JSONObject): MalwareConfig { @@ -175,4 +180,4 @@ private inline fun processArray(jsonArray: JSONArray): Array { } return list.toTypedArray() -} \ No newline at end of file +} From 690905498fa3295c1aac74e4da6843f005f67751 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Thu, 7 May 2026 16:27:28 +0200 Subject: [PATCH 06/20] chore: update changelog for 7.6.0 --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd06d3d..596435a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [7.6.0] - 2026-05-07 + +- Android SDK version: 18.3.0 +- iOS SDK version: 6.14.4 + +### Breaking + +- `SuspiciousAppInfo.reason` (String) renamed to `reasons` (List\) +- Value `"blacklist"` in `reasons` renamed to `"blocklist"` + +### Flutter + +#### Deprecated + +- `blacklistedPackageNames`, `blacklistedHashes`, `suspiciousPermissions`, `whitelistedInstallationSources` are deprecated but remain functional — use `SuspiciousAppDetectionConfig` instead + +### Android + +#### Added + +- Added a new sub-check for `HMA` detection to the root detector +- Added a new sub-check for `KernelSU` detection to the root detector +- Added a new sub-check for `Frida Server` detection to the hook detector +- Added Huawei App Market provider to HMA detection queries +- New API class `SuspiciousAppDetectionConfig` that can be used to configure malware detection +- New API for malware detection configuration in `TalsecConfig`, see `TalsecConfig.Builder#suspiciousAppDetection` + +#### Fixed + +- Fixed `VerifyError` caused by `JaCoCo` bytecode instrumentation +- Fixed a potential cause of crash in the multi-instance detector +- Fixed crash caused by unhandled `SecurityException` thrown by `UsageStatsManager` in root detection +- Fixed manifest merge conflicts in HMA detection providers +- Fixed Java interoperability of `ScreenProtector` methods +- Fixed Kotlin classpath conflicts in SDK dependency resolution (Kotlin 2.0.0) + +#### Changed + +- Fine-tuned `KernelSU` detection +- Fine-tuned hook detection +- Fine-tuned location spoofing detection +- Modified malware incident log structure for better aggregation +- Old malware configuration API methods in `TalsecConfig.Builder` tagged as deprecated (but remain functional): `blacklistedPackageNames`, `blacklistedHashes`, `suspiciousPermissions`, `whitelistedInstallationSources` + ## [7.5.1] - 2026-03-24 - Android SDK version: 18.0.4 From be0649a60094542042e297490b35a503f51ba7a3 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Mon, 11 May 2026 11:07:36 +0200 Subject: [PATCH 07/20] style: format suspicious_app_detection_config.g.dart --- lib/src/models/suspicious_app_detection_config.g.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/models/suspicious_app_detection_config.g.dart b/lib/src/models/suspicious_app_detection_config.g.dart index 8a5009b..28f640a 100644 --- a/lib/src/models/suspicious_app_detection_config.g.dart +++ b/lib/src/models/suspicious_app_detection_config.g.dart @@ -48,9 +48,8 @@ SuspiciousAppDetectionConfig _$SuspiciousAppDetectionConfigFromJson( packageNames: (json['packageNames'] as List?) ?.map((e) => e as String) .toList(), - hashes: (json['hashes'] as List?) - ?.map((e) => e as String) - .toList(), + hashes: + (json['hashes'] as List?)?.map((e) => e as String).toList(), requestedPermissions: (json['requestedPermissions'] as List?) ?.map( (e) => (e as List).map((e) => e as String).toList(), From e4258fc62e8c0dd82b774198b42b0ce888d15a58 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Mon, 11 May 2026 14:12:23 +0200 Subject: [PATCH 08/20] fix: resolve lint issues - deprecated_consistency and prefer_const_constructors --- example/lib/main.dart | 2 +- lib/src/models/android_config.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 378a21e..59ea3e1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -38,7 +38,7 @@ Future _initializeTalsec() async { packageName: 'com.aheaditec.freeraspExample', signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], supportedStores: ['com.sec.android.app.samsungapps'], - suspiciousAppDetectionConfig: SuspiciousAppDetectionConfig( + suspiciousAppDetectionConfig: const SuspiciousAppDetectionConfig( packageNames: ['com.aheaditec.freeraspExample'], requestedPermissions: [ ['android.permission.CAMERA'], diff --git a/lib/src/models/android_config.dart b/lib/src/models/android_config.dart index 1cf6715..fba9372 100644 --- a/lib/src/models/android_config.dart +++ b/lib/src/models/android_config.dart @@ -12,6 +12,7 @@ class AndroidConfig { required this.packageName, required this.signingCertHashes, this.supportedStores = const [], + @Deprecated('Use suspiciousAppDetectionConfig instead') this.malwareConfig, this.suspiciousAppDetectionConfig, }) { From ba45995a5caecae05d032e93e7048b40d1096fc1 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Mon, 11 May 2026 14:15:34 +0200 Subject: [PATCH 09/20] style: format android_config.dart --- lib/src/models/android_config.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/models/android_config.dart b/lib/src/models/android_config.dart index fba9372..e4685f7 100644 --- a/lib/src/models/android_config.dart +++ b/lib/src/models/android_config.dart @@ -12,8 +12,7 @@ class AndroidConfig { required this.packageName, required this.signingCertHashes, this.supportedStores = const [], - @Deprecated('Use suspiciousAppDetectionConfig instead') - this.malwareConfig, + @Deprecated('Use suspiciousAppDetectionConfig instead') this.malwareConfig, this.suspiciousAppDetectionConfig, }) { ConfigVerifier.verifyAndroid(this); From cccbd74df6d3799fadecf9cd46b9c36f2a3daa10 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Tue, 12 May 2026 14:10:54 +0200 Subject: [PATCH 10/20] fix: use positional args and correct types for MalwareScanScope and SuspiciousAppDetectionConfig --- .../kotlin/com/aheaditec/freerasp/Extensions.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt b/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt index 6b667a5..9fafd14 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt @@ -93,9 +93,9 @@ internal fun JSONObject.toMalwareScanScope(): MalwareScanScope { val scopeTypeStr = optString("scanScope", "SIDELOADED_ONLY") val scanScope = runCatching { ScopeType.valueOf(scopeTypeStr) }.getOrDefault(ScopeType.SIDELOADED_ONLY) val trustedInstallSources = optJSONArray("trustedInstallSources")?.let { arr -> - (0 until arr.length()).map { arr.getString(it) }.toSet() + (0 until arr.length()).map { arr.getString(it) } } - return MalwareScanScope(scanScope = scanScope, trustedInstallSources = trustedInstallSources) + return MalwareScanScope(scanScope, trustedInstallSources) } internal fun JSONObject.toSuspiciousAppDetectionConfig(): SuspiciousAppDetectionConfig { @@ -118,18 +118,12 @@ internal fun JSONObject.toSuspiciousAppDetectionConfig(): SuspiciousAppDetection }.toSet() } val malwareScanScope = optJSONObject("malwareScanScope")?.toMalwareScanScope() + ?: MalwareScanScope(ScopeType.SIDELOADED_ONLY, emptyList()) val reasonModeStr = optString("reasonMode") val reasonMode = if (reasonModeStr.isNullOrEmpty()) { ReasonMode.HIGHEST_CONFIDENCE } else { runCatching { ReasonMode.valueOf(reasonModeStr) }.getOrDefault(ReasonMode.HIGHEST_CONFIDENCE) } - return SuspiciousAppDetectionConfig( - packageNames = packageNames, - hashes = hashes, - requestedPermissions = requestedPermissions, - grantedPermissions = grantedPermissions, - malwareScanScope = malwareScanScope, - reasonMode = reasonMode, - ) + return SuspiciousAppDetectionConfig(packageNames, hashes, requestedPermissions, grantedPermissions, malwareScanScope, reasonMode) } From 393fa7036937735740e181da1e2c696d5b9bf4a9 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Wed, 13 May 2026 16:52:13 +0200 Subject: [PATCH 11/20] feat!: remove deprecated MalwareConfig API --- .../kotlin/com/aheaditec/freerasp/Utils.kt | 29 ------------- lib/src/models/android_config.dart | 5 --- lib/src/models/android_config.g.dart | 7 ---- lib/src/models/malware_config.dart | 42 ------------------- lib/src/models/malware_config.g.dart | 38 ----------------- lib/src/models/models.dart | 1 - 6 files changed, 122 deletions(-) delete mode 100644 lib/src/models/malware_config.dart delete mode 100644 lib/src/models/malware_config.g.dart diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt b/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt index 897e40f..c91d022 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt @@ -14,14 +14,6 @@ import org.json.JSONObject import java.io.ByteArrayOutputStream internal object Utils { - @Suppress("ArrayInDataClass") - data class MalwareConfig( - val blacklistedPackageNames: Array, - val blacklistedHashes: Array, - val suspiciousPermissions: Array>, - val whitelistedInstallationSources: Array - ) - fun toTalsecConfigThrowing(configJson: String?): TalsecConfig { if (configJson == null) { throw JSONException("Configuration is null") @@ -36,17 +28,12 @@ internal object Utils { val packageName = androidConfig.getString("packageName") val certificateHashes = androidConfig.extractArray("signingCertHashes") val alternativeStores = androidConfig.extractArray("supportedStores") - val malwareConfig = parseMalwareConfig(androidConfig) val builder = TalsecConfig.Builder(packageName, certificateHashes) .watcherMail(watcherMail) .supportedAlternativeStores(alternativeStores) .prod(isProd) .killOnBypass(killOnBypass) - .blacklistedPackageNames(malwareConfig.blacklistedPackageNames) - .blacklistedHashes(malwareConfig.blacklistedHashes) - .suspiciousPermissions(malwareConfig.suspiciousPermissions) - .whitelistedInstallationSources(malwareConfig.whitelistedInstallationSources) androidConfig.optJSONObject("suspiciousAppDetectionConfig")?.let { builder.suspiciousAppDetection(it.toSuspiciousAppDetectionConfig()) @@ -55,22 +42,6 @@ internal object Utils { return builder.build() } - private fun parseMalwareConfig(androidConfig: JSONObject): MalwareConfig { - if (!androidConfig.has("malwareConfig")) { - return MalwareConfig(emptyArray(), emptyArray(), emptyArray(), emptyArray()) - } - - val malwareConfig = androidConfig.getJSONObject("malwareConfig") - - return MalwareConfig( - malwareConfig.extractArray("blacklistedPackageNames"), - malwareConfig.extractArray("blacklistedHashes"), - malwareConfig.extractArray>("suspiciousPermissions"), - malwareConfig.extractArray("whitelistedInstallationSources") - ) - } - - /** * Retrieves the package name of the installer for a given app package. * diff --git a/lib/src/models/android_config.dart b/lib/src/models/android_config.dart index e4685f7..5543259 100644 --- a/lib/src/models/android_config.dart +++ b/lib/src/models/android_config.dart @@ -12,7 +12,6 @@ class AndroidConfig { required this.packageName, required this.signingCertHashes, this.supportedStores = const [], - @Deprecated('Use suspiciousAppDetectionConfig instead') this.malwareConfig, this.suspiciousAppDetectionConfig, }) { ConfigVerifier.verifyAndroid(this); @@ -34,10 +33,6 @@ class AndroidConfig { /// List of supported sources where application can be installed from. final List supportedStores; - /// Malware configuration for Android. - @Deprecated('Use suspiciousAppDetectionConfig instead') - final MalwareConfig? malwareConfig; - /// Suspicious app detection configuration for Android. final SuspiciousAppDetectionConfig? suspiciousAppDetectionConfig; } diff --git a/lib/src/models/android_config.g.dart b/lib/src/models/android_config.g.dart index a0c415e..43cb86f 100644 --- a/lib/src/models/android_config.g.dart +++ b/lib/src/models/android_config.g.dart @@ -6,7 +6,6 @@ part of 'android_config.dart'; // JsonSerializableGenerator // ************************************************************************** -// ignore: deprecated_member_use_from_same_package AndroidConfig _$AndroidConfigFromJson(Map json) => AndroidConfig( packageName: json['packageName'] as String, @@ -17,10 +16,6 @@ AndroidConfig _$AndroidConfigFromJson(Map json) => ?.map((e) => e as String) .toList() ?? const [], - malwareConfig: json['malwareConfig'] == null - ? null - : MalwareConfig.fromJson( - json['malwareConfig'] as Map), suspiciousAppDetectionConfig: json['suspiciousAppDetectionConfig'] == null ? null : SuspiciousAppDetectionConfig.fromJson( @@ -40,8 +35,6 @@ Map _$AndroidConfigToJson(AndroidConfig instance) { } } - // ignore: deprecated_member_use_from_same_package - writeNotNull('malwareConfig', instance.malwareConfig?.toJson()); writeNotNull('suspiciousAppDetectionConfig', instance.suspiciousAppDetectionConfig?.toJson()); return val; diff --git a/lib/src/models/malware_config.dart b/lib/src/models/malware_config.dart deleted file mode 100644 index c520b5b..0000000 --- a/lib/src/models/malware_config.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'malware_config.g.dart'; - -/// Configuration for malware detection. -@JsonSerializable() -class MalwareConfig { - /// Creates a new instance of [MalwareConfig]. - MalwareConfig({ - @Deprecated('Use SuspiciousAppDetectionConfig instead') - this.blacklistedPackageNames = const [], - @Deprecated('Use SuspiciousAppDetectionConfig instead') - this.blacklistedHashes = const [], - @Deprecated('Use SuspiciousAppDetectionConfig instead') - this.suspiciousPermissions = const [], - @Deprecated('Use SuspiciousAppDetectionConfig instead') - this.whitelistedInstallationSources = const [], - }); - - /// Converts config from json - factory MalwareConfig.fromJson(Map json) => - _$MalwareConfigFromJson(json); - - /// Converts config to json - Map toJson() => _$MalwareConfigToJson(this); - - /// List of blocklisted applications with given package name. - @Deprecated('Use SuspiciousAppDetectionConfig instead') - final List blacklistedPackageNames; - - /// List of blocklisted applications with given hash. - @Deprecated('Use SuspiciousAppDetectionConfig instead') - final List blacklistedHashes; - - /// List of blocklisted applications with given permissions. - @Deprecated('Use SuspiciousAppDetectionConfig instead') - final List> suspiciousPermissions; - - /// List of whitelisted installation sources. - @Deprecated('Use SuspiciousAppDetectionConfig instead') - final List whitelistedInstallationSources; -} diff --git a/lib/src/models/malware_config.g.dart b/lib/src/models/malware_config.g.dart deleted file mode 100644 index dbc31e4..0000000 --- a/lib/src/models/malware_config.g.dart +++ /dev/null @@ -1,38 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'malware_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -MalwareConfig _$MalwareConfigFromJson(Map json) => - MalwareConfig( - blacklistedPackageNames: - (json['blacklistedPackageNames'] as List?) - ?.map((e) => e as String) - .toList() ?? - const [], - blacklistedHashes: (json['blacklistedHashes'] as List?) - ?.map((e) => e as String) - .toList() ?? - const [], - suspiciousPermissions: (json['suspiciousPermissions'] as List?) - ?.map( - (e) => (e as List).map((e) => e as String).toList()) - .toList() ?? - const [], - whitelistedInstallationSources: - (json['whitelistedInstallationSources'] as List?) - ?.map((e) => e as String) - .toList() ?? - const [], - ); - -Map _$MalwareConfigToJson(MalwareConfig instance) => - { - 'blacklistedPackageNames': instance.blacklistedPackageNames, - 'blacklistedHashes': instance.blacklistedHashes, - 'suspiciousPermissions': instance.suspiciousPermissions, - 'whitelistedInstallationSources': instance.whitelistedInstallationSources, - }; diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 7f5199a..2db18eb 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -1,5 +1,4 @@ export 'android_config.dart'; export 'ios_config.dart'; -export 'malware_config.dart'; export 'suspicious_app_detection_config.dart'; export 'talsec_config.dart'; From 8cf8248c98deefa7dc503af611b2b8d6ee0f8ba1 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Wed, 13 May 2026 16:52:30 +0200 Subject: [PATCH 12/20] refactor: tighten SuspiciousAppDetectionConfig parsing --- .../com/aheaditec/freerasp/Extensions.kt | 54 ++++++++----------- .../kotlin/com/aheaditec/freerasp/Utils.kt | 4 +- .../suspicious_app_detection_config.dart | 9 ++-- .../suspicious_app_detection_config.g.dart | 14 +++-- 4 files changed, 35 insertions(+), 46 deletions(-) diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt b/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt index 9fafd14..29da4c9 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt @@ -10,6 +10,7 @@ import com.aheaditec.talsec_security.security.api.ScopeType import com.aheaditec.talsec_security.security.api.SuspiciousAppDetectionConfig import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo import io.flutter.plugin.common.MethodChannel +import org.json.JSONArray import org.json.JSONObject import com.aheaditec.freerasp.generated.PackageInfo as FlutterPackageInfo import com.aheaditec.freerasp.generated.SuspiciousAppInfo as FlutterSuspiciousAppInfo @@ -90,40 +91,29 @@ internal fun ExternalIdResult.resolve(result: MethodChannel.Result) { } internal fun JSONObject.toMalwareScanScope(): MalwareScanScope { - val scopeTypeStr = optString("scanScope", "SIDELOADED_ONLY") - val scanScope = runCatching { ScopeType.valueOf(scopeTypeStr) }.getOrDefault(ScopeType.SIDELOADED_ONLY) - val trustedInstallSources = optJSONArray("trustedInstallSources")?.let { arr -> - (0 until arr.length()).map { arr.getString(it) } - } + val scanScope = ScopeType.valueOf(getString("scanScope")) + val trustedInstallSources = optJSONArray("trustedInstallSources") + ?.let { processArray(it).asList() } return MalwareScanScope(scanScope, trustedInstallSources) } internal fun JSONObject.toSuspiciousAppDetectionConfig(): SuspiciousAppDetectionConfig { - val packageNames = optJSONArray("packageNames")?.let { arr -> - (0 until arr.length()).map { arr.getString(it) }.toSet() - } - val hashes = optJSONArray("hashes")?.let { arr -> - (0 until arr.length()).map { arr.getString(it) }.toSet() - } - val requestedPermissions = optJSONArray("requestedPermissions")?.let { outer -> - (0 until outer.length()).map { i -> - val inner = outer.getJSONArray(i) - (0 until inner.length()).map { j -> inner.getString(j) }.toSet() - }.toSet() - } - val grantedPermissions = optJSONArray("grantedPermissions")?.let { outer -> - (0 until outer.length()).map { i -> - val inner = outer.getJSONArray(i) - (0 until inner.length()).map { j -> inner.getString(j) }.toSet() - }.toSet() - } - val malwareScanScope = optJSONObject("malwareScanScope")?.toMalwareScanScope() - ?: MalwareScanScope(ScopeType.SIDELOADED_ONLY, emptyList()) - val reasonModeStr = optString("reasonMode") - val reasonMode = if (reasonModeStr.isNullOrEmpty()) { - ReasonMode.HIGHEST_CONFIDENCE - } else { - runCatching { ReasonMode.valueOf(reasonModeStr) }.getOrDefault(ReasonMode.HIGHEST_CONFIDENCE) - } - return SuspiciousAppDetectionConfig(packageNames, hashes, requestedPermissions, grantedPermissions, malwareScanScope, reasonMode) + val packageNames = optJSONArray("packageNames") + ?.let { processArray(it).toMutableSet() } + val hashes = optJSONArray("hashes") + ?.let { processArray(it).toMutableSet() } + val requestedPermissions = optJSONArray("requestedPermissions") + ?.let { processArray>(it).mapTo(mutableSetOf()) { it.toMutableSet() } } + val grantedPermissions = optJSONArray("grantedPermissions") + ?.let { processArray>(it).mapTo(mutableSetOf()) { it.toMutableSet() } } + val malwareScanScope = getJSONObject("malwareScanScope").toMalwareScanScope() + val reasonMode = ReasonMode.valueOf(getString("reasonMode")) + return SuspiciousAppDetectionConfig( + packageNames, + hashes, + requestedPermissions, + grantedPermissions, + malwareScanScope, + reasonMode, + ) } diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt b/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt index c91d022..1a805a3 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt @@ -121,11 +121,11 @@ internal object Utils { } } -private inline fun JSONObject.extractArray(key: String): Array { +internal inline fun JSONObject.extractArray(key: String): Array { return this.optJSONArray(key)?.let { processArray(it) } ?: emptyArray() } -private inline fun processArray(jsonArray: JSONArray): Array { +internal inline fun processArray(jsonArray: JSONArray): Array { val list = mutableListOf() for (i in 0 until jsonArray.length()) { diff --git a/lib/src/models/suspicious_app_detection_config.dart b/lib/src/models/suspicious_app_detection_config.dart index 351e881..1fb4d86 100644 --- a/lib/src/models/suspicious_app_detection_config.dart +++ b/lib/src/models/suspicious_app_detection_config.dart @@ -61,8 +61,9 @@ class SuspiciousAppDetectionConfig { this.hashes, this.requestedPermissions, this.grantedPermissions, - this.malwareScanScope, - this.reasonMode, + this.malwareScanScope = + const MalwareScanScope(scanScope: ScopeType.sideloadedOnly), + this.reasonMode = ReasonMode.highestConfidence, }); /// Converts from json @@ -85,8 +86,8 @@ class SuspiciousAppDetectionConfig { final List>? grantedPermissions; /// Configuration for the malware scan scope. - final MalwareScanScope? malwareScanScope; + final MalwareScanScope malwareScanScope; /// The mode for reporting detection reasons. - final ReasonMode? reasonMode; + final ReasonMode reasonMode; } diff --git a/lib/src/models/suspicious_app_detection_config.g.dart b/lib/src/models/suspicious_app_detection_config.g.dart index 28f640a..25dae2e 100644 --- a/lib/src/models/suspicious_app_detection_config.g.dart +++ b/lib/src/models/suspicious_app_detection_config.g.dart @@ -61,10 +61,12 @@ SuspiciousAppDetectionConfig _$SuspiciousAppDetectionConfigFromJson( ) .toList(), malwareScanScope: json['malwareScanScope'] == null - ? null + ? const MalwareScanScope(scanScope: ScopeType.sideloadedOnly) : MalwareScanScope.fromJson( json['malwareScanScope'] as Map), - reasonMode: $enumDecodeNullable(_$ReasonModeEnumMap, json['reasonMode']), + reasonMode: + $enumDecodeNullable(_$ReasonModeEnumMap, json['reasonMode']) ?? + ReasonMode.highestConfidence, ); Map _$SuspiciousAppDetectionConfigToJson( @@ -81,11 +83,7 @@ Map _$SuspiciousAppDetectionConfigToJson( writeNotNull('hashes', instance.hashes); writeNotNull('requestedPermissions', instance.requestedPermissions); writeNotNull('grantedPermissions', instance.grantedPermissions); - writeNotNull('malwareScanScope', instance.malwareScanScope?.toJson()); - writeNotNull( - 'reasonMode', - instance.reasonMode == null - ? null - : _$ReasonModeEnumMap[instance.reasonMode!]); + val['malwareScanScope'] = instance.malwareScanScope.toJson(); + val['reasonMode'] = _$ReasonModeEnumMap[instance.reasonMode]!; return val; } From aafe0d23943560d8cb0afecc1045a174e48fd824 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Wed, 13 May 2026 16:52:38 +0200 Subject: [PATCH 13/20] test: cover SuspiciousAppDetectionConfig and related models --- test/src/models/android_config_test.dart | 48 ++++ .../suspicious_app_detection_config_test.dart | 266 ++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 test/src/models/suspicious_app_detection_config_test.dart diff --git a/test/src/models/android_config_test.dart b/test/src/models/android_config_test.dart index 34400bf..7013c99 100644 --- a/test/src/models/android_config_test.dart +++ b/test/src/models/android_config_test.dart @@ -126,5 +126,53 @@ void main() { throwsA(isA()), ); }); + + test('Should serialize suspiciousAppDetectionConfig to JSON', () { + final config = AndroidConfig( + packageName: 'com.example.app', + signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], + suspiciousAppDetectionConfig: const SuspiciousAppDetectionConfig( + packageNames: ['com.malware.app'], + malwareScanScope: MalwareScanScope(scanScope: ScopeType.all), + reasonMode: ReasonMode.all, + ), + ); + + final json = config.toJson(); + final detection = + json['suspiciousAppDetectionConfig'] as Map; + + expect(detection['packageNames'], ['com.malware.app']); + expect( + (detection['malwareScanScope'] as Map)['scanScope'], + 'ALL', + ); + expect(detection['reasonMode'], 'ALL'); + }); + + test('Should omit suspiciousAppDetectionConfig from JSON when null', () { + final json = androidConfig.toJson(); + + expect(json.containsKey('suspiciousAppDetectionConfig'), isFalse); + }); + + test('Should deserialize suspiciousAppDetectionConfig from JSON', () { + final json = { + 'packageName': 'com.example.app', + 'signingCertHashes': ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], + 'suspiciousAppDetectionConfig': { + 'packageNames': ['com.malware.app'], + 'malwareScanScope': {'scanScope': 'SIDELOADED_AND_OEM'}, + 'reasonMode': 'ALL', + }, + }; + + final config = AndroidConfig.fromJson(json); + final detection = config.suspiciousAppDetectionConfig!; + + expect(detection.packageNames, ['com.malware.app']); + expect(detection.malwareScanScope.scanScope, ScopeType.sideloadedAndOem); + expect(detection.reasonMode, ReasonMode.all); + }); }); } diff --git a/test/src/models/suspicious_app_detection_config_test.dart b/test/src/models/suspicious_app_detection_config_test.dart new file mode 100644 index 0000000..8178561 --- /dev/null +++ b/test/src/models/suspicious_app_detection_config_test.dart @@ -0,0 +1,266 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:freerasp/freerasp.dart'; + +void main() { + group('MalwareScanScope', () { + test('Should create MalwareScanScope instance with required field only', + () { + const scope = MalwareScanScope(scanScope: ScopeType.sideloadedOnly); + + expect(scope, isA()); + expect(scope.scanScope, ScopeType.sideloadedOnly); + expect(scope.trustedInstallSources, isNull); + }); + + test('Should create MalwareScanScope instance with all fields', () { + const scope = MalwareScanScope( + scanScope: ScopeType.all, + trustedInstallSources: ['com.android.vending'], + ); + + expect(scope.scanScope, ScopeType.all); + expect(scope.trustedInstallSources, ['com.android.vending']); + }); + + test('Should convert MalwareScanScope to JSON', () { + const scope = MalwareScanScope( + scanScope: ScopeType.sideloadedAndOem, + trustedInstallSources: ['com.android.vending'], + ); + + final json = scope.toJson(); + + expect(json['scanScope'], 'SIDELOADED_AND_OEM'); + expect(json['trustedInstallSources'], ['com.android.vending']); + }); + + test('Should omit trustedInstallSources from JSON when null', () { + const scope = MalwareScanScope(scanScope: ScopeType.sideloadedOnly); + + final json = scope.toJson(); + + expect(json.containsKey('trustedInstallSources'), isFalse); + }); + + test('Should create MalwareScanScope instance from JSON', () { + final json = { + 'scanScope': 'SIDELOADED_AND_SYSTEM_EXCLUDE_OEM', + 'trustedInstallSources': ['com.android.vending'], + }; + + final scope = MalwareScanScope.fromJson(json); + + expect(scope.scanScope, ScopeType.sideloadedAndSystemExcludeOem); + expect(scope.trustedInstallSources, ['com.android.vending']); + }); + + test('Should create MalwareScanScope instance from JSON without optional', + () { + final json = {'scanScope': 'ALL'}; + + final scope = MalwareScanScope.fromJson(json); + + expect(scope.scanScope, ScopeType.all); + expect(scope.trustedInstallSources, isNull); + }); + }); + + group('SuspiciousAppDetectionConfig', () { + test('Should create instance with defaults when no args provided', () { + const config = SuspiciousAppDetectionConfig(); + + expect(config.packageNames, isNull); + expect(config.hashes, isNull); + expect(config.requestedPermissions, isNull); + expect(config.grantedPermissions, isNull); + expect(config.malwareScanScope.scanScope, ScopeType.sideloadedOnly); + expect(config.malwareScanScope.trustedInstallSources, isNull); + expect(config.reasonMode, ReasonMode.highestConfidence); + }); + + test('Should create instance with all fields populated', () { + const config = SuspiciousAppDetectionConfig( + packageNames: ['com.malware.app'], + hashes: ['abc123'], + requestedPermissions: [ + ['android.permission.CAMERA'], + ], + grantedPermissions: [ + ['android.permission.READ_SMS'], + ], + malwareScanScope: MalwareScanScope(scanScope: ScopeType.all), + reasonMode: ReasonMode.all, + ); + + expect(config.packageNames, ['com.malware.app']); + expect(config.hashes, ['abc123']); + expect(config.requestedPermissions, [ + ['android.permission.CAMERA'], + ]); + expect(config.grantedPermissions, [ + ['android.permission.READ_SMS'], + ]); + expect(config.malwareScanScope.scanScope, ScopeType.all); + expect(config.reasonMode, ReasonMode.all); + }); + + test('Should always serialize defaults to JSON', () { + const config = SuspiciousAppDetectionConfig(); + + final json = config.toJson(); + + expect(json['malwareScanScope'], isA>()); + expect( + (json['malwareScanScope'] as Map)['scanScope'], + 'SIDELOADED_ONLY', + ); + expect(json['reasonMode'], 'HIGHEST_CONFIDENCE'); + expect(json.containsKey('packageNames'), isFalse); + expect(json.containsKey('hashes'), isFalse); + expect(json.containsKey('requestedPermissions'), isFalse); + expect(json.containsKey('grantedPermissions'), isFalse); + }); + + test('Should convert SuspiciousAppDetectionConfig to JSON', () { + const config = SuspiciousAppDetectionConfig( + packageNames: ['com.malware.app'], + hashes: ['abc123'], + requestedPermissions: [ + ['android.permission.CAMERA'], + ], + grantedPermissions: [ + ['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'], + ], + malwareScanScope: MalwareScanScope( + scanScope: ScopeType.sideloadedAndSystemAndOem, + trustedInstallSources: ['com.android.vending'], + ), + reasonMode: ReasonMode.all, + ); + + final json = config.toJson(); + + expect(json['packageNames'], ['com.malware.app']); + expect(json['hashes'], ['abc123']); + expect(json['requestedPermissions'], [ + ['android.permission.CAMERA'], + ]); + expect(json['grantedPermissions'], [ + ['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'], + ]); + expect( + (json['malwareScanScope'] as Map)['scanScope'], + 'SIDELOADED_AND_SYSTEM_AND_OEM', + ); + expect(json['reasonMode'], 'ALL'); + }); + + test('Should create from JSON applying defaults for missing fields', () { + final config = SuspiciousAppDetectionConfig.fromJson({}); + + expect(config.packageNames, isNull); + expect(config.malwareScanScope.scanScope, ScopeType.sideloadedOnly); + expect(config.reasonMode, ReasonMode.highestConfidence); + }); + + test('Should create SuspiciousAppDetectionConfig from JSON', () { + final json = { + 'packageNames': ['com.malware.app'], + 'hashes': ['abc123'], + 'requestedPermissions': [ + ['android.permission.CAMERA'], + ], + 'grantedPermissions': [ + ['android.permission.READ_SMS'], + ], + 'malwareScanScope': { + 'scanScope': 'SIDELOADED_AND_OEM', + 'trustedInstallSources': ['com.android.vending'], + }, + 'reasonMode': 'ALL', + }; + + final config = SuspiciousAppDetectionConfig.fromJson(json); + + expect(config.packageNames, ['com.malware.app']); + expect(config.hashes, ['abc123']); + expect(config.requestedPermissions, [ + ['android.permission.CAMERA'], + ]); + expect(config.grantedPermissions, [ + ['android.permission.READ_SMS'], + ]); + expect(config.malwareScanScope.scanScope, ScopeType.sideloadedAndOem); + expect( + config.malwareScanScope.trustedInstallSources, + ['com.android.vending'], + ); + expect(config.reasonMode, ReasonMode.all); + }); + + test('Should round-trip through JSON without loss', () { + const original = SuspiciousAppDetectionConfig( + packageNames: ['com.malware.app'], + hashes: ['abc123'], + malwareScanScope: MalwareScanScope( + scanScope: ScopeType.sideloadedAndSystemExcludeOem, + ), + reasonMode: ReasonMode.all, + ); + + final restored = SuspiciousAppDetectionConfig.fromJson(original.toJson()); + + expect(restored.packageNames, original.packageNames); + expect(restored.hashes, original.hashes); + expect( + restored.malwareScanScope.scanScope, + original.malwareScanScope.scanScope, + ); + expect(restored.reasonMode, original.reasonMode); + }); + }); + + group('ScopeType', () { + test('Should map every value to its wire format string', () { + const expected = { + ScopeType.sideloadedOnly: 'SIDELOADED_ONLY', + ScopeType.sideloadedAndSystemExcludeOem: + 'SIDELOADED_AND_SYSTEM_EXCLUDE_OEM', + ScopeType.sideloadedAndOem: 'SIDELOADED_AND_OEM', + ScopeType.sideloadedAndSystemAndOem: 'SIDELOADED_AND_SYSTEM_AND_OEM', + ScopeType.all: 'ALL', + }; + + for (final entry in expected.entries) { + final json = + MalwareScanScope(scanScope: entry.key).toJson(); + expect(json['scanScope'], entry.value); + + final restored = + MalwareScanScope.fromJson({'scanScope': entry.value}); + expect(restored.scanScope, entry.key); + } + }); + }); + + group('ReasonMode', () { + test('Should map every value to its wire format string', () { + const expected = { + ReasonMode.all: 'ALL', + ReasonMode.highestConfidence: 'HIGHEST_CONFIDENCE', + }; + + for (final entry in expected.entries) { + final json = + SuspiciousAppDetectionConfig(reasonMode: entry.key).toJson(); + expect(json['reasonMode'], entry.value); + + final restored = SuspiciousAppDetectionConfig.fromJson({ + 'malwareScanScope': {'scanScope': 'SIDELOADED_ONLY'}, + 'reasonMode': entry.value, + }); + expect(restored.reasonMode, entry.key); + } + }); + }); +} From e0653645269169fff669a91b5c5080390993cf64 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Wed, 13 May 2026 16:52:44 +0200 Subject: [PATCH 14/20] chore: bump to 8.0.0 --- CHANGELOG.md | 8 +++++--- pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 596435a..a9cc2b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [7.6.0] - 2026-05-07 +## [8.0.0] - 2026-05-13 - Android SDK version: 18.3.0 - iOS SDK version: 6.14.4 @@ -14,12 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `SuspiciousAppInfo.reason` (String) renamed to `reasons` (List\) - Value `"blacklist"` in `reasons` renamed to `"blocklist"` +- Removed `MalwareConfig` and `AndroidConfig.malwareConfig` — use `SuspiciousAppDetectionConfig` instead +- Default `reasonMode` is now `ReasonMode.highestConfidence` — set `ReasonMode.all` to keep the previous behavior ### Flutter -#### Deprecated +#### Removed -- `blacklistedPackageNames`, `blacklistedHashes`, `suspiciousPermissions`, `whitelistedInstallationSources` are deprecated but remain functional — use `SuspiciousAppDetectionConfig` instead +- `MalwareConfig` class and `AndroidConfig.malwareConfig` field — use `SuspiciousAppDetectionConfig` instead ### Android diff --git a/pubspec.yaml b/pubspec.yaml index fd5562d..3c119cb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: freerasp description: Flutter library for improving app security and threat monitoring on Android and iOS mobile devices. Learn more about provided features on the freeRASP's homepage first. -version: 7.6.0 +version: 8.0.0 homepage: https://www.talsec.app/freerasp-in-app-protection-security-talsec repository: https://github.com/talsec/Free-RASP-Flutter From 6d9947bd0014ee9e74ed5627068937dca858f329 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Wed, 13 May 2026 16:55:38 +0200 Subject: [PATCH 15/20] style: format suspicious_app_detection_config_test.dart --- test/src/models/suspicious_app_detection_config_test.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/src/models/suspicious_app_detection_config_test.dart b/test/src/models/suspicious_app_detection_config_test.dart index 8178561..639b94a 100644 --- a/test/src/models/suspicious_app_detection_config_test.dart +++ b/test/src/models/suspicious_app_detection_config_test.dart @@ -232,12 +232,10 @@ void main() { }; for (final entry in expected.entries) { - final json = - MalwareScanScope(scanScope: entry.key).toJson(); + final json = MalwareScanScope(scanScope: entry.key).toJson(); expect(json['scanScope'], entry.value); - final restored = - MalwareScanScope.fromJson({'scanScope': entry.value}); + final restored = MalwareScanScope.fromJson({'scanScope': entry.value}); expect(restored.scanScope, entry.key); } }); From 44e000a209ad29e1f48a7b2a21d5be03c3eac060 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Wed, 13 May 2026 17:20:40 +0200 Subject: [PATCH 16/20] docs: drop stale TalsecConfig.Builder deprecation note --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9cc2b2..8ac5ed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fine-tuned hook detection - Fine-tuned location spoofing detection - Modified malware incident log structure for better aggregation -- Old malware configuration API methods in `TalsecConfig.Builder` tagged as deprecated (but remain functional): `blacklistedPackageNames`, `blacklistedHashes`, `suspiciousPermissions`, `whitelistedInstallationSources` ## [7.5.1] - 2026-03-24 From 74f7cce18b8f6983ebae3d93b58387945a4d8080 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Fri, 15 May 2026 10:23:58 +0200 Subject: [PATCH 17/20] chore: changelog + comments update --- CHANGELOG.md | 1 - .../suspicious_app_detection_config.dart | 21 ------------------- 2 files changed, 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac5ed9..8c16f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `SuspiciousAppInfo.reason` (String) renamed to `reasons` (List\) - Value `"blacklist"` in `reasons` renamed to `"blocklist"` - Removed `MalwareConfig` and `AndroidConfig.malwareConfig` — use `SuspiciousAppDetectionConfig` instead -- Default `reasonMode` is now `ReasonMode.highestConfidence` — set `ReasonMode.all` to keep the previous behavior ### Flutter diff --git a/lib/src/models/suspicious_app_detection_config.dart b/lib/src/models/suspicious_app_detection_config.dart index 1fb4d86..14f4b37 100644 --- a/lib/src/models/suspicious_app_detection_config.dart +++ b/lib/src/models/suspicious_app_detection_config.dart @@ -4,58 +4,45 @@ part 'suspicious_app_detection_config.g.dart'; /// The scope of apps to be scanned for malware. enum ScopeType { - /// Only sideloaded apps. sideloadedOnly, - /// Sideloaded and system apps, excluding OEM apps. sideloadedAndSystemExcludeOem, - /// Sideloaded and OEM apps. sideloadedAndOem, - /// Sideloaded, system, and OEM apps. sideloadedAndSystemAndOem, - /// All apps. all, } /// The mode for reporting malware detection reasons. enum ReasonMode { - /// Report all reasons. all, - /// Report only the highest confidence reason. highestConfidence, } /// Configuration for malware scan scope and trusted install sources. @JsonSerializable(includeIfNull: false) class MalwareScanScope { - /// Creates a new instance of [MalwareScanScope]. const MalwareScanScope({ required this.scanScope, this.trustedInstallSources, }); - /// Converts from json factory MalwareScanScope.fromJson(Map json) => _$MalwareScanScopeFromJson(json); - /// Converts to json Map toJson() => _$MalwareScanScopeToJson(this); - /// The scope of apps to be scanned. final ScopeType scanScope; - /// List of trusted install sources. final List? trustedInstallSources; } /// Configuration for suspicious app detection. @JsonSerializable(includeIfNull: false) class SuspiciousAppDetectionConfig { - /// Creates a new instance of [SuspiciousAppDetectionConfig]. const SuspiciousAppDetectionConfig({ this.packageNames, this.hashes, @@ -66,28 +53,20 @@ class SuspiciousAppDetectionConfig { this.reasonMode = ReasonMode.highestConfidence, }); - /// Converts from json factory SuspiciousAppDetectionConfig.fromJson(Map json) => _$SuspiciousAppDetectionConfigFromJson(json); - /// Converts to json Map toJson() => _$SuspiciousAppDetectionConfigToJson(this); - /// List of suspicious package names to detect. final List? packageNames; - /// List of suspicious app hashes to detect. final List? hashes; - /// Sets of requested permissions that indicate a suspicious app. final List>? requestedPermissions; - /// Sets of granted permissions that indicate a suspicious app. final List>? grantedPermissions; - /// Configuration for the malware scan scope. final MalwareScanScope malwareScanScope; - /// The mode for reporting detection reasons. final ReasonMode reasonMode; } From bd99a7f2e994738c36bf96722204a0e8ce247e74 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Fri, 15 May 2026 10:53:17 +0200 Subject: [PATCH 18/20] Revert "chore: changelog + comments update" This reverts commit 74f7cce18b8f6983ebae3d93b58387945a4d8080. --- CHANGELOG.md | 1 + .../suspicious_app_detection_config.dart | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c16f61..8ac5ed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `SuspiciousAppInfo.reason` (String) renamed to `reasons` (List\) - Value `"blacklist"` in `reasons` renamed to `"blocklist"` - Removed `MalwareConfig` and `AndroidConfig.malwareConfig` — use `SuspiciousAppDetectionConfig` instead +- Default `reasonMode` is now `ReasonMode.highestConfidence` — set `ReasonMode.all` to keep the previous behavior ### Flutter diff --git a/lib/src/models/suspicious_app_detection_config.dart b/lib/src/models/suspicious_app_detection_config.dart index 14f4b37..1fb4d86 100644 --- a/lib/src/models/suspicious_app_detection_config.dart +++ b/lib/src/models/suspicious_app_detection_config.dart @@ -4,45 +4,58 @@ part 'suspicious_app_detection_config.g.dart'; /// The scope of apps to be scanned for malware. enum ScopeType { + /// Only sideloaded apps. sideloadedOnly, + /// Sideloaded and system apps, excluding OEM apps. sideloadedAndSystemExcludeOem, + /// Sideloaded and OEM apps. sideloadedAndOem, + /// Sideloaded, system, and OEM apps. sideloadedAndSystemAndOem, + /// All apps. all, } /// The mode for reporting malware detection reasons. enum ReasonMode { + /// Report all reasons. all, + /// Report only the highest confidence reason. highestConfidence, } /// Configuration for malware scan scope and trusted install sources. @JsonSerializable(includeIfNull: false) class MalwareScanScope { + /// Creates a new instance of [MalwareScanScope]. const MalwareScanScope({ required this.scanScope, this.trustedInstallSources, }); + /// Converts from json factory MalwareScanScope.fromJson(Map json) => _$MalwareScanScopeFromJson(json); + /// Converts to json Map toJson() => _$MalwareScanScopeToJson(this); + /// The scope of apps to be scanned. final ScopeType scanScope; + /// List of trusted install sources. final List? trustedInstallSources; } /// Configuration for suspicious app detection. @JsonSerializable(includeIfNull: false) class SuspiciousAppDetectionConfig { + /// Creates a new instance of [SuspiciousAppDetectionConfig]. const SuspiciousAppDetectionConfig({ this.packageNames, this.hashes, @@ -53,20 +66,28 @@ class SuspiciousAppDetectionConfig { this.reasonMode = ReasonMode.highestConfidence, }); + /// Converts from json factory SuspiciousAppDetectionConfig.fromJson(Map json) => _$SuspiciousAppDetectionConfigFromJson(json); + /// Converts to json Map toJson() => _$SuspiciousAppDetectionConfigToJson(this); + /// List of suspicious package names to detect. final List? packageNames; + /// List of suspicious app hashes to detect. final List? hashes; + /// Sets of requested permissions that indicate a suspicious app. final List>? requestedPermissions; + /// Sets of granted permissions that indicate a suspicious app. final List>? grantedPermissions; + /// Configuration for the malware scan scope. final MalwareScanScope malwareScanScope; + /// The mode for reporting detection reasons. final ReasonMode reasonMode; } From e3dcac7b668008122da7fffa2badbe75feb2ea33 Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Fri, 15 May 2026 10:58:42 +0200 Subject: [PATCH 19/20] docs: CHANGELOG update --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac5ed9..8c16f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `SuspiciousAppInfo.reason` (String) renamed to `reasons` (List\) - Value `"blacklist"` in `reasons` renamed to `"blocklist"` - Removed `MalwareConfig` and `AndroidConfig.malwareConfig` — use `SuspiciousAppDetectionConfig` instead -- Default `reasonMode` is now `ReasonMode.highestConfidence` — set `ReasonMode.all` to keep the previous behavior ### Flutter From 11f8e458bbed8a19cca999885bfbf493da6b8b3c Mon Sep 17 00:00:00 2001 From: martinzigrai Date: Mon, 18 May 2026 18:20:09 +0200 Subject: [PATCH 20/20] refactor: rename MalwareScanScope to ScanScope and malwareScanScope field to scanScope --- .../com/aheaditec/freerasp/Extensions.kt | 6 +- example/lib/main.dart | 2 +- .../suspicious_app_detection_config.dart | 17 +++-- .../suspicious_app_detection_config.g.dart | 14 ++-- test/src/models/android_config_test.dart | 8 +-- .../suspicious_app_detection_config_test.dart | 64 +++++++++---------- 6 files changed, 53 insertions(+), 58 deletions(-) diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt b/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt index 29da4c9..b05ad19 100644 --- a/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt +++ b/android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt @@ -90,7 +90,7 @@ internal fun ExternalIdResult.resolve(result: MethodChannel.Result) { } } -internal fun JSONObject.toMalwareScanScope(): MalwareScanScope { +internal fun JSONObject.toScanScope(): MalwareScanScope { val scanScope = ScopeType.valueOf(getString("scanScope")) val trustedInstallSources = optJSONArray("trustedInstallSources") ?.let { processArray(it).asList() } @@ -106,14 +106,14 @@ internal fun JSONObject.toSuspiciousAppDetectionConfig(): SuspiciousAppDetection ?.let { processArray>(it).mapTo(mutableSetOf()) { it.toMutableSet() } } val grantedPermissions = optJSONArray("grantedPermissions") ?.let { processArray>(it).mapTo(mutableSetOf()) { it.toMutableSet() } } - val malwareScanScope = getJSONObject("malwareScanScope").toMalwareScanScope() + val scanScope = getJSONObject("scanScope").toScanScope() val reasonMode = ReasonMode.valueOf(getString("reasonMode")) return SuspiciousAppDetectionConfig( packageNames, hashes, requestedPermissions, grantedPermissions, - malwareScanScope, + scanScope, reasonMode, ) } diff --git a/example/lib/main.dart b/example/lib/main.dart index 59ea3e1..138b4e5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -44,7 +44,7 @@ Future _initializeTalsec() async { ['android.permission.CAMERA'], ['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'], ], - malwareScanScope: MalwareScanScope( + scanScope: ScanScope( scanScope: ScopeType.sideloadedOnly, ), reasonMode: ReasonMode.highestConfidence, diff --git a/lib/src/models/suspicious_app_detection_config.dart b/lib/src/models/suspicious_app_detection_config.dart index 1fb4d86..c1c1106 100644 --- a/lib/src/models/suspicious_app_detection_config.dart +++ b/lib/src/models/suspicious_app_detection_config.dart @@ -31,19 +31,19 @@ enum ReasonMode { /// Configuration for malware scan scope and trusted install sources. @JsonSerializable(includeIfNull: false) -class MalwareScanScope { - /// Creates a new instance of [MalwareScanScope]. - const MalwareScanScope({ +class ScanScope { + /// Creates a new instance of [ScanScope]. + const ScanScope({ required this.scanScope, this.trustedInstallSources, }); /// Converts from json - factory MalwareScanScope.fromJson(Map json) => - _$MalwareScanScopeFromJson(json); + factory ScanScope.fromJson(Map json) => + _$ScanScopeFromJson(json); /// Converts to json - Map toJson() => _$MalwareScanScopeToJson(this); + Map toJson() => _$ScanScopeToJson(this); /// The scope of apps to be scanned. final ScopeType scanScope; @@ -61,8 +61,7 @@ class SuspiciousAppDetectionConfig { this.hashes, this.requestedPermissions, this.grantedPermissions, - this.malwareScanScope = - const MalwareScanScope(scanScope: ScopeType.sideloadedOnly), + this.scanScope = const ScanScope(scanScope: ScopeType.sideloadedOnly), this.reasonMode = ReasonMode.highestConfidence, }); @@ -86,7 +85,7 @@ class SuspiciousAppDetectionConfig { final List>? grantedPermissions; /// Configuration for the malware scan scope. - final MalwareScanScope malwareScanScope; + final ScanScope scanScope; /// The mode for reporting detection reasons. final ReasonMode reasonMode; diff --git a/lib/src/models/suspicious_app_detection_config.g.dart b/lib/src/models/suspicious_app_detection_config.g.dart index 25dae2e..73e68a3 100644 --- a/lib/src/models/suspicious_app_detection_config.g.dart +++ b/lib/src/models/suspicious_app_detection_config.g.dart @@ -19,15 +19,14 @@ const _$ReasonModeEnumMap = { ReasonMode.highestConfidence: 'HIGHEST_CONFIDENCE', }; -MalwareScanScope _$MalwareScanScopeFromJson(Map json) => - MalwareScanScope( +ScanScope _$ScanScopeFromJson(Map json) => ScanScope( scanScope: $enumDecode(_$ScopeTypeEnumMap, json['scanScope']), trustedInstallSources: (json['trustedInstallSources'] as List?) ?.map((e) => e as String) .toList(), ); -Map _$MalwareScanScopeToJson(MalwareScanScope instance) { +Map _$ScanScopeToJson(ScanScope instance) { final val = { 'scanScope': _$ScopeTypeEnumMap[instance.scanScope]!, }; @@ -60,10 +59,9 @@ SuspiciousAppDetectionConfig _$SuspiciousAppDetectionConfigFromJson( (e) => (e as List).map((e) => e as String).toList(), ) .toList(), - malwareScanScope: json['malwareScanScope'] == null - ? const MalwareScanScope(scanScope: ScopeType.sideloadedOnly) - : MalwareScanScope.fromJson( - json['malwareScanScope'] as Map), + scanScope: json['scanScope'] == null + ? const ScanScope(scanScope: ScopeType.sideloadedOnly) + : ScanScope.fromJson(json['scanScope'] as Map), reasonMode: $enumDecodeNullable(_$ReasonModeEnumMap, json['reasonMode']) ?? ReasonMode.highestConfidence, @@ -83,7 +81,7 @@ Map _$SuspiciousAppDetectionConfigToJson( writeNotNull('hashes', instance.hashes); writeNotNull('requestedPermissions', instance.requestedPermissions); writeNotNull('grantedPermissions', instance.grantedPermissions); - val['malwareScanScope'] = instance.malwareScanScope.toJson(); + val['scanScope'] = instance.scanScope.toJson(); val['reasonMode'] = _$ReasonModeEnumMap[instance.reasonMode]!; return val; } diff --git a/test/src/models/android_config_test.dart b/test/src/models/android_config_test.dart index 7013c99..a90350f 100644 --- a/test/src/models/android_config_test.dart +++ b/test/src/models/android_config_test.dart @@ -133,7 +133,7 @@ void main() { signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], suspiciousAppDetectionConfig: const SuspiciousAppDetectionConfig( packageNames: ['com.malware.app'], - malwareScanScope: MalwareScanScope(scanScope: ScopeType.all), + scanScope: ScanScope(scanScope: ScopeType.all), reasonMode: ReasonMode.all, ), ); @@ -144,7 +144,7 @@ void main() { expect(detection['packageNames'], ['com.malware.app']); expect( - (detection['malwareScanScope'] as Map)['scanScope'], + (detection['scanScope'] as Map)['scanScope'], 'ALL', ); expect(detection['reasonMode'], 'ALL'); @@ -162,7 +162,7 @@ void main() { 'signingCertHashes': ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], 'suspiciousAppDetectionConfig': { 'packageNames': ['com.malware.app'], - 'malwareScanScope': {'scanScope': 'SIDELOADED_AND_OEM'}, + 'scanScope': {'scanScope': 'SIDELOADED_AND_OEM'}, 'reasonMode': 'ALL', }, }; @@ -171,7 +171,7 @@ void main() { final detection = config.suspiciousAppDetectionConfig!; expect(detection.packageNames, ['com.malware.app']); - expect(detection.malwareScanScope.scanScope, ScopeType.sideloadedAndOem); + expect(detection.scanScope.scanScope, ScopeType.sideloadedAndOem); expect(detection.reasonMode, ReasonMode.all); }); }); diff --git a/test/src/models/suspicious_app_detection_config_test.dart b/test/src/models/suspicious_app_detection_config_test.dart index 639b94a..f41ba67 100644 --- a/test/src/models/suspicious_app_detection_config_test.dart +++ b/test/src/models/suspicious_app_detection_config_test.dart @@ -2,18 +2,17 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:freerasp/freerasp.dart'; void main() { - group('MalwareScanScope', () { - test('Should create MalwareScanScope instance with required field only', - () { - const scope = MalwareScanScope(scanScope: ScopeType.sideloadedOnly); + group('ScanScope', () { + test('Should create ScanScope instance with required field only', () { + const scope = ScanScope(scanScope: ScopeType.sideloadedOnly); - expect(scope, isA()); + expect(scope, isA()); expect(scope.scanScope, ScopeType.sideloadedOnly); expect(scope.trustedInstallSources, isNull); }); - test('Should create MalwareScanScope instance with all fields', () { - const scope = MalwareScanScope( + test('Should create ScanScope instance with all fields', () { + const scope = ScanScope( scanScope: ScopeType.all, trustedInstallSources: ['com.android.vending'], ); @@ -22,8 +21,8 @@ void main() { expect(scope.trustedInstallSources, ['com.android.vending']); }); - test('Should convert MalwareScanScope to JSON', () { - const scope = MalwareScanScope( + test('Should convert ScanScope to JSON', () { + const scope = ScanScope( scanScope: ScopeType.sideloadedAndOem, trustedInstallSources: ['com.android.vending'], ); @@ -35,30 +34,29 @@ void main() { }); test('Should omit trustedInstallSources from JSON when null', () { - const scope = MalwareScanScope(scanScope: ScopeType.sideloadedOnly); + const scope = ScanScope(scanScope: ScopeType.sideloadedOnly); final json = scope.toJson(); expect(json.containsKey('trustedInstallSources'), isFalse); }); - test('Should create MalwareScanScope instance from JSON', () { + test('Should create ScanScope instance from JSON', () { final json = { 'scanScope': 'SIDELOADED_AND_SYSTEM_EXCLUDE_OEM', 'trustedInstallSources': ['com.android.vending'], }; - final scope = MalwareScanScope.fromJson(json); + final scope = ScanScope.fromJson(json); expect(scope.scanScope, ScopeType.sideloadedAndSystemExcludeOem); expect(scope.trustedInstallSources, ['com.android.vending']); }); - test('Should create MalwareScanScope instance from JSON without optional', - () { + test('Should create ScanScope instance from JSON without optional', () { final json = {'scanScope': 'ALL'}; - final scope = MalwareScanScope.fromJson(json); + final scope = ScanScope.fromJson(json); expect(scope.scanScope, ScopeType.all); expect(scope.trustedInstallSources, isNull); @@ -73,8 +71,8 @@ void main() { expect(config.hashes, isNull); expect(config.requestedPermissions, isNull); expect(config.grantedPermissions, isNull); - expect(config.malwareScanScope.scanScope, ScopeType.sideloadedOnly); - expect(config.malwareScanScope.trustedInstallSources, isNull); + expect(config.scanScope.scanScope, ScopeType.sideloadedOnly); + expect(config.scanScope.trustedInstallSources, isNull); expect(config.reasonMode, ReasonMode.highestConfidence); }); @@ -88,7 +86,7 @@ void main() { grantedPermissions: [ ['android.permission.READ_SMS'], ], - malwareScanScope: MalwareScanScope(scanScope: ScopeType.all), + scanScope: ScanScope(scanScope: ScopeType.all), reasonMode: ReasonMode.all, ); @@ -100,7 +98,7 @@ void main() { expect(config.grantedPermissions, [ ['android.permission.READ_SMS'], ]); - expect(config.malwareScanScope.scanScope, ScopeType.all); + expect(config.scanScope.scanScope, ScopeType.all); expect(config.reasonMode, ReasonMode.all); }); @@ -109,9 +107,9 @@ void main() { final json = config.toJson(); - expect(json['malwareScanScope'], isA>()); + expect(json['scanScope'], isA>()); expect( - (json['malwareScanScope'] as Map)['scanScope'], + (json['scanScope'] as Map)['scanScope'], 'SIDELOADED_ONLY', ); expect(json['reasonMode'], 'HIGHEST_CONFIDENCE'); @@ -131,7 +129,7 @@ void main() { grantedPermissions: [ ['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'], ], - malwareScanScope: MalwareScanScope( + scanScope: ScanScope( scanScope: ScopeType.sideloadedAndSystemAndOem, trustedInstallSources: ['com.android.vending'], ), @@ -149,7 +147,7 @@ void main() { ['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'], ]); expect( - (json['malwareScanScope'] as Map)['scanScope'], + (json['scanScope'] as Map)['scanScope'], 'SIDELOADED_AND_SYSTEM_AND_OEM', ); expect(json['reasonMode'], 'ALL'); @@ -159,7 +157,7 @@ void main() { final config = SuspiciousAppDetectionConfig.fromJson({}); expect(config.packageNames, isNull); - expect(config.malwareScanScope.scanScope, ScopeType.sideloadedOnly); + expect(config.scanScope.scanScope, ScopeType.sideloadedOnly); expect(config.reasonMode, ReasonMode.highestConfidence); }); @@ -173,7 +171,7 @@ void main() { 'grantedPermissions': [ ['android.permission.READ_SMS'], ], - 'malwareScanScope': { + 'scanScope': { 'scanScope': 'SIDELOADED_AND_OEM', 'trustedInstallSources': ['com.android.vending'], }, @@ -190,9 +188,9 @@ void main() { expect(config.grantedPermissions, [ ['android.permission.READ_SMS'], ]); - expect(config.malwareScanScope.scanScope, ScopeType.sideloadedAndOem); + expect(config.scanScope.scanScope, ScopeType.sideloadedAndOem); expect( - config.malwareScanScope.trustedInstallSources, + config.scanScope.trustedInstallSources, ['com.android.vending'], ); expect(config.reasonMode, ReasonMode.all); @@ -202,7 +200,7 @@ void main() { const original = SuspiciousAppDetectionConfig( packageNames: ['com.malware.app'], hashes: ['abc123'], - malwareScanScope: MalwareScanScope( + scanScope: ScanScope( scanScope: ScopeType.sideloadedAndSystemExcludeOem, ), reasonMode: ReasonMode.all, @@ -213,8 +211,8 @@ void main() { expect(restored.packageNames, original.packageNames); expect(restored.hashes, original.hashes); expect( - restored.malwareScanScope.scanScope, - original.malwareScanScope.scanScope, + restored.scanScope.scanScope, + original.scanScope.scanScope, ); expect(restored.reasonMode, original.reasonMode); }); @@ -232,10 +230,10 @@ void main() { }; for (final entry in expected.entries) { - final json = MalwareScanScope(scanScope: entry.key).toJson(); + final json = ScanScope(scanScope: entry.key).toJson(); expect(json['scanScope'], entry.value); - final restored = MalwareScanScope.fromJson({'scanScope': entry.value}); + final restored = ScanScope.fromJson({'scanScope': entry.value}); expect(restored.scanScope, entry.key); } }); @@ -254,7 +252,7 @@ void main() { expect(json['reasonMode'], entry.value); final restored = SuspiciousAppDetectionConfig.fromJson({ - 'malwareScanScope': {'scanScope': 'SIDELOADED_ONLY'}, + 'scanScope': {'scanScope': 'SIDELOADED_ONLY'}, 'reasonMode': entry.value, }); expect(restored.reasonMode, entry.key);