diff --git a/CHANGELOG.md b/CHANGELOG.md index 8352d9a..43e28fd 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). +## [4.6.0] - 2026-05-07 + +- Android SDK version: 18.3.0 +- iOS SDK version: 6.14.4 + +### Breaking + +- `SuspiciousAppInfo.reason` (String) renamed to `reasons` (string[]) +- Value `"blacklist"` in `reasons` renamed to `"blocklist"` + +### React Native + +#### 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` + ## [4.5.2] - 2026-03-24 - Android SDK version: 18.0.4 diff --git a/android/build.gradle b/android/build.gradle index 7a726b4..83eefc6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -105,7 +105,7 @@ dependencies { implementation "com.facebook.react:react-native:$react_native_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" - implementation "com.aheaditec.talsec.security:TalsecSecurity-Community-ReactNative:18.0.4" + implementation "com.aheaditec.talsec.security:TalsecSecurity-Community-ReactNative:18.3.0" } if (isNewArchitectureEnabled()) { diff --git a/android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt b/android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt index ba40fdf..dc16106 100644 --- a/android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt +++ b/android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt @@ -33,6 +33,7 @@ import com.freeraspreactnative.utils.getMapThrowing import com.freeraspreactnative.utils.getNestedArraySafe import com.freeraspreactnative.utils.getStringThrowing import com.freeraspreactnative.utils.toEncodedWritableArray +import com.freeraspreactnative.utils.toSuspiciousAppDetectionConfig class FreeraspReactNativeModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { @@ -306,6 +307,11 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex talsecBuilder.suspiciousPermissions(malwareConfig.getNestedArraySafe("suspiciousPermissions")) } + if (androidConfig.hasKey("suspiciousAppDetectionConfig")) { + val suspiciousAppConfig = androidConfig.getMapThrowing("suspiciousAppDetectionConfig") + talsecBuilder.suspiciousAppDetection(suspiciousAppConfig.toSuspiciousAppDetectionConfig()) + } + return talsecBuilder.build() } diff --git a/android/src/main/java/com/freeraspreactnative/models/RNSuspiciousAppInfo.kt b/android/src/main/java/com/freeraspreactnative/models/RNSuspiciousAppInfo.kt index 11dc239..4c6b0e9 100644 --- a/android/src/main/java/com/freeraspreactnative/models/RNSuspiciousAppInfo.kt +++ b/android/src/main/java/com/freeraspreactnative/models/RNSuspiciousAppInfo.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable @Serializable data class RNSuspiciousAppInfo( val packageInfo: RNPackageInfo, - val reason: String, + val reasons: Set, val permissions: Set? ) diff --git a/android/src/main/java/com/freeraspreactnative/utils/Extensions.kt b/android/src/main/java/com/freeraspreactnative/utils/Extensions.kt index 5feac76..e735c0a 100644 --- a/android/src/main/java/com/freeraspreactnative/utils/Extensions.kt +++ b/android/src/main/java/com/freeraspreactnative/utils/Extensions.kt @@ -3,6 +3,10 @@ package com.freeraspreactnative.utils import android.content.pm.PackageInfo import android.util.Base64 import android.util.Log +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 com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactContext @@ -74,7 +78,7 @@ internal fun ReadableMap.getNestedArraySafe(key: String): Array> { internal fun SuspiciousAppInfo.toRNSuspiciousAppInfo(context: ReactContext): RNSuspiciousAppInfo { return RNSuspiciousAppInfo( packageInfo = this.packageInfo.toRNPackageInfo(context), - reason = this.reason, + reasons = this.reasons, permissions = this.permissions ) } @@ -92,6 +96,42 @@ internal fun PackageInfo.toRNPackageInfo(context: ReactContext): RNPackageInfo { ) } +internal fun ReadableMap.toMalwareScanScope(): MalwareScanScope { + val scanScope = try { + ScopeType.valueOf(getString("scanScope") ?: "SIDELOADED_ONLY") + } catch (_: IllegalArgumentException) { + ScopeType.SIDELOADED_ONLY + } + return MalwareScanScope( + scanScope = scanScope, + trustedInstallSources = getArraySafe("trustedInstallSources").toSet().ifEmpty { null } + ) +} + +internal fun ReadableMap.toSuspiciousAppDetectionConfig(): SuspiciousAppDetectionConfig { + val packageNames = this.getArraySafe("packageNames").toSet().ifEmpty { null } + val hashes = this.getArraySafe("hashes").toSet().ifEmpty { null } + val requestedPermissions = this.getNestedArraySafe("requestedPermissions") + .map { it.toSet() }.toSet().ifEmpty { null } + val grantedPermissions = this.getNestedArraySafe("grantedPermissions") + .map { it.toSet() }.toSet().ifEmpty { null } + val malwareScanScope = if (this.hasKey("malwareScanScope")) + this.getMap("malwareScanScope")?.toMalwareScanScope() else null + val reasonMode = try { + ReasonMode.valueOf(this.getString("reasonMode") ?: "HIGHEST_CONFIDENCE") + } catch (_: IllegalArgumentException) { + ReasonMode.HIGHEST_CONFIDENCE + } + return SuspiciousAppDetectionConfig( + packageNames = packageNames, + hashes = hashes, + requestedPermissions = requestedPermissions, + grantedPermissions = grantedPermissions, + malwareScanScope = malwareScanScope, + reasonMode = reasonMode + ) +} + /** * Convert the Talsec's SuspiciousAppInfo to base64-encoded json array, * which can be then sent to React Native diff --git a/example/src/App.tsx b/example/src/App.tsx index 1b92aa4..4d95476 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -34,10 +34,10 @@ const App = () => { packageName: 'com.freeraspreactnativeexample', certificateHashes: ['AKoRuyLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0='], // supportedAlternativeStores: ['storeOne', 'storeTwo'], - malwareConfig: { - blacklistedHashes: ['FgvSehLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0u'], - blacklistedPackageNames: ['com.freeraspreactnativeexample'], - suspiciousPermissions: [ + suspiciousAppDetectionConfig: { + packageNames: ['com.freeraspreactnativeexample'], + hashes: ['FgvSehLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0u'], + requestedPermissions: [ [ 'android.permission.INTERNET', 'android.permission.ACCESS_COARSE_LOCATION', @@ -45,7 +45,12 @@ const App = () => { ['android.permission.BLUETOOTH'], ['android.permission.BATTERY_STATS'], ], - whitelistedInstallationSources: ['com.apkpure.aegon'], + grantedPermissions: [['android.permission.ACCESS_FINE_LOCATION']], + malwareScanScope: { + scanScope: 'SIDELOADED_AND_SYSTEM_EXCLUDE_OEM', + trustedInstallSources: ['com.apkpure.aegon'], + }, + reasonMode: 'HIGHEST_CONFIDENCE', }, }, iosConfig: { diff --git a/example/src/MalwareItem.tsx b/example/src/MalwareItem.tsx index 1b9fe0a..ce7c311 100644 --- a/example/src/MalwareItem.tsx +++ b/example/src/MalwareItem.tsx @@ -90,8 +90,8 @@ export const MalwareItem: React.FC<{ app: SuspiciousAppInfo }> = ({ app }) => { {app.packageInfo.installerStore ?? 'Not specified'} - Detection reason: - {app.reason} + Detection reasons: + {app.reasons.join(', ')} Granted permissions: {app.permissions?.join(', ') ?? 'Not specified'} diff --git a/package.json b/package.json index c3051c3..e74d399 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freerasp-react-native", - "version": "4.5.2", + "version": "4.6.0", "description": "React Native plugin for improving app security and threat monitoring on Android and iOS mobile devices.", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/src/types/types.ts b/src/types/types.ts index 96f4d56..450ef5b 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -6,11 +6,35 @@ export type TalsecConfig = { killOnBypass?: boolean; }; +export type ScopeType = + | 'SIDELOADED_ONLY' + | 'SIDELOADED_AND_SYSTEM_EXCLUDE_OEM' + | 'SIDELOADED_AND_OEM' + | 'SIDELOADED_AND_SYSTEM_AND_OEM' + | 'ALL'; + +export type ReasonMode = 'ALL' | 'HIGHEST_CONFIDENCE'; + +export type MalwareScanScope = { + scanScope: ScopeType; + trustedInstallSources?: string[]; +}; + +export type SuspiciousAppDetectionConfig = { + packageNames?: string[]; + hashes?: string[]; + requestedPermissions?: string[][]; + grantedPermissions?: string[][]; + malwareScanScope?: MalwareScanScope; + reasonMode?: ReasonMode; +}; + export type TalsecAndroidConfig = { packageName: string; certificateHashes: string[]; supportedAlternativeStores?: string[]; malwareConfig?: TalsecMalwareConfig; + suspiciousAppDetectionConfig?: SuspiciousAppDetectionConfig; }; export type TalsecIosConfig = { @@ -19,15 +43,19 @@ export type TalsecIosConfig = { }; export type TalsecMalwareConfig = { + /** @deprecated Use SuspiciousAppDetectionConfig instead */ blacklistedHashes?: string[]; + /** @deprecated Use SuspiciousAppDetectionConfig instead */ blacklistedPackageNames?: string[]; + /** @deprecated Use SuspiciousAppDetectionConfig instead */ suspiciousPermissions?: string[][]; + /** @deprecated Use SuspiciousAppDetectionConfig instead */ whitelistedInstallationSources?: string[]; }; export type SuspiciousAppInfo = { packageInfo: PackageInfo; - reason: string; + reasons: string[]; permissions?: string[]; }; diff --git a/src/utils/malware.ts b/src/utils/malware.ts index 02929c3..6a6f634 100644 --- a/src/utils/malware.ts +++ b/src/utils/malware.ts @@ -18,7 +18,7 @@ export const toSuspiciousAppInfo = (base64Value: string): SuspiciousAppInfo => { const packageInfo = data.packageInfo as PackageInfo; return { packageInfo, - reason: data.reason, + reasons: data.reasons, permissions: data.permissions, } as SuspiciousAppInfo; };