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
62 changes: 58 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,60 @@ 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.3.0] - 2025-10-31

- Android SDK version: 17.0.1
- iOS SDK version: 6.13.0

### React Native

#### Added

- Added `killOnBypass` to `TalsecConfig` that configures if the app should be terminated when the threat callbacks are suppressed/hooked by an attacker (Android only) ([Issue 65](https://github.com/talsec/Free-RASP-Android/issues/65))
- Added API for `timeSpoofing` callback into `ThreatEventActions` (Android only)
- Added API for `unsecureWifi` callback into `ThreatEventActions` (Android only)
- Added API for `allChecksFinished` callback into new `RaspExecutionStateEventActions` object
- Added matched permissions to `SuspiciousAppInfo` object when malware detection reason is `suspiciousPermission`

#### Fixed

- Resolved potential collision in threat identifiers

### Android

#### Added

- Added `killOnBypass` method to the `TalsecConfig.Builder` that configures if the app should be terminated when the threat callbacks are suppressed/hooked by an attacker [Issue 65](https://github.com/talsec/Free-RASP-Android/issues/65)
- We are introducing a new capability, detecting whether the device time has been tampered with (`timeSpoofing`)
- We are introducing a new capability, detecting whether the location is being spoofed on the device (`locationSpoofing`)
- We are introducing a new capability, detection of unsecure WiFi (`unecureWifi`)
- Removed deprecated functionality `Pbkdf2Native` and both related native libraries (`libpbkdf2_native.so` and `libpolarssl.so`)
- Added new `RaspExecutionState` which contains `onAllChecksFinished()` method, which is triggered after all checks are completed.
- Added matched permissions to `SuspiciousAppInfo` object when malware detection reason is `suspiciousPermission`
- New option to start Talsec, `Talsec.start()` takes new parameter `TalsecMode` that determines the dispatcher thread of initialization and sync checks (uses background thread by default)
- Capability to check if another app has an option `REQUEST_INSTALL_PACKAGES` enabled in the system settings to malware detection

#### Fixed

- ANR issue caused by `registerScreenCaptureCallback()` method on the main thread
- `NullPointerException` when checking key alias in Keystore on Android 7
- `JaCoCo` issue causing `MethodTooLargeException` during instrumentation
- `DeadApplicationException` when calling `Settings.Global.getInt` or `Settings.Secure.getInt` on invalid context
- `AndroidKeyStore` crashes causing `java.util.concurrent.TimeoutException` when calling `finalize()` method on `Cipher` (GC issues)
- Fixed issue with late initializers and `TalsecMode` coroutines scopes

#### Changed

- Shortened the value of threat detection interval
- Refactoring of internal architecture of SDK that newly uses Coroutines to manage threading
- Update of internal dependencies and security libraries

### iOS

#### Changed

- Updated internal dependencies

## [4.2.4] - 2025-09-17

- iOS SDK version: 6.12.1
Expand Down Expand Up @@ -112,7 +166,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added interface for screenshot / screen recording blocking on iOS
- Added interface for external ID storage

### Android
### Android

#### Added

Expand Down Expand Up @@ -148,7 +202,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Android SDK requires `kotlinVersion` >= `2.0.0`
- Set Java verison to 17

### Android
### Android

#### Changed

Expand Down Expand Up @@ -200,7 +254,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Raised Android compileSDK level to 35

#### Fixed
#### Fixed

- Compatibility issues with RN New Architecture
- Added proguard rules for malware data serialization in release mode on Android
Expand Down Expand Up @@ -278,7 +332,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

#### Added

- Added configuration fields for malware detection
- Added configuration fields for malware detection

### Android

Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,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:16.0.4"
implementation "com.aheaditec.talsec.security:TalsecSecurity-Community-ReactNative:17.0.1"
}

if (isNewArchitectureEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.os.Looper
import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
import com.aheaditec.talsec_security.security.api.Talsec
import com.aheaditec.talsec_security.security.api.TalsecConfig
import com.aheaditec.talsec_security.security.api.TalsecMode
import com.aheaditec.talsec_security.security.api.ThreatListener
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.LifecycleEventListener
Expand All @@ -18,6 +19,9 @@ import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
import com.facebook.react.bridge.WritableArray
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.freeraspreactnative.events.BaseRaspEvent
import com.freeraspreactnative.events.RaspExecutionStateEvent
import com.freeraspreactnative.events.ThreatEvent
import com.freeraspreactnative.utils.Utils
import com.freeraspreactnative.utils.getArraySafe
import com.freeraspreactnative.utils.getBooleanSafe
Expand All @@ -29,7 +33,7 @@ import com.freeraspreactnative.utils.toEncodedWritableArray
class FreeraspReactNativeModule(private val reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {

private val listener = ThreatListener(FreeraspThreatHandler, FreeraspThreatHandler)
private val listener = ThreatListener(FreeraspThreatHandler, FreeraspThreatHandler, FreeraspThreatHandler)
private val lifecycleListener = object : LifecycleEventListener {
override fun onHostResume() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
Expand Down Expand Up @@ -67,7 +71,7 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
FreeraspThreatHandler.listener = ThreatListener
listener.registerListener(reactContext)
runOnUiThread {
Talsec.start(reactContext, config)
Talsec.start(reactContext, config, TalsecMode.BACKGROUND)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not give an option to set a different mode?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opted not to do so in Flutter as well

mainHandler.post {
talsecStarted = true
// This code must be called only AFTER Talsec.start
Expand All @@ -90,19 +94,39 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
*/
@ReactMethod
fun getThreatIdentifiers(promise: Promise) {
promise.resolve(Threat.getThreatValues())
promise.resolve(ThreatEvent.ALL_EVENTS)
}

/**
* Method to setup the message passing between native and React Native
* Method to get the random identifiers of callbacks
*/
@ReactMethod
fun getRaspExecutionStateIdentifiers(promise: Promise) {
promise.resolve(RaspExecutionStateEvent.ALL_EVENTS)
}

/**
* Method to setup the threat message passing between native and React Native
* @return list of [THREAT_CHANNEL_NAME, THREAT_CHANNEL_KEY]
*/
@ReactMethod
fun getThreatChannelData(promise: Promise) {
val channelData: WritableArray = Arguments.createArray()
channelData.pushString(THREAT_CHANNEL_NAME)
channelData.pushString(THREAT_CHANNEL_KEY)
channelData.pushString(MALWARE_CHANNEL_KEY)
channelData.pushString(ThreatEvent.CHANNEL_NAME)
channelData.pushString(ThreatEvent.CHANNEL_KEY)
channelData.pushString(ThreatEvent.MALWARE_CHANNEL_KEY)
promise.resolve(channelData)
}

/**
* Method to setup the execution state message passing between native and React Native
* @return list of [THREAT_CHANNEL_NAME, THREAT_CHANNEL_KEY]
*/
@ReactMethod
fun getRaspExecutionStateChannelData(promise: Promise) {
val channelData: WritableArray = Arguments.createArray()
channelData.pushString(RaspExecutionStateEvent.CHANNEL_NAME)
channelData.pushString(RaspExecutionStateEvent.CHANNEL_KEY)
promise.resolve(channelData)
}

Expand Down Expand Up @@ -207,8 +231,9 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex

val talsecBuilder = TalsecConfig.Builder(packageName, certificateHashes)
.watcherMail(config.getString("watcherMail"))
.supportedAlternativeStores(androidConfig.getArraySafe("supportedAlternativeStores"))
.prod(config.getBooleanSafe("isProd"))
.killOnBypass(config.getBooleanSafe("killOnBypass", false))
.supportedAlternativeStores(androidConfig.getArraySafe("supportedAlternativeStores"))

if (androidConfig.hasKey("malwareConfig")) {
val malwareConfig = androidConfig.getMapThrowing("malwareConfig")
Expand All @@ -223,12 +248,6 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex

companion object {
const val NAME = "FreeraspReactNative"
private val THREAT_CHANNEL_NAME = (10000..999999999).random()
.toString() // name of the channel over which threat callbacks are sent
private val THREAT_CHANNEL_KEY = (10000..999999999).random()
.toString() // key of the argument map under which threats are expected
private val MALWARE_CHANNEL_KEY = (10000..999999999).random()
.toString() // key of the argument map under which malware data is expected

private val backgroundHandlerThread = HandlerThread("BackgroundThread").apply { start() }
private val backgroundHandler = Handler(backgroundHandlerThread.looper)
Expand All @@ -238,11 +257,11 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex

internal var talsecStarted = false

private fun notifyListeners(threat: Threat) {
private fun notifyEvent(event: BaseRaspEvent) {
val params = Arguments.createMap()
params.putInt(THREAT_CHANNEL_KEY, threat.value)
params.putInt(event.channelKey, event.value)
appReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(THREAT_CHANNEL_NAME, params)
.emit(event.channelName, params)
}

/**
Expand All @@ -256,25 +275,29 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex

mainHandler.post {
val params = Arguments.createMap()
params.putInt(THREAT_CHANNEL_KEY, Threat.Malware.value)
params.putInt(ThreatEvent.CHANNEL_KEY, ThreatEvent.Malware.value)
params.putArray(
MALWARE_CHANNEL_KEY, encodedSuspiciousApps
ThreatEvent.MALWARE_CHANNEL_KEY, encodedSuspiciousApps
)

appReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(THREAT_CHANNEL_NAME, params)
.emit(ThreatEvent.CHANNEL_NAME, params)
}
}
}
}

internal object ThreatListener : FreeraspThreatHandler.TalsecReactNative {
override fun threatDetected(threatType: Threat) {
notifyListeners(threatType)
override fun threatDetected(threatEventType: ThreatEvent) {
notifyEvent(threatEventType)
}

override fun malwareDetected(suspiciousApps: MutableList<SuspiciousAppInfo>) {
notifyMalware(suspiciousApps)
}

override fun raspExecutionStateChanged(event: RaspExecutionStateEvent) {
notifyEvent(event)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,102 @@ package com.freeraspreactnative

import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
import com.aheaditec.talsec_security.security.api.ThreatListener
import com.freeraspreactnative.events.RaspExecutionStateEvent
import com.freeraspreactnative.events.ThreatEvent

internal object FreeraspThreatHandler : ThreatListener.ThreatDetected, ThreatListener.DeviceState {
internal object FreeraspThreatHandler : ThreatListener.ThreatDetected, ThreatListener.DeviceState, ThreatListener.RaspExecutionState() {

internal var listener: TalsecReactNative? = null

override fun onRootDetected() {
listener?.threatDetected(Threat.PrivilegedAccess)
listener?.threatDetected(ThreatEvent.PrivilegedAccess)
}

override fun onDebuggerDetected() {
listener?.threatDetected(Threat.Debug)
listener?.threatDetected(ThreatEvent.Debug)
}

override fun onEmulatorDetected() {
listener?.threatDetected(Threat.Simulator)
listener?.threatDetected(ThreatEvent.Simulator)
}

override fun onTamperDetected() {
listener?.threatDetected(Threat.AppIntegrity)
listener?.threatDetected(ThreatEvent.AppIntegrity)
}

override fun onUntrustedInstallationSourceDetected() {
listener?.threatDetected(Threat.UnofficialStore)
listener?.threatDetected(ThreatEvent.UnofficialStore)
}

override fun onHookDetected() {
listener?.threatDetected(Threat.Hooks)
listener?.threatDetected(ThreatEvent.Hooks)
}

override fun onDeviceBindingDetected() {
listener?.threatDetected(Threat.DeviceBinding)
listener?.threatDetected(ThreatEvent.DeviceBinding)
}

override fun onObfuscationIssuesDetected() {
listener?.threatDetected(Threat.ObfuscationIssues)
listener?.threatDetected(ThreatEvent.ObfuscationIssues)
}

override fun onMalwareDetected(suspiciousAppInfos: MutableList<SuspiciousAppInfo>?) {
override fun onMalwareDetected(suspiciousAppInfos: MutableList<SuspiciousAppInfo>) {
listener?.malwareDetected(suspiciousAppInfos ?: mutableListOf())
}

override fun onUnlockedDeviceDetected() {
listener?.threatDetected(Threat.Passcode)
listener?.threatDetected(ThreatEvent.Passcode)
}

override fun onHardwareBackedKeystoreNotAvailableDetected() {
listener?.threatDetected(Threat.SecureHardwareNotAvailable)
listener?.threatDetected(ThreatEvent.SecureHardwareNotAvailable)
}

override fun onDeveloperModeDetected() {
listener?.threatDetected(Threat.DevMode)
listener?.threatDetected(ThreatEvent.DevMode)
}

override fun onADBEnabledDetected() {
listener?.threatDetected(Threat.ADBEnabled)
listener?.threatDetected(ThreatEvent.ADBEnabled)
}

override fun onSystemVPNDetected() {
listener?.threatDetected(Threat.SystemVPN)
listener?.threatDetected(ThreatEvent.SystemVPN)
}

override fun onScreenshotDetected() {
listener?.threatDetected(Threat.Screenshot)
listener?.threatDetected(ThreatEvent.Screenshot)
}

override fun onScreenRecordingDetected() {
listener?.threatDetected(Threat.ScreenRecording)
listener?.threatDetected(ThreatEvent.ScreenRecording)
}

override fun onMultiInstanceDetected() {
listener?.threatDetected(Threat.MultiInstance)
listener?.threatDetected(ThreatEvent.MultiInstance)
}

override fun onUnsecureWifiDetected() {
listener?.threatDetected(ThreatEvent.UnsecureWifi)
}

override fun onTimeSpoofingDetected() {
listener?.threatDetected(ThreatEvent.TimeSpoofing)
}

override fun onLocationSpoofingDetected() {
listener?.threatDetected(ThreatEvent.LocationSpoofing)
}

override fun onAllChecksFinished() {
listener?.raspExecutionStateChanged(RaspExecutionStateEvent.AllChecksFinished)
}

internal interface TalsecReactNative {
fun threatDetected(threatType: Threat)
fun threatDetected(threatEventType: ThreatEvent)

fun malwareDetected(suspiciousApps: MutableList<SuspiciousAppInfo>)

fun raspExecutionStateChanged(event: RaspExecutionStateEvent)
}
}
Loading
Loading