Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
16f9d72
feat!: add new detection callbacks
yardexx Oct 20, 2025
45646e4
feat!: add new termination option
yardexx Oct 20, 2025
af33809
chore: update tests
yardexx Oct 20, 2025
52033f1
chore: raise SDK version
yardexx Oct 20, 2025
079967f
chore: raise version
yardexx Oct 20, 2025
5aa751e
feat: add new kill option
yardexx Oct 20, 2025
c2206c8
refactor(demo): fix `minSdkVersion`
yardexx Oct 20, 2025
2bd1439
refactor(demo): add new checks and run check control
yardexx Oct 20, 2025
2fa2248
chore: update iOS SDK
yardexx Oct 21, 2025
01adb87
refactor: add pigeon api
yardexx Oct 21, 2025
3ca2413
refactor: implement new callback
yardexx Oct 21, 2025
d302fd6
chore: add dependency
yardexx Oct 21, 2025
dc2f393
chore: update demo app
yardexx Oct 21, 2025
3565952
chore: update build script
yardexx Oct 21, 2025
62da88e
chore: update tests
yardexx Oct 21, 2025
360b000
chore: update analysis_options
yardexx Oct 21, 2025
ce1322d
chore: update docs
yardexx Oct 21, 2025
68160fa
style: formatting
yardexx Oct 21, 2025
c84821b
chore: update CHANGELOG
yardexx Oct 21, 2025
95a6cc6
ci: raise flutter sdk version
yardexx Oct 21, 2025
1d9406c
fix: downgrade `very_good_analysis`
yardexx Oct 21, 2025
b449f79
fix: downgrade `pigeon`
yardexx Oct 21, 2025
1dbc7a4
style: formatting
yardexx Oct 21, 2025
0101f0e
style: formatting
yardexx Oct 21, 2025
9d73ecf
fix: raise minSdkVersion
yardexx Oct 21, 2025
5cf0540
fix: argument handling
yardexx Oct 23, 2025
c43084c
fix: argument handling
yardexx Oct 23, 2025
5ff8e6e
fix: remove unnecessary log
yardexx Oct 23, 2025
9bfe516
fix: rename variable
yardexx Oct 23, 2025
3fbfde2
refactor: organise files
yardexx Oct 23, 2025
2511300
refactor: update generation path
yardexx Oct 23, 2025
f570969
refactor: update
yardexx Oct 23, 2025
06a282d
fix: add docs
yardexx Oct 23, 2025
08f47c7
chore: update CHANGELOG
yardexx Oct 23, 2025
8fa0eb2
fix: change default value of `killOnBypass`
yardexx Oct 23, 2025
a0718a5
docs: update
yardexx Oct 23, 2025
e0f37fe
chore: fix versions
yardexx Oct 23, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/flutter-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ on:
workflow_dispatch:

env:
FLUTTER_VERSION: 3.19.0
FLUTTER_VERSION: 3.24.0
PANA_VERSION: 0.22.8

jobs:
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@ 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).

## [7.3.0] - 2025-10-20
- Android SDK version: 17.0.0
- iOS SDK version: 6.13.0

### Flutter

#### 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 `onTimeSpoofing` callback to `ThreatCallback` for handling `Threat.timeSpoofing` threat (Android only)
- We are introducing a new capability, detecting whether the device time has been tampered with
- Added `onLocationSpoofing` callback to `ThreatCallback` for handling `Threat.locationSpoofing` threat (Android only)
- We are introducing a new capability, detecting whether the location is being spoofed on the device.
- Added `onUnsecureWifi` callback to `ThreatCallback` for handling `Threat.unsecureWifi` threat (Android only)
- We are introducing a new capability, detecting whether the device is connected to an unsecured Wi-Fi network.
- Added `onAllChecksDone` callback to new `RaspExecutionStateCallback`
- We are introducing a new callback that notifies when all security checks have been completed.

### Android

#### Removed
- Removed deprecated functionality `Pbkdf2Native` and both related native libraries (`libpbkdf2_native.so` and `libpolarssl.so`)

#### Changed
- Updated internal dependencies

### iOS

#### Changed
- Updated internal dependencies

## [7.2.2] - 2025-10-09
- iOS SDK version: 6.12.1
- Android SDK version: 16.0.1
Expand Down
2 changes: 2 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
include: package:very_good_analysis/analysis_options.yaml

analyzer:
errors:
document_ignores: false
exclude:
- '**/*.g.dart'
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '2.1.0'
ext.talsec_version = '16.0.1'
ext.talsec_version = '17.0.0'
repositories {
google()
mavenCentral()
Expand Down
6 changes: 6 additions & 0 deletions android/src/main/kotlin/com/aheaditec/freerasp/Threat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,10 @@ internal sealed class Threat(val value: Int) {
object ScreenRecording : Threat(64690214)

object MultiInstance : Threat(859307284)

object UnsecureWiFi : Threat(363588890)

object TimeSpoofing : Threat(189105221)

object LocationSpoofing : Threat(653273273)
}
2 changes: 2 additions & 0 deletions android/src/main/kotlin/com/aheaditec/freerasp/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal object Utils {

val watcherMail = json.getString("watcherMail")
val isProd = json.getBoolean("isProd")
val killOnBypass = json.optBoolean("killOnBypass")
val androidConfig = json.getJSONObject("androidConfig")
val packageName = androidConfig.getString("packageName")
val certificateHashes = androidConfig.extractArray<String>("signingCertHashes")
Expand All @@ -41,6 +42,7 @@ internal object Utils {
.watcherMail(watcherMail)
.supportedAlternativeStores(alternativeStores)
.prod(isProd)
.killOnBypass(killOnBypass)
.blacklistedPackageNames(malwareConfig.blacklistedPackageNames)
.blacklistedHashes(malwareConfig.blacklistedHashes)
.suspiciousPermissions(malwareConfig.suspiciousPermissions)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Autogenerated from Pigeon (v22.7.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

package com.aheaditec.freerasp.generated

import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer

private fun createConnectionError(channelName: String): FlutterError {
return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "")}
private open class RaspExecutionStatePigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return super.readValueOfType(type, buffer)
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
super.writeValue(stream, value)
}
}

/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */
class RaspExecutionState(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") {
companion object {
/** The codec used by RaspExecutionState. */
val codec: MessageCodec<Any?> by lazy {
RaspExecutionStatePigeonCodec()
}
}
fun onAllChecksFinished(callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.freerasp.RaspExecutionState.onAllChecksFinished$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// Autogenerated from Pigeon (v22.4.0), do not edit directly.
// Autogenerated from Pigeon (v22.7.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

package com.aheaditec.freerasp.generated

import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import android.os.Looper
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import com.aheaditec.freerasp.runResultCatching
import com.aheaditec.freerasp.Utils
import com.aheaditec.freerasp.generated.RaspExecutionState
import com.aheaditec.freerasp.runResultCatching
import com.aheaditec.freerasp.generated.TalsecPigeonApi
import com.aheaditec.freerasp.toPigeon
import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
Expand All @@ -26,7 +27,8 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler
internal class MethodCallHandler : MethodCallHandler, LifecycleEventObserver {
private var context: Context? = null
private var methodChannel: MethodChannel? = null
private var pigeonApi: TalsecPigeonApi? = null
private var talsecPigeon: TalsecPigeonApi? = null
private var raspExecutionPigeon : RaspExecutionState? = null
private val backgroundHandlerThread = HandlerThread("BackgroundThread").apply { start() }
private val backgroundHandler = Handler(backgroundHandlerThread.looper)
private val mainHandler = Handler(Looper.getMainLooper())
Expand All @@ -41,7 +43,7 @@ internal class MethodCallHandler : MethodCallHandler, LifecycleEventObserver {
override fun onMalwareDetected(packageInfo: List<SuspiciousAppInfo>) {
context?.let { context ->
val pigeonPackageInfo = packageInfo.map { it.toPigeon(context) }
pigeonApi?.onMalwareDetected(pigeonPackageInfo) { result ->
talsecPigeon?.onMalwareDetected(pigeonPackageInfo) { result ->
// Parse the result (which is Unit so we can ignore it) or throw an exception
// Exceptions are translated to Flutter errors automatically
result.getOrElse {
Expand All @@ -51,10 +53,22 @@ internal class MethodCallHandler : MethodCallHandler, LifecycleEventObserver {
}
}
}

override fun onAllChecksFinished() {
raspExecutionPigeon?.onAllChecksFinished { result ->
// Parse the result (which is Unit so we can ignore it) or throw an exception
// Exceptions are translated to Flutter errors automatically
result.getOrElse {
Log.e("MethodCallHandlerSink", "Result ended with failure")
throw it
}
}
}
}

internal interface MethodSink {
fun onMalwareDetected(packageInfo: List<SuspiciousAppInfo>)
fun onAllChecksFinished()
}

/**
Expand All @@ -76,7 +90,8 @@ internal class MethodCallHandler : MethodCallHandler, LifecycleEventObserver {
}

this.context = context
this.pigeonApi = TalsecPigeonApi(messenger)
this.talsecPigeon = TalsecPigeonApi(messenger)
this.raspExecutionPigeon = RaspExecutionState(messenger)

TalsecThreatHandler.attachMethodSink(sink)
}
Expand All @@ -89,7 +104,8 @@ internal class MethodCallHandler : MethodCallHandler, LifecycleEventObserver {
methodChannel = null

this.context = null
this.pigeonApi = null
this.talsecPigeon = null
this.raspExecutionPigeon = null

TalsecThreatHandler.detachMethodSink()
}
Expand Down Expand Up @@ -184,9 +200,13 @@ internal class MethodCallHandler : MethodCallHandler, LifecycleEventObserver {
*/
private fun blockScreenCapture(call: MethodCall, result: MethodChannel.Result) {
runResultCatching(result) {
val enable = call.argument<Boolean>("enable") ?: false
Talsec.blockScreenCapture(activity, enable)
result.success(null)
val enable = call.argument<Boolean>("enable") ?: throw NullPointerException("Enable flag cannot be null.")
activity?.let {
Talsec.blockScreenCapture(it, enable)
result.success(null)
return@runResultCatching
}
throw IllegalStateException("Unable to block screen capture - context is null")
}
}

Expand All @@ -209,9 +229,13 @@ internal class MethodCallHandler : MethodCallHandler, LifecycleEventObserver {
*/
private fun storeExternalId(call: MethodCall, result: MethodChannel.Result) {
runResultCatching(result) {
val data = call.argument<String>("data")
Talsec.storeExternalId(context, data)
result.success(null)
context?.let {
val data = call.argument<String>("data") ?: throw NullPointerException("External ID data cannot be null.")
Talsec.storeExternalId(it, data)
result.success(null)
return@runResultCatching
}
throw IllegalStateException("Unable to store external ID - context is null")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.aheaditec.freerasp.Threat
import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
import com.aheaditec.talsec_security.security.api.ThreatListener
import com.aheaditec.talsec_security.security.api.ThreatListener.DeviceState
import com.aheaditec.talsec_security.security.api.ThreatListener.RaspExecutionState
import com.aheaditec.talsec_security.security.api.ThreatListener.ThreatDetected

/**
Expand All @@ -13,12 +14,13 @@ import com.aheaditec.talsec_security.security.api.ThreatListener.ThreatDetected
* The object provides methods to register a listener for threat notifications and notifies the
* listener when a security threat is detected.
*/
internal object PluginThreatHandler : ThreatDetected, DeviceState {
internal object PluginThreatHandler : ThreatDetected, DeviceState, RaspExecutionState() {
internal val detectedThreats = mutableSetOf<Threat>()
internal val detectedMalware = mutableListOf<SuspiciousAppInfo>()
internal var shouldNotifyAllChecksFinished = false

internal var listener: TalsecFlutter? = null
private val internalListener = ThreatListener(this, this)
private val internalListener = ThreatListener(this, this, this)

internal fun registerListener(context: Context) {
internalListener.registerListener(context)
Expand All @@ -28,6 +30,10 @@ internal object PluginThreatHandler : ThreatDetected, DeviceState {
internalListener.unregisterListener(context)
}

override fun onAllChecksFinished() {
notifyAllChecksFinished()
}

override fun onRootDetected() {
notify(Threat.PrivilegedAccess)
}
Expand Down Expand Up @@ -96,6 +102,18 @@ internal object PluginThreatHandler : ThreatDetected, DeviceState {
notify(Threat.MultiInstance)
}

override fun onUnsecureWifiDetected() {
notify(Threat.UnsecureWiFi)
}

override fun onTimeSpoofingDetected() {
notify(Threat.TimeSpoofing)
}

override fun onLocationSpoofingDetected() {
notify(Threat.LocationSpoofing)
}

private fun notify(threat: Threat) {
listener?.threatDetected(threat) ?: detectedThreats.add(threat)
}
Expand All @@ -104,9 +122,15 @@ internal object PluginThreatHandler : ThreatDetected, DeviceState {
listener?.malwareDetected(suspiciousApps) ?: detectedMalware.addAll(suspiciousApps)
}

private fun notifyAllChecksFinished() {
listener?.allChecksFinished() ?: run { shouldNotifyAllChecksFinished = true }
}

internal interface TalsecFlutter {
fun threatDetected(threatType: Threat)

fun malwareDetected(suspiciousApps: List<SuspiciousAppInfo>)

fun allChecksFinished()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,13 @@ internal object TalsecThreatHandler {
}
}

if (PluginThreatHandler.shouldNotifyAllChecksFinished) {
methodSink?.onAllChecksFinished()
}

PluginThreatHandler.detectedThreats.clear()
PluginThreatHandler.detectedMalware.clear()
PluginThreatHandler.shouldNotifyAllChecksFinished = false
}

internal fun attachMethodSink(sink: MethodCallHandler.MethodSink) {
Expand All @@ -172,6 +177,10 @@ internal object TalsecThreatHandler {
override fun malwareDetected(suspiciousApps: List<SuspiciousAppInfo>) {
methodSink?.onMalwareDetected(suspiciousApps)
}

override fun allChecksFinished() {
methodSink?.onAllChecksFinished()
}
}
}

4 changes: 4 additions & 0 deletions example/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
include: package:very_good_analysis/analysis_options.yaml

analyzer:
errors:
document_ignores: false
7 changes: 6 additions & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
<uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
<uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />

<application
<!-- These permissions are needed if want to detect location spoofing and unsecure wifi. -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<application
android:label="freerasp_example"
android:icon="@mipmap/ic_launcher">
<activity
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<string>13.0</string>
</dict>
</plist>
Loading