Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class RNSuspiciousAppInfo(
val packageInfo: RNPackageInfo,
val reason: String,
val reasons: Set<String>,
val permissions: Set<String>?
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -74,7 +78,7 @@ internal fun ReadableMap.getNestedArraySafe(key: String): Array<Array<String>> {
internal fun SuspiciousAppInfo.toRNSuspiciousAppInfo(context: ReactContext): RNSuspiciousAppInfo {
return RNSuspiciousAppInfo(
packageInfo = this.packageInfo.toRNPackageInfo(context),
reason = this.reason,
reasons = this.reasons,
permissions = this.permissions
)
}
Expand All @@ -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
Expand Down
15 changes: 10 additions & 5 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,23 @@
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',
],
['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: {
Expand Down Expand Up @@ -315,7 +320,7 @@
);
};

useFreeRasp(config, actions, raspExecutionStateActions);

Check failure on line 323 in example/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint

Argument of type '{ androidConfig: { packageName: string; certificateHashes: string[]; suspiciousAppDetectionConfig: { packageNames: string[]; hashes: string[]; requestedPermissions: string[][]; grantedPermissions: string[][]; malwareScanScope: { ...; }; reasonMode: string; }; }; iosConfig: { ...; }; watcherMail: string; isProd: bool...' is not assignable to parameter of type 'TalsecConfig'.

return (
<DemoApp
Expand Down
4 changes: 2 additions & 2 deletions example/src/MalwareItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ export const MalwareItem: React.FC<{ app: SuspiciousAppInfo }> = ({ app }) => {
<Text style={styles.listItem}>
{app.packageInfo.installerStore ?? 'Not specified'}
</Text>
<Text style={styles.listItemTitle}>Detection reason:</Text>
<Text style={styles.listItem}>{app.reason}</Text>
<Text style={styles.listItemTitle}>Detection reasons:</Text>
<Text style={styles.listItem}>{app.reasons.join(', ')}</Text>
<Text style={styles.listItemTitle}>Granted permissions:</Text>
<Text style={styles.listItem}>
{app.permissions?.join(', ') ?? 'Not specified'}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
30 changes: 29 additions & 1 deletion src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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[];
};

Expand Down
2 changes: 1 addition & 1 deletion src/utils/malware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Loading