Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6b1d5c8
feat: bump Android SDK to 18.3.0
martinzigrai May 7, 2026
1058b9e
feat!: rename reason to reasons in SuspiciousAppInfo
martinzigrai May 7, 2026
bd7894b
feat!: deprecate old malware config fields
martinzigrai May 7, 2026
56487c3
feat: add SuspiciousAppDetectionConfig to API
martinzigrai May 7, 2026
ba56ae0
refactor: extract SuspiciousAppDetectionConfig parsing to utils
martinzigrai May 7, 2026
6909054
chore: update changelog for 7.6.0
martinzigrai May 7, 2026
be0649a
style: format suspicious_app_detection_config.g.dart
martinzigrai May 11, 2026
e4258fc
fix: resolve lint issues - deprecated_consistency and prefer_const_co…
martinzigrai May 11, 2026
ba45995
style: format android_config.dart
martinzigrai May 11, 2026
cccbd74
fix: use positional args and correct types for MalwareScanScope and S…
martinzigrai May 12, 2026
a545b78
Merge branch 'master' into rc/7.6.0
tompsota May 13, 2026
393fa70
feat!: remove deprecated MalwareConfig API
martinzigrai May 13, 2026
8cf8248
refactor: tighten SuspiciousAppDetectionConfig parsing
martinzigrai May 13, 2026
aafe0d2
test: cover SuspiciousAppDetectionConfig and related models
martinzigrai May 13, 2026
e065364
chore: bump to 8.0.0
martinzigrai May 13, 2026
6d9947b
style: format suspicious_app_detection_config_test.dart
martinzigrai May 13, 2026
44e000a
docs: drop stale TalsecConfig.Builder deprecation note
martinzigrai May 13, 2026
74f7cce
chore: changelog + comments update
martinzigrai May 15, 2026
bd99a7f
Revert "chore: changelog + comments update"
martinzigrai May 15, 2026
e3dcac7
docs: CHANGELOG update
martinzigrai May 15, 2026
11f8e45
refactor: rename MalwareScanScope to ScanScope and malwareScanScope f…
martinzigrai May 18, 2026
3958139
fix: unify API with other platforms
martinzigrai May 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,44 @@ 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).

## [8.0.0] - 2026-05-13

- Android SDK version: 18.3.0
- iOS SDK version: 6.14.4

### Flutter

#### Breaking

- `RaspExecutionStateCallback.onAllChecksDone` renamed to `onAllChecksFinished`
- `PackageInfo.installationSource` renamed to `installerStore`
- `SuspiciousAppInfo.reason` (String) renamed to `reasons` (List\<String\>)
- Value `"blacklist"` in `reasons` renamed to `"blocklist"`
- Removed `MalwareConfig` and `AndroidConfig.malwareConfig` — use `SuspiciousAppDetectionConfig` instead

#### Added

- `SuspiciousAppInfo.permissions` field (`List<String>?`) — list of suspicious permissions detected on the app

### Android

#### Added

- 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 Java interoperability of `ScreenProtector` methods
- Fixed Kotlin classpath conflicts in SDK dependency resolution (Kotlin 2.0.0)

#### Changed

- Fine-tuned location spoofing detection
- Modified malware incident log structure for better aggregation

## [7.5.1] - 2026-03-24

- Android SDK version: 18.0.4
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
38 changes: 36 additions & 2 deletions android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ 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.JSONArray
import org.json.JSONObject
import com.aheaditec.freerasp.generated.PackageInfo as FlutterPackageInfo
import com.aheaditec.freerasp.generated.SuspiciousAppInfo as FlutterSuspiciousAppInfo

Expand Down Expand Up @@ -33,7 +39,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(), this.permissions?.toList())
}

/**
Expand All @@ -50,7 +56,7 @@ private fun PackageInfo.toPigeon(context: Context): FlutterPackageInfo {
context.packageManager.getApplicationLabel(it) as String
},
version = getVersionString(),
installationSource = Utils.getInstallerPackageName(context, packageName),
installerStore = Utils.getInstallerPackageName(context, packageName),
)
}

Expand Down Expand Up @@ -83,3 +89,31 @@ internal fun ExternalIdResult.resolve(result: MethodChannel.Result) {
is ExternalIdResult.Error -> result.error("external-id-failure", this.errorMsg, null)
}
}

internal fun JSONObject.toScanScope(): MalwareScanScope {
val scanScope = ScopeType.valueOf(getString("scanScope"))
val trustedInstallSources = optJSONArray("trustedInstallSources")
?.let { processArray<String>(it).asList() }
return MalwareScanScope(scanScope, trustedInstallSources)
}

internal fun JSONObject.toSuspiciousAppDetectionConfig(): SuspiciousAppDetectionConfig {
val packageNames = optJSONArray("packageNames")
?.let { processArray<String>(it).toMutableSet() }
val hashes = optJSONArray("hashes")
?.let { processArray<String>(it).toMutableSet() }
val requestedPermissions = optJSONArray("requestedPermissions")
?.let { processArray<Array<String>>(it).mapTo(mutableSetOf()) { it.toMutableSet() } }
val grantedPermissions = optJSONArray("grantedPermissions")
?.let { processArray<Array<String>>(it).mapTo(mutableSetOf()) { it.toMutableSet() } }
val scanScope = getJSONObject("scanScope").toScanScope()
val reasonMode = ReasonMode.valueOf(getString("reasonMode"))
return SuspiciousAppDetectionConfig(
packageNames,
hashes,
requestedPermissions,
grantedPermissions,
scanScope,
reasonMode,
)
}
38 changes: 7 additions & 31 deletions android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ import org.json.JSONObject
import java.io.ByteArrayOutputStream

internal object Utils {
@Suppress("ArrayInDataClass")
data class MalwareConfig(
val blacklistedPackageNames: Array<String>,
val blacklistedHashes: Array<String>,
val suspiciousPermissions: Array<Array<String>>,
val whitelistedInstallationSources: Array<String>
)

fun toTalsecConfigThrowing(configJson: String?): TalsecConfig {
if (configJson == null) {
throw JSONException("Configuration is null")
Expand All @@ -36,36 +28,20 @@ internal object Utils {
val packageName = androidConfig.getString("packageName")
val certificateHashes = androidConfig.extractArray<String>("signingCertHashes")
val alternativeStores = androidConfig.extractArray<String>("supportedStores")
val malwareConfig = parseMalwareConfig(androidConfig)

return TalsecConfig.Builder(packageName, certificateHashes)
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)
.build()
}

private fun parseMalwareConfig(androidConfig: JSONObject): MalwareConfig {
if (!androidConfig.has("malwareConfig")) {
return MalwareConfig(emptyArray(), emptyArray(), emptyArray(), emptyArray())
androidConfig.optJSONObject("suspiciousAppDetectionConfig")?.let {
builder.suspiciousAppDetection(it.toSuspiciousAppDetectionConfig())
}

val malwareConfig = androidConfig.getJSONObject("malwareConfig")

return MalwareConfig(
malwareConfig.extractArray("blacklistedPackageNames"),
malwareConfig.extractArray("blacklistedHashes"),
malwareConfig.extractArray<Array<String>>("suspiciousPermissions"),
malwareConfig.extractArray("whitelistedInstallationSources")
)
return builder.build()
}


/**
* Retrieves the package name of the installer for a given app package.
*
Expand Down Expand Up @@ -145,11 +121,11 @@ internal object Utils {
}
}

private inline fun <reified T> JSONObject.extractArray(key: String): Array<T> {
internal inline fun <reified T> JSONObject.extractArray(key: String): Array<T> {
return this.optJSONArray(key)?.let { processArray(it) } ?: emptyArray()
}

private inline fun <reified T> processArray(jsonArray: JSONArray): Array<T> {
internal inline fun <reified T> processArray(jsonArray: JSONArray): Array<T> {
val list = mutableListOf<T>()

for (i in 0 until jsonArray.length()) {
Expand All @@ -175,4 +151,4 @@ private inline fun <reified T> processArray(jsonArray: JSONArray): Array<T> {
}

return list.toTypedArray()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ data class PackageInfo (
val appIcon: String? = null,
val appName: String? = null,
val version: String? = null,
val installationSource: String? = null
val installerStore: String? = null
)
{
companion object {
Expand All @@ -44,8 +44,8 @@ data class PackageInfo (
val appIcon = pigeonVar_list[1] as String?
val appName = pigeonVar_list[2] as String?
val version = pigeonVar_list[3] as String?
val installationSource = pigeonVar_list[4] as String?
return PackageInfo(packageName, appIcon, appName, version, installationSource)
val installerStore = pigeonVar_list[4] as String?
return PackageInfo(packageName, appIcon, appName, version, installerStore)
}
}
fun toList(): List<Any?> {
Expand All @@ -54,28 +54,32 @@ data class PackageInfo (
appIcon,
appName,
version,
installationSource,
installerStore,
)
}
}

/** Generated class from Pigeon that represents data sent in messages. */
data class SuspiciousAppInfo (
val packageInfo: PackageInfo,
val reason: String
val reasons: List<String>,
val permissions: List<String>? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): 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<String>
@Suppress("UNCHECKED_CAST")
val permissions = pigeonVar_list[2] as List<String>?
return SuspiciousAppInfo(packageInfo, reasons, permissions)
}
}
fun toList(): List<Any?> {
return listOf(
packageInfo,
reason,
reasons,
permissions,
)
}
}
Expand Down
10 changes: 7 additions & 3 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ Future<void> _initializeTalsec() async {
packageName: 'com.aheaditec.freeraspExample',
signingCertHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='],
supportedStores: ['com.sec.android.app.samsungapps'],
malwareConfig: MalwareConfig(
blacklistedPackageNames: ['com.aheaditec.freeraspExample'],
suspiciousPermissions: [
suspiciousAppDetectionConfig: const SuspiciousAppDetectionConfig(
packageNames: ['com.aheaditec.freeraspExample'],
requestedPermissions: [
['android.permission.CAMERA'],
['android.permission.READ_SMS', 'android.permission.READ_CONTACTS'],
],
scanScope: ScanScope(
scanScope: ScopeType.sideloadedOnly,
),
reasonMode: ReasonMode.highestConfidence,
),
),
iosConfig: IOSConfig(
Expand Down
2 changes: 1 addition & 1 deletion example/lib/threat_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class ThreatNotifier extends AutoDisposeNotifier<ThreatState> {
);

final raspExecutionStateCallback =
RaspExecutionStateCallback(onAllChecksDone: _updateChecksStatus);
RaspExecutionStateCallback(onAllChecksFinished: _updateChecksStatus);

Talsec.instance.attachListener(threatCallback);
Talsec.instance.attachExecutionStateListener(raspExecutionStateCallback);
Expand Down
2 changes: 1 addition & 1 deletion example/lib/widgets/malware_bottom_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
},
Expand Down
10 changes: 5 additions & 5 deletions lib/src/callbacks/rasp_execution_state_callback.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:freerasp/src/typedefs.dart';
/// Example usage:
/// ```dart
/// final callback = RaspExecutionStateCallback(
/// onAllChecksDone: () {
/// onAllChecksFinished: () {
/// print('All security checks have been completed');
/// // Update UI or perform additional actions
/// },
Expand All @@ -17,12 +17,12 @@ import 'package:freerasp/src/typedefs.dart';
class RaspExecutionStateCallback {
/// Creates a new [RaspExecutionStateCallback] instance.
///
/// The [onAllChecksDone] callback will be invoked when all security checks
/// have been completed by the native security engine.
/// The [onAllChecksFinished] callback will be invoked when all security
/// checks have been completed by the native security engine.
RaspExecutionStateCallback({
this.onAllChecksDone,
this.onAllChecksFinished,
});

/// Callback invoked when all security checks are completed.
final VoidCallback? onAllChecksDone;
final VoidCallback? onAllChecksFinished;
}
21 changes: 13 additions & 8 deletions lib/src/generated/talsec_pigeon_api.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class PackageInfo {
this.appIcon,
this.appName,
this.version,
this.installationSource,
this.installerStore,
});

String packageName;
Expand All @@ -36,15 +36,15 @@ class PackageInfo {

String? version;

String? installationSource;
String? installerStore;

Object encode() {
return <Object?>[
packageName,
appIcon,
appName,
version,
installationSource,
installerStore,
];
}

Expand All @@ -55,33 +55,38 @@ class PackageInfo {
appIcon: result[1] as String?,
appName: result[2] as String?,
version: result[3] as String?,
installationSource: result[4] as String?,
installerStore: result[4] as String?,
);
}
}

class SuspiciousAppInfo {
SuspiciousAppInfo({
required this.packageInfo,
required this.reason,
required this.reasons,
this.permissions,
});

PackageInfo packageInfo;

String reason;
List<String> reasons;

List<String>? permissions;

Object encode() {
return <Object?>[
packageInfo,
reason,
reasons,
permissions,
];
}

static SuspiciousAppInfo decode(Object result) {
result as List<Object?>;
return SuspiciousAppInfo(
packageInfo: result[0]! as PackageInfo,
reason: result[1]! as String,
reasons: (result[1] as List<Object?>?)!.cast<String>(),
permissions: (result[2] as List<Object?>?)?.cast<String>(),
);
}
}
Expand Down
Loading