Skip to content

Commit 2ea551c

Browse files
release: freeRASP 2.3.0 (#38)
* feat: Refactor Android and TypeScript architecture * feat: Complete Talsec SDK 17.0.1 integration & fixes * chore: dependency update * docs: CHANGELOG update * fix: random number generator update * chore: allChecksFinnished visualisation * refactor: Move startFreeRASP to dedicated module * docs: CHANGELOG update * fix: add missing package-lock.json * fix: package.json package-lock.json update * Fix: Regenerate package-lock.json to resolve CI/CD dependency issues * Fix: Explicitly add yaml dependency to resolve CI/CD lockfile error * fix: package-lock.json update * fix package lock * chore: action.yml update * feat: resolve eslint issues * chore: fix linters * chore: TalsecRuntime update * chore: add source files by path on ios * chore: update dist --------- Co-authored-by: Tomas Psota <to.psota@gmail.com> Co-authored-by: Tomas Psota <72520867+tompsota@users.noreply.github.com>
1 parent fc853a5 commit 2ea551c

File tree

152 files changed

+13112
-7886
lines changed

Some content is hidden

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

152 files changed

+13112
-7886
lines changed

.eslintrc.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module.exports = {
2+
parser: "@typescript-eslint/parser",
3+
parserOptions: {
4+
project: "./tsconfig.json"
5+
},
6+
plugins: [
7+
"@typescript-eslint",
8+
"import"
9+
],
10+
extends: [
11+
"eslint:recommended",
12+
"plugin:@typescript-eslint/recommended",
13+
"prettier",
14+
"plugin:import/typescript"
15+
],
16+
rules: {
17+
"no-fallthrough": "off",
18+
"no-constant-condition": "off",
19+
"@typescript-eslint/no-this-alias": "off",
20+
"@typescript-eslint/no-explicit-any": "off",
21+
"@typescript-eslint/explicit-module-boundary-types": [
22+
"error",
23+
{
24+
"allowArgumentsExplicitlyTypedAsAny": true
25+
}
26+
],
27+
"@typescript-eslint/array-type": "error",
28+
"@typescript-eslint/consistent-type-assertions": "error",
29+
"@typescript-eslint/consistent-type-imports": "error",
30+
"@typescript-eslint/prefer-for-of": "error",
31+
"@typescript-eslint/prefer-optional-chain": "error",
32+
"import/first": "error",
33+
"import/order": [
34+
"error",
35+
{
36+
"alphabetize": {
37+
"order": "asc",
38+
"caseInsensitive": false
39+
},
40+
"groups": [
41+
[
42+
"builtin",
43+
"external"
44+
],
45+
"parent",
46+
[
47+
"sibling",
48+
"index"
49+
]
50+
],
51+
"newlines-between": "always"
52+
}
53+
],
54+
"import/newline-after-import": "error",
55+
"import/no-duplicates": "error",
56+
"import/no-mutable-exports": "error"
57+
}
58+
};

.github/actions/setup/action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ runs:
55
using: composite
66
steps:
77
- name: Setup Node.js
8-
uses: actions/setup-node@v4
8+
uses: actions/setup-node@v6
99
with:
10-
node-version: '20.x'
10+
node-version: 24.x
1111
node-version-file: .nvmrc
1212

1313
- name: Upgrade npm for trusted publishing

CHANGELOG.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,63 @@ 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+
## [2.3.0] - 2025-12-15
9+
10+
- Android SDK version: 17.0.1
11+
- iOS SDK version: 6.13.0
12+
13+
### Capacitor
14+
15+
#### Added
16+
17+
- 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))
18+
- Added API for `timeSpoofing` callback into `ThreatEventActions` (Android only)
19+
- Added API for `unsecureWifi` callback into `ThreatEventActions` (Android only)
20+
- Added API for `allChecksFinished` callback into new `RaspExecutionStateEventActions` object
21+
- Added matched permissions to `SuspiciousAppInfo` object when malware detection reason is `suspiciousPermission`
22+
23+
#### Fixed
24+
25+
- Resolved potential collision in threat identifiers
26+
27+
### Android
28+
29+
#### Added
30+
31+
- 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)
32+
- We are introducing a new capability, detecting whether the device time has been tampered with (`timeSpoofing`)
33+
- We are introducing a new capability, detecting whether the location is being spoofed on the device (`locationSpoofing`)
34+
- We are introducing a new capability, detection of unsecure WiFi (`unecureWifi`)
35+
- Removed deprecated functionality `Pbkdf2Native` and both related native libraries (`libpbkdf2_native.so` and `libpolarssl.so`)
36+
- Added new `RaspExecutionState` which contains `onAllChecksFinished()` method, which is triggered after all checks are completed.
37+
- Added matched permissions to `SuspiciousAppInfo` object when malware detection reason is `suspiciousPermission`
38+
- 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)
39+
- Capability to check if another app has an option `REQUEST_INSTALL_PACKAGES` enabled in the system settings to malware detection
40+
41+
#### Fixed
42+
43+
- Root detection related bugs causing false positives
44+
- ANR issue caused by `registerScreenCaptureCallback()` method on the main thread
45+
- `NullPointerException` when checking key alias in Keystore on Android 7
46+
- `JaCoCo` issue causing `MethodTooLargeException` during instrumentation
47+
- `DeadApplicationException` when calling `Settings.Global.getInt` or `Settings.Secure.getInt` on invalid context
48+
- `AndroidKeyStore` crashes causing `java.util.concurrent.TimeoutException` when calling `finalize()` method on `Cipher` (GC issues)
49+
- Fixed issue with late initializers and `TalsecMode` coroutines scopes
50+
51+
52+
#### Changed
53+
54+
- Deprecated Nexus repository removed (GCP artifact registry is the main supported distribution repository)
55+
- Shortened the value of threat detection interval
56+
- Refactoring of internal architecture of SDK that newly uses Coroutines to manage threading
57+
- Update of internal dependencies and security libraries
58+
59+
### iOS
60+
61+
#### Changed
62+
63+
- Updated internal dependencies
64+
865
## [2.2.2] - 2025-08-12
966

1067
- iOS SDK version: 6.12.1

CapacitorFreerasp.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Pod::Spec.new do |s|
1010
s.homepage = package['repository']['url']
1111
s.author = package['author']
1212
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13-
s.source_files = 'ios/Plugin/*.{swift,h,m,c,cc,mm,cpp}', 'ios/Plugin/TalsecRuntime.xcframework'
13+
s.source_files = 'ios/Plugin/models/*.{swift,h,m,c,cc,mm,cpp}', 'ios/Plugin/utils/*.{swift,h,m,c,cc,mm,cpp}', 'ios/Plugin/*.{swift,h,m,c,cc,mm,cpp}', 'ios/Plugin/TalsecRuntime.xcframework'
1414
s.ios.deployment_target = '13.0'
1515
s.dependency 'Capacitor'
1616
s.swift_version = '5.1'

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,5 @@ dependencies {
7676
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
7777
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
7878

79-
implementation 'com.aheaditec.talsec.security:TalsecSecurity-Community-Capacitor:16.0.2'
79+
implementation 'com.aheaditec.talsec.security:TalsecSecurity-Community-Capacitor:17.0.1'
8080
}

android/src/main/java/com/aheaditec/freerasp/FreeraspPlugin.kt

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
1212
import com.aheaditec.talsec_security.security.api.Talsec
1313
import com.aheaditec.talsec_security.security.api.TalsecConfig
1414
import com.aheaditec.talsec_security.security.api.ThreatListener
15+
import com.aheaditec.freerasp.events.BaseRaspEvent
16+
import com.aheaditec.freerasp.events.RaspExecutionStateEvent
17+
import com.aheaditec.freerasp.events.ThreatEvent
1518
import com.getcapacitor.JSObject
1619
import com.getcapacitor.Plugin
1720
import com.getcapacitor.PluginCall
@@ -23,7 +26,7 @@ import org.json.JSONArray
2326
class FreeraspPlugin : Plugin() {
2427

2528
private val threatHandler = TalsecThreatHandler(this)
26-
private val listener = ThreatListener(threatHandler, threatHandler)
29+
private val listener = ThreatListener(threatHandler, threatHandler, threatHandler)
2730
private var registered = true
2831

2932
@PluginMethod
@@ -97,29 +100,51 @@ class FreeraspPlugin : Plugin() {
97100
*/
98101
@PluginMethod
99102
fun getThreatIdentifiers(call: PluginCall) {
100-
call.resolve(JSObject().put("ids", Threat.getThreatValues()))
103+
call.resolve(JSObject().put("ids", ThreatEvent.ALL_EVENTS))
101104
}
102105

103106
/**
104-
* Method to setup the message passing between native and React Native
105-
* @return list of [THREAT_CHANNEL_NAME, THREAT_CHANNEL_KEY]
107+
* Method to get the random identifiers of callbacks
108+
*/
109+
@PluginMethod
110+
fun getRaspExecutionStateIdentifiers(call: PluginCall) {
111+
call.resolve(JSObject().put("ids", RaspExecutionStateEvent.ALL_EVENTS))
112+
}
113+
114+
/**
115+
* Method to setup the message passing between native and Capacitor
116+
* @return list of [CHANNEL_NAME, CHANNEL_KEY, MALWARE_CHANNEL_KEY]
106117
*/
107118
@PluginMethod
108119
fun getThreatChannelData(call: PluginCall) {
109120
val channelData = JSONArray(
110121
(listOf(
111-
THREAT_CHANNEL_NAME, THREAT_CHANNEL_KEY, MALWARE_CHANNEL_KEY
122+
ThreatEvent.CHANNEL_NAME, ThreatEvent.CHANNEL_KEY, ThreatEvent.MALWARE_CHANNEL_KEY
112123
))
113124
)
114125
call.resolve(JSObject().put("ids", channelData))
115126
}
116127

128+
/**
129+
* Method to setup the execution state message passing between native and Capacitor
130+
* @return list of [CHANNEL_NAME, CHANNEL_KEY]
131+
*/
132+
@PluginMethod
133+
fun getRaspExecutionStateChannelData(call: PluginCall) {
134+
val channelData = JSONArray(
135+
(listOf(
136+
RaspExecutionStateEvent.CHANNEL_NAME, RaspExecutionStateEvent.CHANNEL_KEY
137+
))
138+
)
139+
call.resolve(JSObject().put("ids", channelData))
140+
}
141+
117142
/**
118143
* We never send an invalid callback over our channel.
119144
* Therefore, if this happens, we want to kill the app.
120145
*/
121146
@PluginMethod
122-
fun onInvalidCallback() {
147+
fun onInvalidCallback(call: PluginCall) {
123148
android.os.Process.killProcess(android.os.Process.myPid())
124149
}
125150

@@ -179,7 +204,7 @@ class FreeraspPlugin : Plugin() {
179204

180205
activity?.runOnUiThread {
181206
try {
182-
Talsec.blockScreenCapture(context, enable)
207+
Talsec.blockScreenCapture(activity, enable)
183208
call.resolve(JSObject().put("result", true))
184209
} catch (e: Exception) {
185210
call.reject(
@@ -224,11 +249,12 @@ class FreeraspPlugin : Plugin() {
224249
"Error during storeExternalId operation in freeRASP Native Plugin",
225250
"NativePluginError"
226251
)
252+
return
227253
}
228254
}
229255

230-
internal fun notifyListeners(threat: Threat) {
231-
notifyListeners(THREAT_CHANNEL_NAME, JSObject().put(THREAT_CHANNEL_KEY, threat.value), true)
256+
internal fun notifyListeners(event: BaseRaspEvent) {
257+
notifyListeners(event.channelName, JSObject().put(event.channelKey, event.value), true)
232258
}
233259

234260
internal fun notifyMalware(suspiciousApps: MutableList<SuspiciousAppInfo>) {
@@ -238,9 +264,9 @@ class FreeraspPlugin : Plugin() {
238264
val encodedSuspiciousApps = suspiciousApps.toEncodedJSArray(context)
239265
mainHandler.post {
240266
val params = JSObject()
241-
.put(THREAT_CHANNEL_KEY, Threat.Malware.value)
242-
.put(MALWARE_CHANNEL_KEY, encodedSuspiciousApps)
243-
notifyListeners(THREAT_CHANNEL_NAME, params, true)
267+
.put(ThreatEvent.CHANNEL_KEY, ThreatEvent.Malware.value)
268+
.put(ThreatEvent.MALWARE_CHANNEL_KEY, encodedSuspiciousApps)
269+
notifyListeners(ThreatEvent.CHANNEL_NAME, params, true)
244270
}
245271
}
246272
}
@@ -253,6 +279,7 @@ class FreeraspPlugin : Plugin() {
253279
.watcherMail(configJson.getString("watcherMail"))
254280
.supportedAlternativeStores(androidConfig.getArraySafe("supportedAlternativeStores"))
255281
.prod(configJson.getBool("isProd") ?: true)
282+
.killOnBypass(configJson.getBool("killOnBypass") ?: false)
256283

257284
if (androidConfig.has("malwareConfig")) {
258285
val malwareConfig = androidConfig.getJSONObject("malwareConfig")
@@ -266,12 +293,6 @@ class FreeraspPlugin : Plugin() {
266293

267294

268295
companion object {
269-
private val THREAT_CHANNEL_NAME = (10000..999999999).random()
270-
.toString() // name of the channel over which threat callbacks are sent
271-
private val THREAT_CHANNEL_KEY = (10000..999999999).random()
272-
.toString() // key of the argument map under which threats are expected
273-
private val MALWARE_CHANNEL_KEY = (10000..999999999).random()
274-
.toString() // key of the argument map under which malware data is expected
275296
private val backgroundHandlerThread = HandlerThread("BackgroundThread").apply { start() }
276297
private val backgroundHandler = Handler(backgroundHandlerThread.looper)
277298
private val mainHandler = Handler(Looper.getMainLooper())

android/src/main/java/com/aheaditec/freerasp/ScreenProtector.kt

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,43 @@ import android.view.WindowManager.SCREEN_RECORDING_STATE_VISIBLE
1111
import androidx.annotation.RequiresApi
1212
import androidx.core.content.ContextCompat
1313
import com.aheaditec.talsec_security.security.api.Talsec
14+
import com.aheaditec.freerasp.events.ThreatEvent
1415
import java.util.function.Consumer
1516

1617
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1718
internal object ScreenProtector {
1819
private const val TAG = "TalsecScreenProtector"
1920
private const val SCREEN_CAPTURE_PERMISSION = "android.permission.DETECT_SCREEN_CAPTURE"
2021
private const val SCREEN_RECORDING_PERMISSION = "android.permission.DETECT_SCREEN_RECORDING"
22+
2123
private var registered = false
22-
private val screenCaptureCallback = ScreenCaptureCallback { Talsec.onScreenshotDetected() }
24+
private val cachedThreats = mutableSetOf<ThreatEvent>()
25+
26+
private val screenCaptureCallback = ScreenCaptureCallback { handleThreat(ThreatEvent.Screenshot) }
2327
private val screenRecordCallback: Consumer<Int> = Consumer<Int> { state ->
2428
if (state == SCREEN_RECORDING_STATE_VISIBLE) {
25-
Talsec.onScreenRecordingDetected()
29+
handleThreat(ThreatEvent.ScreenRecording)
30+
}
31+
}
32+
33+
private fun handleThreat(threat: ThreatEvent) {
34+
if(!FreeraspPlugin.talsecStarted) {
35+
cachedThreats.add(threat)
36+
return
37+
}
38+
39+
when (threat) {
40+
ThreatEvent.Screenshot -> Talsec.onScreenshotDetected()
41+
ThreatEvent.ScreenRecording -> Talsec.onScreenRecordingDetected()
42+
else -> throw IllegalArgumentException("Unexpected Threat type: $threat")
2643
}
2744
}
2845

46+
internal fun flushCache() {
47+
cachedThreats.forEach { handleThreat(it) }
48+
cachedThreats.clear()
49+
}
50+
2951
/**
3052
* Registers screenshot and screen recording detector with the given activity
3153
*

android/src/main/java/com/aheaditec/freerasp/Threat.kt

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)