Skip to content

Commit 0d87872

Browse files
release: freeRASP 4.5.0 (#140)
* feat: refactor event dispatcher * fix * update SDKs * add ios identifier * feat: update ios sdk * feat: unify lifecycle events * fix * update changelog for ios * lint * fix: changelog + version * Update CHANGELOG.md Co-authored-by: martinzigrai <102868889+martinzigrai@users.noreply.github.com> --------- Co-authored-by: martinzigrai <102868889+martinzigrai@users.noreply.github.com>
1 parent 093b4c0 commit 0d87872

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1821
-2095
lines changed

CHANGELOG.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,55 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [4.5.0] - 2026-03-03
9+
10+
- Android SDK version: 18.0.4
11+
- iOS SDK version: 6.14.1
12+
13+
### React Native
14+
15+
#### Changed
16+
17+
- Updated the internal handling of ExternalIdResult on Android (for `storeExternalId()` method)
18+
19+
### Android
20+
21+
#### Added
22+
23+
- Added support for `KernelSU` to the existing root detection capabilities
24+
- Added support for `HMA` to the existing root detection capabilities
25+
- Added new malware detection capabilities
26+
- Added `onAutomationDetected()` callback to `ThreatDetected` interface
27+
- We are introducing a new capability, detecting whether the device is being automated using tools like Appium
28+
- Added value restrictions to `externalId`
29+
- Method `storeExternalId()` now returns `ExternalIdResult`, which indicates `Success` or `Error` when `externalId` violates restrictions
30+
31+
#### Fixed
32+
33+
- Fixed exception handling for the KeyStore `getEntry` operation
34+
- Fixed issue in `ScreenProtector` concerning the `onScreenRecordingDetected` invocations
35+
- Merged internal shared libraries into a single one, reducing the final APK size
36+
- Fixed bug related to key storing in keystore type detection (hw-backed keystore check)
37+
- Fixed manifest queries merge
38+
39+
#### Changed
40+
41+
- Removed unused library `tmlib`
42+
- Refactoring of signature verification code
43+
- Updated compile and target API to 36
44+
- Improved root detection capabilities
45+
- Detection of wireless ADB added to ADB detections
46+
47+
### iOS
48+
49+
#### Added
50+
51+
- Added time spoofing detection, detecting an inaccurate device clock. It is a new threat `timeSpoofing`.
52+
53+
#### Changed
54+
55+
- Improved jailbreak detection methods.
56+
857
## [4.4.0] - 2026-02-06
958

1059
- Android SDK version: 18.0.1

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ dependencies {
105105
implementation "com.facebook.react:react-native:$react_native_version"
106106
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
107107
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
108-
implementation "com.aheaditec.talsec.security:TalsecSecurity-Community-ReactNative:18.0.2"
108+
implementation "com.aheaditec.talsec.security:TalsecSecurity-Community-ReactNative:18.0.4"
109109
}
110110

111111
if (isNewArchitectureEnabled()) {

android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
99
import com.aheaditec.talsec_security.security.api.Talsec
1010
import com.aheaditec.talsec_security.security.api.TalsecConfig
1111
import com.aheaditec.talsec_security.security.api.TalsecMode
12-
import com.aheaditec.talsec_security.security.api.ThreatListener
1312
import com.facebook.react.bridge.Arguments
1413
import com.facebook.react.bridge.LifecycleEventListener
1514
import com.facebook.react.bridge.Promise
@@ -19,8 +18,8 @@ import com.facebook.react.bridge.ReactMethod
1918
import com.facebook.react.bridge.ReadableMap
2019
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
2120
import com.facebook.react.bridge.WritableArray
21+
import com.facebook.react.bridge.WritableMap
2222
import com.facebook.react.modules.core.DeviceEventManagerModule
23-
import com.freeraspreactnative.events.BaseRaspEvent
2423
import com.freeraspreactnative.events.RaspExecutionStateEvent
2524
import com.freeraspreactnative.events.ThreatEvent
2625
import com.freeraspreactnative.interfaces.PluginExecutionStateListener
@@ -38,15 +37,24 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
3837

3938
private val lifecycleListener = object : LifecycleEventListener {
4039
override fun onHostResume() {
40+
PluginThreatHandler.threatDispatcher.onResume()
41+
PluginThreatHandler.executionStateDispatcher.onResume()
4142
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
4243
reactContext.currentActivity?.let { ScreenProtector.register(it) }
4344
}
4445
}
4546

4647
override fun onHostPause() {
48+
PluginThreatHandler.threatDispatcher.onPause()
49+
PluginThreatHandler.executionStateDispatcher.onPause()
4750
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
4851
reactContext.currentActivity?.let { ScreenProtector.unregister(it) }
4952
}
53+
if (reactContext.currentActivity?.isFinishing == true) {
54+
PluginThreatHandler.threatDispatcher.unregisterListener()
55+
PluginThreatHandler.executionStateDispatcher.unregisterListener()
56+
PluginThreatHandler.unregisterSDKListener(reactContext)
57+
}
5058
}
5159

5260
override fun onHostDestroy() {
@@ -61,16 +69,21 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
6169
init {
6270
reactContext.addLifecycleEventListener(lifecycleListener)
6371
initializeEventKeys()
72+
PluginThreatHandler.initializeDispatchers(PluginListener(reactContext))
6473
}
6574

6675
@ReactMethod
6776
fun talsecStart(
6877
options: ReadableMap, promise: Promise
6978
) {
79+
if (talsecStarted) {
80+
promise.resolve("freeRASP started")
81+
return
82+
}
7083

7184
try {
7285
val config = buildTalsecConfig(options)
73-
PluginThreatHandler.registerListener(reactContext)
86+
PluginThreatHandler.registerSDKListener(reactContext)
7487
runOnUiThread {
7588
Talsec.start(reactContext, config, TalsecMode.BACKGROUND)
7689
mainHandler.post {
@@ -149,10 +162,10 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
149162
@ReactMethod
150163
fun addListener(eventName: String) {
151164
if (eventName == ThreatEvent.CHANNEL_NAME) {
152-
PluginThreatHandler.threatDispatcher.listener = PluginListener(reactContext)
165+
PluginThreatHandler.threatDispatcher.registerListener()
153166
}
154167
if (eventName == RaspExecutionStateEvent.CHANNEL_NAME) {
155-
PluginThreatHandler.executionStateDispatcher.listener = PluginListener(reactContext)
168+
PluginThreatHandler.executionStateDispatcher.registerListener()
156169
}
157170
}
158171

@@ -165,10 +178,10 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
165178
@ReactMethod
166179
fun removeListenerForEvent(eventName: String, promise: Promise) {
167180
if (eventName == ThreatEvent.CHANNEL_NAME) {
168-
PluginThreatHandler.threatDispatcher.listener = null
181+
PluginThreatHandler.threatDispatcher.unregisterListener()
169182
}
170183
if (eventName == RaspExecutionStateEvent.CHANNEL_NAME) {
171-
PluginThreatHandler.executionStateDispatcher.listener = null
184+
PluginThreatHandler.executionStateDispatcher.unregisterListener()
172185
}
173186
promise.resolve("Listener unregistered")
174187
}
@@ -302,48 +315,40 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
302315
private val mainHandler = Handler(Looper.getMainLooper())
303316

304317
internal var talsecStarted = false
318+
}
319+
320+
internal class PluginListener(private val reactContext: ReactApplicationContext) :
321+
PluginThreatListener, PluginExecutionStateListener {
305322

306-
private fun notifyEvent(event: BaseRaspEvent, appReactContext: ReactApplicationContext) {
323+
override fun threatDetected(threatEventType: ThreatEvent) {
307324
val params = Arguments.createMap()
308-
params.putInt(event.channelKey, event.value)
309-
appReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
310-
.emit(event.channelName, params)
325+
params.putInt(threatEventType.channelKey, threatEventType.value)
326+
notifyEvent(ThreatEvent.CHANNEL_NAME, params)
311327
}
312328

313-
/**
314-
* Sends malware detected event to React Native
315-
*/
316-
private fun notifyMalware(suspiciousApps: MutableList<SuspiciousAppInfo>, appReactContext: ReactApplicationContext) {
317-
// Perform the malware encoding on a background thread
329+
override fun malwareDetected(suspiciousApps: MutableList<SuspiciousAppInfo>) {
318330
backgroundHandler.post {
319-
320-
val encodedSuspiciousApps = suspiciousApps.toEncodedWritableArray(appReactContext)
321-
331+
val encodedSuspiciousApps = suspiciousApps.toEncodedWritableArray(reactContext)
322332
mainHandler.post {
323333
val params = Arguments.createMap()
324334
params.putInt(ThreatEvent.CHANNEL_KEY, ThreatEvent.Malware.value)
325335
params.putArray(
326336
ThreatEvent.MALWARE_CHANNEL_KEY, encodedSuspiciousApps
327337
)
328-
329-
appReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
330-
.emit(ThreatEvent.CHANNEL_NAME, params)
338+
notifyEvent(ThreatEvent.CHANNEL_NAME, params)
331339
}
332340
}
333341
}
334-
}
335-
336-
internal class PluginListener(private val reactContext: ReactApplicationContext) : PluginThreatListener, PluginExecutionStateListener {
337-
override fun threatDetected(threatEventType: ThreatEvent) {
338-
notifyEvent(threatEventType, reactContext)
339-
}
340342

341-
override fun malwareDetected(suspiciousApps: MutableList<SuspiciousAppInfo>) {
342-
notifyMalware(suspiciousApps, reactContext)
343+
override fun raspExecutionStateChanged(event: RaspExecutionStateEvent) {
344+
val params = Arguments.createMap()
345+
params.putInt(event.channelKey, event.value)
346+
notifyEvent(event.channelName, params)
343347
}
344348

345-
override fun raspExecutionStateChanged(event: RaspExecutionStateEvent) {
346-
notifyEvent(event, reactContext)
349+
private fun notifyEvent(eventName: String, params: WritableMap) {
350+
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
351+
.emit(eventName, params)
347352
}
348353
}
349354
}

android/src/main/java/com/freeraspreactnative/PluginThreatHandler.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ import com.freeraspreactnative.events.ThreatEvent
1010

1111
internal object PluginThreatHandler {
1212

13-
internal val threatDispatcher = ThreatDispatcher()
14-
internal val executionStateDispatcher = ExecutionStateDispatcher()
13+
internal lateinit var threatDispatcher: ThreatDispatcher
14+
internal lateinit var executionStateDispatcher: ExecutionStateDispatcher
15+
16+
fun initializeDispatchers(listener: FreeraspReactNativeModule.PluginListener) {
17+
threatDispatcher = ThreatDispatcher(listener)
18+
executionStateDispatcher = ExecutionStateDispatcher(listener)
19+
}
1520

1621
private val threatDetected = object : ThreatListener.ThreatDetected() {
1722

@@ -111,11 +116,11 @@ internal object PluginThreatHandler {
111116

112117
private val internalListener = ThreatListener(threatDetected, deviceState, raspExecutionState)
113118

114-
internal fun registerListener(context: Context) {
119+
internal fun registerSDKListener(context: Context) {
115120
internalListener.registerListener(context)
116121
}
117122

118-
internal fun unregisterListener(context: Context) {
123+
internal fun unregisterSDKListener(context: Context) {
119124
internalListener.unregisterListener(context)
120125
}
121126
}

android/src/main/java/com/freeraspreactnative/dispatchers/ExecutionStateDispatcher.kt

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,51 @@ package com.freeraspreactnative.dispatchers
33
import com.freeraspreactnative.events.RaspExecutionStateEvent
44
import com.freeraspreactnative.interfaces.PluginExecutionStateListener
55

6-
internal class ExecutionStateDispatcher {
6+
internal class ExecutionStateDispatcher(private val listener: PluginExecutionStateListener) {
77
private val cache = mutableSetOf<RaspExecutionStateEvent>()
88

9-
var listener: PluginExecutionStateListener? = null
10-
set(value) {
11-
field = value
12-
if (value != null) {
13-
flushCache(value)
14-
}
9+
private var isAppInForeground = false
10+
private var isListenerRegistered = false
11+
12+
fun registerListener() {
13+
isListenerRegistered = true
14+
isAppInForeground = true
15+
flushCache()
16+
}
17+
18+
fun unregisterListener() {
19+
isListenerRegistered = false
20+
isAppInForeground = false
21+
}
22+
23+
fun onResume() {
24+
isAppInForeground = true
25+
if (isListenerRegistered) {
26+
flushCache()
1527
}
28+
}
29+
30+
fun onPause() {
31+
isAppInForeground = false
32+
}
1633

1734
fun dispatch(event: RaspExecutionStateEvent) {
18-
val currentListener = listener
19-
if (currentListener != null) {
20-
currentListener.raspExecutionStateChanged(event)
35+
if (isAppInForeground && isListenerRegistered) {
36+
listener.raspExecutionStateChanged(event)
2137
} else {
2238
synchronized(cache) {
23-
val checkedListener = listener
24-
checkedListener?.raspExecutionStateChanged(event) ?: cache.add(event)
39+
cache.add(event)
2540
}
2641
}
2742
}
2843

29-
private fun flushCache(registeredListener: PluginExecutionStateListener) {
44+
private fun flushCache() {
3045
val events = synchronized(cache) {
3146
val snapshot = cache.toSet()
3247
cache.clear()
3348
snapshot
3449
}
35-
events.forEach { registeredListener.raspExecutionStateChanged(it) }
50+
events.forEach { listener.raspExecutionStateChanged(it) }
3651
}
3752
}
3853

0 commit comments

Comments
 (0)