Skip to content
Merged
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
43 changes: 42 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,55 @@ 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).

## Unreleased
## [7.5.0] - 2026-03-02

- Android SDK version: 18.0.4
- iOS SDK version: 6.14.1

### Flutter

#### Changed

- Updated the internal handling of ExternalIdResult on Android (for `storeExternalId()` method)

### Android

#### Added

- Added support for `KernelSU` to the existing root detection capabilities
- Added support for `HMA` to the existing root detection capabilities
- Added new malware detection capabilities
- Added `onAutomationDetected()` callback to `ThreatDetected` interface
- We are introducing a new capability, detecting whether the device is being automated using tools like Appium
- Added value restrictions to `externalId`
- Method `storeExternalId()` now returns `ExternalIdResult`, which indicates `Success` or `Error` when `externalId` violates restrictions

#### Fixed

- Fixed exception handling for the KeyStore `getEntry` operation
- Fixed issue in `ScreenProtector` concerning the `onScreenRecordingDetected` invocations
- Merged internal shared libraries into a single one, reducing the final APK size
- Fixed bug related to key storing in keystore type detection (hw-backed keystore check)
- Fixed manifest queries merge

#### Changed

- Removed unused library `tmlib`
- Refactoring of signature verification code
- Updated compile and target API to 36
- Improved root detection capabilities
- Detection of wireless ADB added to ADB detections

### iOS

#### Added

- Added time spoofing detection, detecting an inaccurate device clock. It is a new threat `timeSpoofing`.

#### Changed

- Improved jailbreak detection methods.

## [7.4.0] - 2026-02-10
- Android SDK version: 18.0.2
- iOS SDK version: 6.13.0
Expand Down
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 = '18.0.2'
ext.talsec_version = '18.0.4'
repositories {
google()
mavenCentral()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,46 @@ import io.flutter.plugin.common.EventChannel.EventSink

internal class ExecutionStateDispatcher {
private val eventCache = mutableSetOf<RaspExecutionStateEvent>()
private var isAppInForeground = false

var eventSink: EventSink? = null
set(value) {
field = value
if (value != null) {
flushCache(value)
isAppInForeground = true
flushCache()
}
}

fun onResume() {
isAppInForeground = true
if (eventSink != null) {
flushCache()
}
}

fun onPause() {
isAppInForeground = false
}

fun dispatch(event: RaspExecutionStateEvent) {
val sink = synchronized(eventCache) {
val currentSink = eventSink
if (currentSink != null) {
currentSink
} else {
// We can only use the sink if the app is in foreground and the sink is not null
// We don't need to check for isListenerRegistered because it is equivalent to eventSink != null
if (isAppInForeground && eventSink != null) {
eventSink?.success(event.value)
} else {
synchronized(eventCache) {
eventCache.add(event)
null
}
}
sink?.success(event.value)
}

private fun flushCache(sink: EventSink) {
private fun flushCache() {
val events = synchronized(eventCache) {
val snapshot = eventCache.toSet()
eventCache.clear()
snapshot
}
events.forEach { sink.success(it.value) }
events.forEach { eventSink?.success(it.value) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,77 @@ import io.flutter.plugin.common.EventChannel.EventSink
internal class ThreatDispatcher {
private val threatCache = mutableSetOf<Threat>()
private val malwareCache = mutableListOf<SuspiciousAppInfo>()
private var isAppInForeground = false

var eventSink: EventSink? = null
set(value) {
field = value
if (value != null) {
flushThreatCache(value)
isAppInForeground = true
flushThreatCache()
}
}

var methodSink: MethodCallHandler.MethodSink? = null
set(value) {
field = value
if (value != null) {
flushMalwareCache(value)
isAppInForeground = true
flushMalwareCache()
}
}

fun onResume() {
isAppInForeground = true
if (eventSink != null) {
flushThreatCache()
}
if (methodSink != null) {
flushMalwareCache()
}
}

fun onPause() {
isAppInForeground = false
}

fun dispatchThreat(threat: Threat) {
val sink = synchronized(threatCache) {
val currentSink = eventSink
if (currentSink != null) {
currentSink
} else {
if (isAppInForeground && eventSink != null) {
eventSink?.success(threat.value)
} else {
synchronized(threatCache) {
threatCache.add(threat)
null
}
}
sink?.success(threat.value)
}

fun dispatchMalware(apps: List<SuspiciousAppInfo>) {
val sink = synchronized(malwareCache) {
val currentSink = methodSink
if (currentSink != null) {
currentSink
} else {
if (isAppInForeground && methodSink != null) {
methodSink?.onMalwareDetected(apps)
} else {
synchronized(malwareCache) {
malwareCache.addAll(apps)
null
}
}
sink?.onMalwareDetected(apps)
}

private fun flushThreatCache(sink: EventSink) {
private fun flushThreatCache() {
val threats = synchronized(threatCache) {
val snapshot = threatCache.toSet()
threatCache.clear()
snapshot
}
threats.forEach { sink.success(it.value) }
threats.forEach { eventSink?.success(it.value) }
}

private fun flushMalwareCache(sink: MethodCallHandler.MethodSink) {
private fun flushMalwareCache() {
val malware = synchronized(malwareCache) {
val snapshot = malwareCache.toMutableList()
malwareCache.clear()
snapshot
}
if (malware.isNotEmpty()) {
sink.onMalwareDetected(malware)
methodSink?.onMalwareDetected(malware)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,8 @@ internal object TalsecThreatHandler {
* [EventSink] is not destroyed but also is not able to send events.
*/
internal fun suspendListener() {
savedThreatEventSink = PluginThreatHandler.threatDispatcher.eventSink
PluginThreatHandler.threatDispatcher.eventSink = null

savedExecutionStateSink = PluginThreatHandler.executionStateDispatcher.eventSink
PluginThreatHandler.executionStateDispatcher.eventSink = null
PluginThreatHandler.threatDispatcher.onPause()
PluginThreatHandler.executionStateDispatcher.onPause()
}

/**
Expand All @@ -107,20 +104,10 @@ internal object TalsecThreatHandler {
* also is not able to send events.
*/
internal fun resumeListener() {
if (savedThreatEventSink != null) {
PluginThreatHandler.threatDispatcher.eventSink = savedThreatEventSink
savedThreatEventSink = null
}
if (savedExecutionStateSink != null) {
PluginThreatHandler.executionStateDispatcher.eventSink = savedExecutionStateSink
savedExecutionStateSink = null
}
PluginThreatHandler.threatDispatcher.onResume()
PluginThreatHandler.executionStateDispatcher.onResume()
}

private var savedThreatEventSink: EventSink? = null
private var savedExecutionStateSink: EventSink? = null


/**
* Called when a new listener subscribes to the event channel. Sends any previously detected
* threats to the new listener.
Expand All @@ -136,7 +123,6 @@ internal object TalsecThreatHandler {
*/
internal fun detachEventSink() {
PluginThreatHandler.threatDispatcher.eventSink = null
savedThreatEventSink = null
}

internal fun attachExecutionStateSink(eventSink: EventSink) {
Expand All @@ -145,7 +131,6 @@ internal object TalsecThreatHandler {

internal fun detachExecutionStateSink() {
PluginThreatHandler.executionStateDispatcher.eventSink = null
savedExecutionStateSink = null
}

internal fun attachMethodSink(sink: MethodCallHandler.MethodSink) {
Expand Down
1 change: 1 addition & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
.buildlog/
.history
.svn/
**/.cxx/

# IntelliJ related
*.iml
Expand Down
3 changes: 3 additions & 0 deletions ios/Classes/TalsecHandlers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ private let unofficialStoreValue = 629780916
private let systemVPNValue = 659382561
private let screenshotValue = 705651459
private let screenRecordingValue = 64690214
private let timeSpoofingValue = 189105221

/// Extension with submits events to plugin
extension SecurityThreatCenter: SecurityThreatHandler, TalsecRuntime.RaspExecutionState {
Expand Down Expand Up @@ -62,6 +63,8 @@ extension SecurityThreat {
return screenshotValue
case .screenRecording:
return screenRecordingValue
case .timeSpoofing:
return timeSpoofingValue
@unknown default:
return unknownValue
}
Expand Down
Binary file modified ios/TalsecRuntime.xcframework/_CodeSignature/CodeDirectory
Binary file not shown.
Binary file not shown.
Loading